summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CLA145
-rw-r--r--LICENSE150
-rw-r--r--README17
-rw-r--r--docs/_static/logo.pngbin0 -> 29742 bytes
-rw-r--r--docs/access_api.rst121
-rw-r--r--docs/api/datatypes.rst477
-rw-r--r--docs/api/json_api.rst106
-rw-r--r--docs/api/overview.rst36
-rw-r--r--docs/api/thrift_api.rst74
-rw-r--r--docs/conf.py17
-rwxr-xr-xdocs/extend_pyload.rst13
-rw-r--r--docs/index.rst46
-rw-r--r--docs/module_overview.rst17
-rw-r--r--docs/plugins/account_plugin.rst11
-rw-r--r--docs/plugins/addon_plugin.rst (renamed from docs/write_hooks.rst)39
-rw-r--r--docs/plugins/base_plugin.rst117
-rw-r--r--docs/plugins/crypter_plugin.rst69
-rw-r--r--docs/plugins/hoster_plugin.rst57
-rwxr-xr-xdocs/plugins/overview.rst33
-rw-r--r--docs/system/hoster_diagrams.rst16
-rwxr-xr-xdocs/system/overview.rst25
-rw-r--r--docs/system/plugin_hierarchy.rst13
-rw-r--r--docs/system/pyload_DataLayout.pngbin0 -> 57771 bytes
-rw-r--r--docs/system/pyload_PluginHierarchy.pngbin0 -> 22550 bytes
-rw-r--r--docs/system/pyload_ad_Hoster.pngbin0 -> 34237 bytes
-rw-r--r--docs/system/pyload_sd_Hoster.pngbin0 -> 28501 bytes
-rw-r--r--docs/write_plugins.rst103
-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--module/AccountManager.py140
-rw-r--r--module/AddonManager.py257
-rw-r--r--module/Api.py1130
-rw-r--r--module/CaptchaManager.py158
-rw-r--r--module/ConfigParser.py392
-rw-r--r--module/FileManager.py583
-rw-r--r--module/HookManager.py315
-rw-r--r--module/InitHomeDir.py6
-rw-r--r--module/PluginManager.py403
-rw-r--r--module/PluginThread.py677
-rw-r--r--module/PullEvents.py120
-rw-r--r--module/PyPackage.py80
-rw-r--r--module/UserManager.py23
-rw-r--r--module/Utils.py201
-rw-r--r--module/cli/ManageFiles.py6
-rw-r--r--module/common/APIExerciser.py8
-rw-r--r--module/common/packagetools.py4
-rw-r--r--module/config/ConfigParser.py252
-rw-r--r--module/config/__init__.py1
-rw-r--r--module/config/default.conf65
-rw-r--r--module/config/default.py115
-rw-r--r--module/config/gui_default.xml13
-rw-r--r--module/database/AccountDatabase.py21
-rw-r--r--module/database/ConfigDatabase.py49
-rw-r--r--module/database/DatabaseBackend.py517
-rw-r--r--module/database/FileDatabase.py1193
-rw-r--r--module/database/StatisticDatabase.py13
-rw-r--r--module/database/StorageDatabase.py9
-rw-r--r--module/database/UserDatabase.py167
-rw-r--r--module/database/__init__.py10
-rw-r--r--module/datatypes/PyFile.py (renamed from module/PyFile.py)276
-rw-r--r--module/datatypes/PyPackage.py109
-rw-r--r--module/datatypes/User.py61
-rw-r--r--module/datatypes/__init__.py (renamed from module/plugins/captcha/__init__.py)0
-rw-r--r--module/debug.py95
-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/interaction/EventManager.py136
-rw-r--r--module/interaction/InteractionManager.py159
-rw-r--r--module/interaction/InteractionTask.py81
-rw-r--r--module/interaction/__init__.py2
-rw-r--r--module/lib/forwarder.py (renamed from module/forwarder.py)0
-rw-r--r--module/lib/hg_tool.py133
-rw-r--r--module/lib/new_collections.py375
-rw-r--r--module/network/Browser.py9
-rw-r--r--module/network/Bucket.py12
-rw-r--r--module/network/CookieJar.py20
-rw-r--r--module/network/HTTPChunk.py25
-rw-r--r--module/network/HTTPDownload.py31
-rw-r--r--module/network/HTTPRequest.py38
-rw-r--r--module/network/RequestFactory.py31
-rw-r--r--module/network/XDCCRequest.py2
-rw-r--r--module/plugins/Account.py416
-rw-r--r--module/plugins/AccountManager.py185
-rw-r--r--module/plugins/Addon.py203
-rw-r--r--module/plugins/Base.py338
-rw-r--r--module/plugins/Container.py75
-rw-r--r--module/plugins/Crypter.py321
-rw-r--r--module/plugins/Hook.py161
-rw-r--r--module/plugins/Hoster.py396
-rw-r--r--module/plugins/MultiHoster.py73
-rw-r--r--module/plugins/Plugin.py617
-rw-r--r--module/plugins/PluginManager.py380
-rw-r--r--module/plugins/UserAddon.py25
-rw-r--r--module/plugins/accounts/FilesonicCom.py13
-rwxr-xr-xmodule/plugins/accounts/OronCom.py12
-rw-r--r--module/plugins/accounts/Premium4Me.py24
-rw-r--r--module/plugins/accounts/RealdebridCom.py60
-rw-r--r--module/plugins/accounts/RyushareCom.py3
-rw-r--r--module/plugins/accounts/ShareonlineBiz.py66
-rw-r--r--module/plugins/addons/AlldebridCom.py (renamed from module/plugins/hooks/AlldebridCom.py)0
-rw-r--r--module/plugins/addons/BypassCaptcha.py (renamed from module/plugins/hooks/BypassCaptcha.py)0
-rw-r--r--module/plugins/addons/CaptchaBrotherhood.py (renamed from module/plugins/hooks/CaptchaBrotherhood.py)0
-rw-r--r--module/plugins/addons/CaptchaTrader.py (renamed from module/plugins/hooks/CaptchaTrader.py)4
-rw-r--r--module/plugins/addons/Checksum.py (renamed from module/plugins/hooks/Checksum.py)0
-rw-r--r--module/plugins/addons/ClickAndLoad.py (renamed from module/plugins/hooks/ClickAndLoad.py)6
-rw-r--r--module/plugins/addons/DeathByCaptcha.py (renamed from module/plugins/hooks/DeathByCaptcha.py)0
-rw-r--r--module/plugins/addons/DownloadScheduler.py (renamed from module/plugins/hooks/DownloadScheduler.py)0
-rw-r--r--module/plugins/addons/EasybytezCom.py (renamed from module/plugins/hooks/EasybytezCom.py)0
-rw-r--r--module/plugins/addons/Ev0InFetcher.py (renamed from module/plugins/hooks/Ev0InFetcher.py)10
-rw-r--r--module/plugins/addons/ExpertDecoders.py (renamed from module/plugins/hooks/ExpertDecoders.py)0
-rw-r--r--module/plugins/addons/ExternalScripts.py (renamed from module/plugins/hooks/ExternalScripts.py)13
-rw-r--r--module/plugins/addons/ExtractArchive.py (renamed from module/plugins/hooks/ExtractArchive.py)9
-rw-r--r--module/plugins/addons/HotFolder.py (renamed from module/plugins/hooks/HotFolder.py)4
-rw-r--r--module/plugins/addons/IRCInterface.py (renamed from module/plugins/hooks/IRCInterface.py)6
-rw-r--r--module/plugins/addons/ImageTyperz.py (renamed from module/plugins/hooks/ImageTyperz.py)0
-rw-r--r--module/plugins/addons/LinkdecrypterCom.py (renamed from module/plugins/hooks/LinkdecrypterCom.py)0
-rw-r--r--module/plugins/addons/MergeFiles.py (renamed from module/plugins/hooks/MergeFiles.py)4
-rw-r--r--module/plugins/addons/MultiHome.py (renamed from module/plugins/hooks/MultiHome.py)4
-rw-r--r--module/plugins/addons/MultiHoster.py102
-rw-r--r--module/plugins/addons/MultishareCz.py (renamed from module/plugins/hooks/MultishareCz.py)0
-rw-r--r--module/plugins/addons/Premium4Me.py (renamed from module/plugins/hooks/Premium4Me.py)0
-rw-r--r--module/plugins/addons/PremiumizeMe.py (renamed from module/plugins/hooks/PremiumizeMe.py)0
-rw-r--r--module/plugins/addons/RealdebridCom.py (renamed from module/plugins/hooks/RealdebridCom.py)0
-rw-r--r--module/plugins/addons/RehostTo.py (renamed from module/plugins/hooks/RehostTo.py)0
-rw-r--r--module/plugins/addons/UpdateManager.py (renamed from module/plugins/hooks/UpdateManager.py)15
-rw-r--r--module/plugins/addons/XFileSharingPro.py (renamed from module/plugins/hooks/XFileSharingPro.py)0
-rw-r--r--module/plugins/addons/XMPPInterface.py (renamed from module/plugins/hooks/XMPPInterface.py)33
-rw-r--r--module/plugins/addons/ZeveraCom.py (renamed from module/plugins/hooks/ZeveraCom.py)0
-rw-r--r--module/plugins/addons/__init__.py (renamed from module/plugins/hooks/__init__.py)0
-rw-r--r--module/plugins/captcha/GigasizeCom.py19
-rw-r--r--module/plugins/captcha/LinksaveIn.py147
-rw-r--r--module/plugins/captcha/MegauploadCom.py14
-rw-r--r--module/plugins/container/CCF.py4
-rw-r--r--module/plugins/container/LinkList.py68
-rw-r--r--module/plugins/container/RSDF.py4
-rw-r--r--module/plugins/crypter/FilesonicComFolder.py12
-rw-r--r--module/plugins/crypter/HotfileFolderCom.py18
-rw-r--r--module/plugins/crypter/LinkList.py55
-rwxr-xr-xmodule/plugins/crypter/OronComFolder.py28
-rw-r--r--module/plugins/crypter/XfilesharingProFolder.py34
-rw-r--r--module/plugins/hoster/BezvadataCz.py2
-rw-r--r--module/plugins/hoster/DdlstorageCom.py4
-rw-r--r--module/plugins/hoster/DlFreeFr.py1
-rw-r--r--module/plugins/hoster/EuroshareEu.py2
-rw-r--r--module/plugins/hoster/FilesMailRu.py3
-rw-r--r--module/plugins/hoster/FilesonicCom.py2
-rw-r--r--module/plugins/hoster/HotfileCom.py2
-rw-r--r--module/plugins/hoster/MegauploadCom.py6
-rw-r--r--module/plugins/hoster/MultishareCz.py14
-rw-r--r--module/plugins/hoster/NetloadIn.py8
-rw-r--r--module/plugins/hoster/Premium4Me.py2
-rw-r--r--module/plugins/hoster/PutlockerCom.py4
-rw-r--r--module/plugins/hoster/RapidshareCom.py4
-rw-r--r--module/plugins/hoster/RealdebridCom.py176
-rw-r--r--module/plugins/hoster/TurbobitNet.py122
-rw-r--r--module/plugins/hoster/UploadedTo.py3
-rw-r--r--module/plugins/hoster/XFileSharingPro.py33
-rw-r--r--module/plugins/hoster/YoutubeCom.py4
-rw-r--r--module/plugins/hoster/ZeveraCom.py214
-rw-r--r--module/plugins/internal/AbstractExtractor.py12
-rw-r--r--module/plugins/internal/MultiHoster.py97
-rw-r--r--module/plugins/internal/NetloadInOCR.py (renamed from module/plugins/captcha/NetloadIn.py)11
-rw-r--r--module/plugins/internal/OCR.py (renamed from module/plugins/captcha/captcha.py)3
-rw-r--r--module/plugins/internal/ShareonlineBizOCR.py (renamed from module/plugins/captcha/ShareonlineBiz.py)8
-rw-r--r--module/plugins/internal/SimpleHoster.py2
-rw-r--r--module/plugins/internal/UnRar.py15
-rw-r--r--module/remote/socketbackend/create_ttypes.py2
-rw-r--r--module/remote/socketbackend/ttypes.py452
-rw-r--r--module/remote/thriftbackend/Processor.py1
-rw-r--r--module/remote/thriftbackend/Socket.py14
-rw-r--r--module/remote/thriftbackend/pyload.thrift567
-rwxr-xr-xmodule/remote/thriftbackend/thriftgen/pyload/Pyload-remote491
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/Pyload.py4177
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/constants.py2
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/ttypes.py1078
-rw-r--r--module/setup.py47
-rw-r--r--module/threads/AddonThread.py65
-rw-r--r--module/threads/BaseThread.py136
-rw-r--r--module/threads/DecrypterThread.py81
-rw-r--r--module/threads/DownloadThread.py223
-rw-r--r--module/threads/InfoThread.py168
-rw-r--r--module/threads/ThreadManager.py (renamed from module/ThreadManager.py)130
-rw-r--r--module/threads/__init__.py0
-rw-r--r--module/unescape.py3
-rw-r--r--module/utils/__init__.py236
-rw-r--r--module/utils/fs.py72
-rw-r--r--module/web/ServerThread.py23
-rw-r--r--module/web/api_app.py47
-rw-r--r--module/web/cnl_app.py6
-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/default/css/window.css73
-rw-r--r--module/web/media/default/img/add_folder.pngbin571 -> 0 bytes
-rw-r--r--module/web/media/default/img/ajax-loader.gifbin404 -> 0 bytes
-rw-r--r--module/web/media/default/img/arrow_right.pngbin349 -> 0 bytes
-rw-r--r--module/web/media/default/img/big_button.gifbin1905 -> 0 bytes
-rw-r--r--module/web/media/default/img/big_button_over.gifbin728 -> 0 bytes
-rw-r--r--module/web/media/default/img/body.pngbin402 -> 0 bytes
-rw-r--r--module/web/media/default/img/button.pngbin452 -> 0 bytes
-rw-r--r--module/web/media/default/img/closebtn.gifbin254 -> 0 bytes
-rw-r--r--module/web/media/default/img/cog.pngbin512 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_add.pngbin446 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_add_blue.pngbin845 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_cancel.pngbin3349 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_cancel_blue.pngbin787 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_pause.pngbin598 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_pause_blue.pngbin721 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_play.pngbin592 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_play_blue.pngbin717 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_stop.pngbin403 -> 0 bytes
-rw-r--r--module/web/media/default/img/control_stop_blue.pngbin695 -> 0 bytes
-rw-r--r--module/web/media/default/img/drag_corner.gifbin76 -> 0 bytes
-rw-r--r--module/web/media/default/img/error.pngbin701 -> 0 bytes
-rw-r--r--module/web/media/default/img/full.pngbin3543 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-login.pngbin1288 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-collector.pngbin1953 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-config.pngbin1802 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-development.pngbin876 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-download.pngbin721 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-home.pngbin920 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-index.pngbin482 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-news.pngbin628 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-queue.pngbin2629 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-recent.pngbin932 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-menu-wiki.pngbin1204 -> 0 bytes
-rw-r--r--module/web/media/default/img/head-search-noshadow.pngbin1187 -> 0 bytes
-rw-r--r--module/web/media/default/img/head_bg1.pngbin125 -> 0 bytes
-rw-r--r--module/web/media/default/img/images.pngbin661 -> 0 bytes
-rw-r--r--module/web/media/default/img/notice.pngbin778 -> 0 bytes
-rw-r--r--module/web/media/default/img/package_go.pngbin898 -> 0 bytes
-rw-r--r--module/web/media/default/img/page-tools-backlinks.pngbin540 -> 0 bytes
-rw-r--r--module/web/media/default/img/page-tools-edit.pngbin574 -> 0 bytes
-rw-r--r--module/web/media/default/img/page-tools-revisions.pngbin603 -> 0 bytes
-rw-r--r--module/web/media/default/img/parseUri.pngbin666 -> 0 bytes
-rw-r--r--module/web/media/default/img/pyload-logo-edited3.5-new-font-small.pngbin8457 -> 0 bytes
-rw-r--r--module/web/media/default/img/reconnect.pngbin755 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_None.pngbin7613 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_downloading.pngbin943 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_failed.pngbin701 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_finished.pngbin781 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_offline.pngbin700 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_proc.pngbin512 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_queue.pngbin7613 -> 0 bytes
-rw-r--r--module/web/media/default/img/status_waiting.pngbin889 -> 0 bytes
-rw-r--r--module/web/media/default/img/success.pngbin781 -> 0 bytes
-rw-r--r--module/web/media/default/img/tab-background.pngbin179 -> 0 bytes
-rw-r--r--module/web/media/default/img/tabs-border-bottom.pngbin163 -> 0 bytes
-rw-r--r--module/web/media/default/img/user-actions-logout.pngbin799 -> 0 bytes
-rw-r--r--module/web/media/default/img/user-actions-profile.pngbin628 -> 0 bytes
-rw-r--r--module/web/media/default/img/user-info.pngbin3963 -> 0 bytes
-rw-r--r--module/web/media/img/dialog-close.pngbin689 -> 0 bytes
-rw-r--r--module/web/media/img/dialog-question.pngbin2073 -> 0 bytes
-rw-r--r--module/web/media/js/MooDialog_static.js401
-rw-r--r--module/web/media/js/MooDropMenu_static.js89
-rw-r--r--module/web/media/js/admin.coffee58
-rw-r--r--module/web/media/js/admin.js3
-rw-r--r--module/web/media/js/base.coffee173
-rw-r--r--module/web/media/js/base.js3
-rw-r--r--module/web/media/js/mootools-core-1.4.1.js476
-rw-r--r--module/web/media/js/mootools-more-1.4.0.1.js216
-rw-r--r--module/web/media/js/package_ui.js377
-rw-r--r--module/web/media/js/purr_static.js308
-rw-r--r--module/web/media/js/settings.coffee107
-rw-r--r--module/web/media/js/settings.js3
-rw-r--r--module/web/media/js/tinytab_static.js50
-rw-r--r--module/web/middlewares.py14
-rw-r--r--module/web/pyload_app.py491
-rw-r--r--module/web/static/css/bootstrap.css5774
-rw-r--r--module/web/static/css/default/style.less318
-rw-r--r--module/web/static/css/font.css37
-rw-r--r--module/web/static/css/mobile/images/ajax-loader.gifbin0 -> 7825 bytes
-rw-r--r--module/web/static/css/mobile/images/ajax-loader.pngbin0 -> 340 bytes
-rw-r--r--module/web/static/css/mobile/images/icons-18-black.pngbin0 -> 1767 bytes
-rw-r--r--module/web/static/css/mobile/images/icons-18-white.pngbin0 -> 1806 bytes
-rw-r--r--module/web/static/css/mobile/images/icons-36-black.pngbin0 -> 3611 bytes
-rw-r--r--module/web/static/css/mobile/images/icons-36-white.pngbin0 -> 3648 bytes
-rw-r--r--module/web/static/css/mobile/jquery.mobile-1.1.1.min.css2
-rw-r--r--module/web/static/css/mobile/my.css93
-rw-r--r--module/web/static/css/mobile/style.css214
-rw-r--r--module/web/static/fonts/Sansation_Bold-webfont.eotbin0 -> 35336 bytes
-rw-r--r--module/web/static/fonts/Sansation_Bold-webfont.ttfbin0 -> 35160 bytes
-rw-r--r--module/web/static/fonts/Sansation_Bold-webfont.woffbin0 -> 18496 bytes
-rw-r--r--module/web/static/fonts/Sansation_Light-webfont.eotbin0 -> 36700 bytes
-rw-r--r--module/web/static/fonts/Sansation_Light-webfont.ttfbin0 -> 36520 bytes
-rw-r--r--module/web/static/fonts/Sansation_Light-webfont.woffbin0 -> 18408 bytes
-rw-r--r--module/web/static/fonts/Sansation_Regular-webfont.eotbin0 -> 36368 bytes
-rw-r--r--module/web/static/fonts/Sansation_Regular-webfont.ttfbin0 -> 36180 bytes
-rw-r--r--module/web/static/fonts/Sansation_Regular-webfont.woffbin0 -> 18316 bytes
-rw-r--r--module/web/static/img/default/arrow_refresh.png (renamed from module/web/media/default/img/arrow_refresh.png)bin685 -> 685 bytes
-rw-r--r--module/web/static/img/default/bgpattern.pngbin0 -> 2487 bytes
-rw-r--r--module/web/static/img/default/delete.png (renamed from module/web/media/default/img/delete.png)bin715 -> 715 bytes
-rw-r--r--module/web/static/img/default/double-line.gifbin0 -> 50 bytes
-rw-r--r--module/web/static/img/default/fancy_deboss.pngbin0 -> 265 bytes
-rw-r--r--module/web/static/img/default/folder.png (renamed from module/web/media/default/img/folder.png)bin537 -> 537 bytes
-rw-r--r--module/web/static/img/default/icon_blank_file_black.pngbin0 -> 291 bytes
-rw-r--r--module/web/static/img/default/icon_clock_small_white.pngbin0 -> 1540 bytes
-rw-r--r--module/web/static/img/default/icon_folder.pngbin0 -> 222 bytes
-rw-r--r--module/web/static/img/default/icon_speed_small_white.pngbin0 -> 1654 bytes
-rw-r--r--module/web/static/img/default/icon_user_small_white.pngbin0 -> 1475 bytes
-rw-r--r--module/web/static/img/default/logo.pngbin0 -> 8340 bytes
-rw-r--r--module/web/static/img/default/logo_grey.pngbin0 -> 6236 bytes
-rw-r--r--module/web/static/img/default/main-wrapper-bg.pngbin0 -> 114902 bytes
-rw-r--r--module/web/static/img/default/pencil.png (renamed from module/web/media/default/img/pencil.png)bin450 -> 450 bytes
-rw-r--r--module/web/static/img/favicon.ico (renamed from module/web/media/img/favicon.ico)bin7206 -> 7206 bytes
-rw-r--r--module/web/static/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--module/web/static/img/glyphicons-halflings.pngbin0 -> 12799 bytes
-rw-r--r--module/web/static/js/app.build.js22
-rw-r--r--module/web/static/js/collections/FileList.js17
-rw-r--r--module/web/static/js/collections/PackageList.js15
-rw-r--r--module/web/static/js/default.js57
-rw-r--r--module/web/static/js/libs/backbone-0.9.2.js1431
-rw-r--r--module/web/static/js/libs/bootstrap-2.1.1.js2027
-rw-r--r--module/web/static/js/libs/jquery-1.8.0.js9227
-rw-r--r--module/web/static/js/libs/jquery.fastClick-0.2.js96
-rw-r--r--module/web/static/js/libs/jquery.flot.min.js6
-rw-r--r--module/web/static/js/libs/jquery.mobile-1.1.1.min.js181
-rw-r--r--module/web/static/js/libs/jquery.omniwindow.js141
-rw-r--r--module/web/static/js/libs/jquery.transit-0.1.3.js658
-rw-r--r--module/web/static/js/libs/jqueryui/accordion.js614
-rw-r--r--module/web/static/js/libs/jqueryui/autocomplete.js634
-rw-r--r--module/web/static/js/libs/jqueryui/button.js417
-rw-r--r--module/web/static/js/libs/jqueryui/core.js337
-rw-r--r--module/web/static/js/libs/jqueryui/datepicker.js1857
-rw-r--r--module/web/static/js/libs/jqueryui/dialog.js869
-rw-r--r--module/web/static/js/libs/jqueryui/draggable.js836
-rw-r--r--module/web/static/js/libs/jqueryui/droppable.js299
-rw-r--r--module/web/static/js/libs/jqueryui/effects/blind.js52
-rw-r--r--module/web/static/js/libs/jqueryui/effects/bounce.js81
-rw-r--r--module/web/static/js/libs/jqueryui/effects/clip.js57
-rw-r--r--module/web/static/js/libs/jqueryui/effects/core.js615
-rw-r--r--module/web/static/js/libs/jqueryui/effects/drop.js53
-rw-r--r--module/web/static/js/libs/jqueryui/effects/explode.js82
-rw-r--r--module/web/static/js/libs/jqueryui/effects/fade.js35
-rw-r--r--module/web/static/js/libs/jqueryui/effects/fold.js59
-rw-r--r--module/web/static/js/libs/jqueryui/effects/highlight.js53
-rw-r--r--module/web/static/js/libs/jqueryui/effects/pulsate.js54
-rw-r--r--module/web/static/js/libs/jqueryui/effects/scale.js181
-rw-r--r--module/web/static/js/libs/jqueryui/effects/shake.js60
-rw-r--r--module/web/static/js/libs/jqueryui/effects/slide.js53
-rw-r--r--module/web/static/js/libs/jqueryui/effects/transfer.js48
-rw-r--r--module/web/static/js/libs/jqueryui/mouse.js170
-rw-r--r--module/web/static/js/libs/jqueryui/position.js311
-rw-r--r--module/web/static/js/libs/jqueryui/progressbar.js112
-rw-r--r--module/web/static/js/libs/jqueryui/resizable.js810
-rw-r--r--module/web/static/js/libs/jqueryui/selectable.js270
-rw-r--r--module/web/static/js/libs/jqueryui/slider.js665
-rw-r--r--module/web/static/js/libs/jqueryui/sortable.js1087
-rw-r--r--module/web/static/js/libs/jqueryui/tabs.js760
-rw-r--r--module/web/static/js/libs/jqueryui/widget.js275
-rw-r--r--module/web/static/js/libs/less-1.3.0.min.js9
-rw-r--r--module/web/static/js/libs/lodash-0.5.2.js4263
-rw-r--r--module/web/static/js/libs/require-2.0.6.js2041
-rw-r--r--module/web/static/js/mobile.js42
-rw-r--r--module/web/static/js/models/File.js33
-rw-r--r--module/web/static/js/models/Package.js76
-rw-r--r--module/web/static/js/models/TreeCollection.js38
-rw-r--r--module/web/static/js/plugins/text-2.0.3.js308
-rw-r--r--module/web/static/js/routers/defaultRouter.js29
-rw-r--r--module/web/static/js/routers/mobileRouter.js55
-rw-r--r--module/web/static/js/utils/animations.js36
-rw-r--r--module/web/static/js/utils/lazyRequire.js89
-rw-r--r--module/web/static/js/views/fileView.js20
-rw-r--r--module/web/static/js/views/headerView.js75
-rw-r--r--module/web/static/js/views/mobile/my.js275
-rw-r--r--module/web/static/js/views/modal/modalView.js84
-rw-r--r--module/web/static/js/views/packageTreeView.js75
-rw-r--r--module/web/static/js/views/packageView.js60
-rw-r--r--module/web/templates/500.html3
-rw-r--r--module/web/templates/default/admin.html98
-rw-r--r--module/web/templates/default/backbone/modal.html11
-rw-r--r--module/web/templates/default/base.html283
-rw-r--r--module/web/templates/default/captcha.html42
-rw-r--r--module/web/templates/default/dashboard.html65
-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.html61
-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.html214
-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/templates/mobile/base.html41
-rw-r--r--module/web/templates/mobile/login.html5
-rw-r--r--module/web/utils.py123
-rw-r--r--module/web/webinterface.py10
-rw-r--r--pavement.py40
-rw-r--r--paver-minilib.zipbin0 -> 22 bytes
-rwxr-xr-xpyLoadCli.py16
-rwxr-xr-xpyLoadCore.py241
-rwxr-xr-xpyLoadGui.py765
-rw-r--r--setup.py7
-rw-r--r--systemCheck.py35
-rw-r--r--testlinks.txt26
-rw-r--r--tests/CrypterPluginTester.py81
-rw-r--r--tests/HosterPluginTester.py152
-rwxr-xr-xtests/clonedigger.sh2
-rw-r--r--tests/config/db.version1
-rw-r--r--tests/config/plugin.conf138
-rw-r--r--tests/config/pyload.conf.org75
-rw-r--r--tests/config/pyload.db.orgbin0 -> 13312 bytes
-rw-r--r--tests/crypterlinks.txt5
-rw-r--r--tests/helper/BenchmarkTest.py66
-rw-r--r--tests/helper/PluginTester.py151
-rw-r--r--tests/helper/Stubs.py130
-rw-r--r--tests/helper/__init__.py0
-rwxr-xr-xtests/hosterlinks.txt50
-rwxr-xr-xtests/nosetests.sh5
-rwxr-xr-xtests/plugin_tests.sh7
-rwxr-xr-xtests/pyflakes.sh5
-rwxr-xr-xtests/quit_pyload.sh7
-rwxr-xr-xtests/run_pyload.sh17
-rwxr-xr-xtests/sloccount.sh2
-rw-r--r--tests/test_api.py24
-rw-r--r--tests/test_backends.py (renamed from tests/test_json.py)25
-rw-r--r--tests/test_configparser.py48
-rw-r--r--tests/test_database.py194
-rw-r--r--tests/test_filemanager.py214
-rw-r--r--tests/test_interactionManager.py58
-rw-r--r--tests/test_syntax.py43
461 files changed, 55546 insertions, 19524 deletions
diff --git a/CLA b/CLA
new file mode 100644
index 000000000..7de970bc5
--- /dev/null
+++ b/CLA
@@ -0,0 +1,145 @@
+Thank you for your interest in contributing to pyLoad ("We" or "Us").
+
+This contributor agreement ("Agreement") documents the rights granted by
+contributors to Us. To make this document effective, please sign it and send it
+to Us by email, following the instructions at http://pyload.org/contributing.
+This is a legally binding document, so please read it carefully before agreeing
+to it. The Agreement may cover more than one software project managed by Us.
+1. Definitions
+
+"You" means the individual who Submits a Contribution to Us.
+
+"Contribution" means any work of authorship that is Submitted by You to Us in
+which You own or assert ownership of the Copyright. If You do not own the
+Copyright in the entire work of authorship, please follow the instructions in
+http://pyload.org/contributing.
+
+"Copyright" means all rights protecting works of authorship owned or controlled
+by You, including copyright, moral and neighboring rights, as appropriate, for
+the full term of their existence including any extensions by You.
+
+"Material" means the work of authorship which is made available by Us to third
+parties. When this Agreement covers more than one software project, the Material
+means the work of authorship to which the Contribution was Submitted. After You
+Submit the Contribution, it may be included in the Material.
+
+"Submit" means any form of electronic, verbal, or written communication sent to
+Us or our representatives, including but not limited to electronic mailing
+lists, source code control systems, and issue tracking systems that are managed
+by, or on behalf of, Us for the purpose of discussing and improving the
+Material, but excluding communication that is conspicuously marked or otherwise
+designated in writing by You as "Not a Contribution."
+
+"Submission Date" means the date on which You Submit a Contribution to Us.
+
+"Effective Date" means the date You execute this Agreement or the date You first
+Submit a Contribution to Us, whichever is earlier.
+2. Grant of Rights
+2.1 Copyright License
+
+(a) You retain ownership of the Copyright in Your Contribution and have the same
+rights to use or license the Contribution which You would have had without
+entering into the Agreement.
+
+(b) To the maximum extent permitted by the relevant law, You grant to Us a
+perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable
+license under the Copyright covering the Contribution, with the right to
+sublicense such rights through multiple tiers of sublicensees, to reproduce,
+modify, display, perform and distribute the Contribution as part of the
+Material; provided that this license is conditioned upon compliance with Section
+2.3.
+2.2 Patent License
+
+For patent claims including, without limitation, method, process, and apparatus
+claims which You own, control or have the right to grant, now or in the future,
+You grant to Us a perpetual, worldwide, non-exclusive, transferable,
+royalty-free, irrevocable patent license, with the right to sublicense these
+rights to multiple tiers of sublicensees, to make, have made, use, sell, offer
+for sale, import and otherwise transfer the Contribution and the Contribution in
+combination with the Material (and portions of such combination). This license
+is granted only to the extent that the exercise of the licensed rights infringes
+such patent claims; and provided that this license is conditioned upon
+compliance with Section 2.3.
+2.3 Outbound License
+
+Based on the grant of rights in Sections 2.1 and 2.2, if We include Your
+Contribution in a Material, We may license the Contribution under any license,
+including copyleft, permissive, commercial, or proprietary licenses. As a
+condition on the exercise of this right, We agree to also license the
+Contribution under the terms of the license or licenses which We are using for
+the Material on the Submission Date.
+
+2.4 Moral Rights. If moral rights apply to the Contribution, to the maximum
+extent permitted by law, You waive and agree not to assert such moral rights
+against Us or our successors in interest, or any of our licensees, either direct
+or indirect.
+
+2.5 Our Rights. You acknowledge that We are not obligated to use Your
+Contribution as part of the Material and may decide to include any Contribution
+We consider appropriate.
+
+2.6 Reservation of Rights. Any rights not expressly licensed under this section
+are expressly reserved by You.
+3. Agreement
+
+You confirm that:
+
+(a) You have the legal authority to enter into this Agreement.
+
+(b) You own the Copyright and patent claims covering the Contribution which are
+required to grant the rights under Section 2.
+
+(c) The grant of rights under Section 2 does not violate any grant of rights
+which You have made to third parties, including Your employer. If You are an
+employee, You have had Your employer approve this Agreement or sign the Entity
+version of this document. If You are less than eighteen years old, please have
+Your parents or guardian sign the Agreement.
+
+(d) You have followed the instructions in http://pyload.org/contributing, if You
+do not own the Copyright in the entire work of authorship Submitted.
+4. Disclaimer
+
+EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, THE CONTRIBUTION IS PROVIDED "AS
+IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT
+LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. TO THE
+EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED
+IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW.
+5. Consequential Damage Waiver
+
+TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU BE
+LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA,
+INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT
+OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR
+OTHERWISE) UPON WHICH THE CLAIM IS BASED.
+6. Miscellaneous
+
+6.1 This Agreement will be governed by and construed in accordance with the laws
+of excluding its conflicts of law provisions. Under certain circumstances, the
+governing law in this section might be superseded by the United Nations
+Convention on Contracts for the International Sale of Goods ("UN Convention")
+and the parties intend to avoid the application of the UN Convention to this
+Agreement and, thus, exclude the application of the UN Convention in its
+entirety to this Agreement.
+
+6.2 This Agreement sets out the entire agreement between You and Us for Your
+Contributions to Us and overrides all other agreements or understandings.
+
+6.3 If You or We assign the rights or obligations received through this
+Agreement to a third party, as a condition of the assignment, that third party
+must agree in writing to abide by all the rights and obligations in the
+Agreement.
+
+6.4 The failure of either party to require performance by the other party of any
+provision of this Agreement in one situation shall not affect the right of a
+party to require such performance at any time in the future. A waiver of
+performance under a provision in one situation shall not be considered a waiver
+of the performance of the provision in the future or a waiver of the provision
+in its entirety.
+
+6.5 If any provision of this Agreement is found void and unenforceable, such
+provision will be replaced to the extent possible with a provision that comes
+closest to the meaning of the original provision and which is enforceable. The
+terms and conditions set forth in this Agreement shall apply notwithstanding any
+failure of essential purpose of this Agreement or any limited remedy to the
+maximum extent possible under law. \ No newline at end of file
diff --git a/LICENSE b/LICENSE
index bc08fe2e4..e3a8bf86a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,41 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
+pyLoad - download manager
+Copyright(c) 2008-2012 pyLoad Team
+All rights reserved.
+licensing@pyload.org
+
+Open Source License
+-------------------
+
+You are allowed to use this software under the terms of the GNU Affero
+General Public License as published by the Free Software Foundation;
+either version 3 of the License, or (at your option) any later version.
+A copy of the GNU Affero General Public License can be found below.
+
+Alternative License
+-------------------
+
+With an explicit permission of the authors you may use or distribute
+this software under a different license according to the agreement.
+Please contact licensing@pyload.org for further information.
+
+Contribution
+------------
+
+If you want to contribute to pyLoad you have to grant permission to the
+pyLoad team to (re)license your code as desired.
+You can do so by adding the following to the file header:
+ # With permission to relicense for the pyLoad Team according to LICENSE
+
+For regular contributions please sign the CLA and send it to
+contributors@pyload.org
+
+Beside that you need to initial release your code with a license compatible
+to the Open Source License as choosen above.
+
+--
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +43,15 @@
Preamble
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
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
+our General Public Licenses are 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.
+software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +60,34 @@ 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.
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -72,7 +96,7 @@ modification follow.
0. Definitions.
- "This License" refers to version 3 of the GNU General Public License.
+ "This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -549,35 +573,45 @@ 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.
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
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
+under version 3 of the GNU 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.
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
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
+the GNU Affero 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
+Program specifies that a certain numbered version of the GNU Affero 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
+GNU Affero 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
+versions of the GNU Affero 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.
diff --git a/README b/README
index 7f3c4f4c8..b44124f90 100644
--- a/README
+++ b/README
@@ -2,11 +2,16 @@
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 a free and open source personal cloud storage as well as download manager
+for all kind of operating systems and devices, designed to be extremely lightweight and
+runnable on personal pc or headless server.
-pyLoad is written entirely in Python and is currently under heavy development.
+You can easily manage your files, downloads, media content and access it from anywhere.
+All common video-sites, one-click-hoster, container formats and well known web standards are supported to download files for you.
+Additionaly it has a great variety of plugins to automate common tasks and make unattended running possible.
+
+pyLoad has a full featured and well documented API, is easily extendable and accessible
+by external tools. It is written entirely in Python and currently under heavy development.
For news, downloads, wiki, forum and further information visit http://pyload.org/
@@ -20,7 +25,7 @@ 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
+The prebuilt pyload packages also install these dependencies or have them included, so manual install
is only needed when installing pyLoad from source.
Required
@@ -29,7 +34,7 @@ Required
- pycurl a.k.a python-curl
- jinja2
- beaker
-- thrift
+- thrift >= 0.8
- simplejson (for python 2.5)
Some plugins require additional packages, only install these when needed.
diff --git a/docs/_static/logo.png b/docs/_static/logo.png
new file mode 100644
index 000000000..1a11f5cc0
--- /dev/null
+++ b/docs/_static/logo.png
Binary files differ
diff --git a/docs/access_api.rst b/docs/access_api.rst
deleted file mode 100644
index df69da8b2..000000000
--- a/docs/access_api.rst
+++ /dev/null
@@ -1,121 +0,0 @@
-.. _access_api:
-
-*********************
-How to access the API
-*********************
-
-pyLoad has a very powerfull API with can be accessed in several ways.
-
-Overview
---------
-
-First of all, you need to know what you can do with our API. It lets you do all common task like
-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>`.
-
-Of course its possible to access the ``core.api`` attribute in plugins and hooks, 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.
-More information about thrift can be found here http://wiki.apache.org/thrift/.
-
-
-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
-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
-supported here http://wiki.apache.org/thrift/LibraryFeatures?action=show&redirect=LanguageSupport.
-
-Now install thrift, for instructions see http://wiki.apache.org/thrift/ThriftInstallation.
-If every thing went fine you are ready to generate the method stubs, the command basically looks like this. ::
-
- $ thrift --gen (language) pyload.thrift
-
-You find now a directory named :file:`gen-(language)`. For instruction how to use the generated files consider the docs
-at the thrift wiki and the examples here http://wiki.apache.org/thrift/ThriftUsage.
-
-
-=======
-Example
-=======
-In case you want to use python, pyload has already all files included to access the api over rpc.
-
-A basic script that prints out some information: ::
-
- from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
-
- try:
- client = ThriftClient(host="127.0.0.1", port=7227, user="User", password="yourpw")
- except:
- print "Login was wrong"
- exit()
-
- print "Server version:", client.getServerVersion()
- print client.statusDownloads()
- q = client.getQueue()
- for p in q:
- data = client.getPackageData(p.pid)
- print "Package Name: ", data.name
-
-That's all for now, pretty easy isn't it?
-If you still have open questions come around in irc or post them at our pyload forum.
-
-
-Using HTTP/JSON
----------------
-
-Another maybe easier way, which does not require much setup is to access the JSON Api via HTTP.
-For this reason the webinterface must be enabled.
-
-=====
-Login
-=====
-
-First you need to authenticate, if you using this within the webinterface and the user is logged the API is also accessible,
-since they share the same cookie/session.
-
-However, if you are building a external client and want to authenticate manually
-you have to send your credentials ``username`` and ``password`` as
-POST parameter to ``http://pyload-core/api/login``.
-
-The result will be your session id. If you are using cookies, it will be set and you can use the API now.
-In case you dont't have cookies enabled you can pass the session id as ``session`` POST parameter
-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
-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`.
-
-==================
-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>`
-documentation.
-
-It is important that *all* arguments are in JSON format. So ``http://pyload-core/api/getFileData/1`` is valid because
-1 represents an integer in json format. On the other hand if the method is expecting strings, this would be correct:
-``http://pyload-core/api/getUserData/"username"/"password"``.
-
-Strings are wrapped in double qoutes, because `"username"` represents a string in json format. It's not limited to strings and intergers,
-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
diff --git a/docs/api/datatypes.rst b/docs/api/datatypes.rst
new file mode 100644
index 000000000..886d95a76
--- /dev/null
+++ b/docs/api/datatypes.rst
@@ -0,0 +1,477 @@
+.. _api_datatypes:
+
+***********************
+API Datatype Definition
+***********************
+
+Below you find a copy of :file:`module/remote/thriftbackend/pyload.thrift`, which is used to generate the data structs
+for various languages. It is also a good overview of avaible methods and return data.
+
+.. code-block:: c
+
+ .. [[[cog cog.out(open('module/remote/thriftbackend/pyload.thrift', 'rb').read()) ]]]
+ namespace java org.pyload.thrift
+
+ typedef i32 FileID
+ typedef i32 PackageID
+ typedef i32 ResultID
+ typedef i32 InteractionID
+ typedef i64 UTCDate
+ typedef i64 ByteCount
+ typedef list<string> LinkList
+ // a string that can represent multiple types int, bool, time, etc..
+ typedef string ValueString
+ typedef string PluginName
+
+ // NA - Not Available
+ enum DownloadStatus {
+ NA,
+ Offline,
+ Online,
+ Queued,
+ Paused,
+ Finished,
+ Skipped,
+ Failed,
+ Starting,
+ Waiting,
+ Downloading,
+ TempOffline,
+ Aborted,
+ Decrypting,
+ Processing,
+ Custom,
+ Unknown
+ }
+
+ enum MediaType {
+ All = 0
+ Other = 1,
+ Audio = 2,
+ Image = 4,
+ Video = 8,
+ Document = 16,
+ Archive = 32,
+ }
+
+ enum FileStatus {
+ Ok,
+ Missing,
+ Remote, // file is available at remote location
+ }
+
+ enum PackageStatus {
+ Ok,
+ Paused,
+ Remote,
+ }
+
+ // 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 {
+ NA,
+ 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 {
+ All = 0,
+ Notification = 1,
+ Captcha = 2,
+ Query = 4,
+ }
+
+ struct ProgressInfo {
+ 1: FileID fid,
+ 2: string name,
+ 3: ByteCount speed,
+ 4: i32 eta,
+ 5: string format_eta,
+ 6: ByteCount bleft,
+ 7: ByteCount size,
+ 8: string format_size,
+ 9: i16 percent,
+ 10: DownloadStatus status,
+ 11: string statusmsg,
+ 12: string format_wait,
+ 13: UTCDate 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: ByteCount speed,
+ 6: bool download,
+ 7: bool reconnect
+ }
+
+ // download info for specific file
+ struct DownloadInfo {
+ 1: string url,
+ 2: PluginName plugin,
+ 3: string hash,
+ 4: DownloadStatus status,
+ 5: string statusmsg,
+ 6: string error,
+ }
+
+ struct FileInfo {
+ 1: FileID fid,
+ 2: string name,
+ 3: PackageID package,
+ 4: ByteCount size,
+ 5: FileStatus status,
+ 6: MediaType media,
+ 7: UTCDate added,
+ 8: i16 fileorder,
+ 9: optional DownloadInfo download,
+ }
+
+ struct PackageStats {
+ 1: i16 linkstotal,
+ 2: i16 linksdone,
+ 3: ByteCount sizetotal,
+ 4: ByteCount sizedone,
+ }
+
+ struct PackageInfo {
+ 1: PackageID pid,
+ 2: string name,
+ 3: string folder,
+ 4: PackageID root,
+ 5: string site,
+ 6: string comment,
+ 7: string password,
+ 8: UTCDate added,
+ 9: PackageStatus status,
+ 10: i16 packageorder,
+ 11: PackageStats stats,
+ 12: list<FileID> fids,
+ 13: list<PackageID> pids,
+ }
+
+ // thrift does not allow recursive datatypes, so all data is accumulated and mapped with id
+ struct PackageView {
+ 1: PackageInfo root,
+ 2: map<FileID, FileInfo> files,
+ 3: map<PackageID, PackageInfo> packages
+ }
+
+ // general info about link, used for collector and online results
+ struct LinkStatus {
+ 1: string url,
+ 2: string name,
+ 3: PluginName plugin,
+ 4: ByteCount size, // size <= 0 : unknown
+ 5: DownloadStatus status,
+ 6: string packagename,
+ }
+
+ struct InteractionTask {
+ 1: InteractionID iid,
+ 2: Input input,
+ 3: list<string> data,
+ 4: Output output,
+ 5: optional ValueString default_value,
+ 6: string title,
+ 7: string description,
+ 8: PluginName plugin,
+ }
+
+ struct AddonInfo {
+ 1: string func_name,
+ 2: string description,
+ 3: ValueString value,
+ }
+
+ struct ConfigItem {
+ 1: string name,
+ 2: string display_name,
+ 3: string description,
+ 4: string type,
+ 5: ValueString default_value,
+ 6: ValueString value,
+ }
+
+ struct ConfigSection {
+ 1: string name,
+ 2: string display_name,
+ 3: string description,
+ 4: string long_description,
+ 5: optional list<ConfigItem> items,
+ 6: optional list<AddonInfo> info,
+ 7: optional list<InteractionTask> handler, // if null plugin is not loaded
+ }
+
+ struct EventInfo {
+ 1: string eventname,
+ 2: list<string> event_args,
+ }
+
+ struct UserData {
+ 1: string name,
+ 2: string email,
+ 3: i32 role,
+ 4: i32 permission,
+ 5: string templateName
+ }
+
+ struct AccountInfo {
+ 1: PluginName plugin,
+ 2: string loginname,
+ 3: bool valid,
+ 4: UTCDate validuntil,
+ 5: ByteCount trafficleft,
+ 6: ByteCount maxtraffic,
+ 7: bool premium,
+ 8: bool activated,
+ 9: map<string, string> options,
+ }
+
+ struct AddonService {
+ 1: string func_name,
+ 2: string description,
+ 3: optional i16 media,
+ 4: optional bool package,
+ }
+
+ struct OnlineCheck {
+ 1: ResultID rid, // -1 -> nothing more to get
+ 2: map<string, LinkStatus> data, //url to result
+ }
+
+
+ // exceptions
+
+ exception PackageDoesNotExists {
+ 1: PackageID pid
+ }
+
+ exception FileDoesNotExists {
+ 1: FileID fid
+ }
+
+ exception UserDoesNotExists {
+ 1: string user
+ }
+
+ exception ServiceDoesNotExists {
+ 1: string plugin
+ 2: string func
+ }
+
+ exception ServiceException {
+ 1: string msg
+ }
+
+ service Pyload {
+
+ ///////////////////////
+ // Server Status
+ ///////////////////////
+
+ string getServerVersion(),
+ ServerStatus statusServer(),
+ void pauseServer(),
+ void unpauseServer(),
+ bool togglePause(),
+ ByteCount freeSpace(),
+ void kill(),
+ void restart(),
+ list<string> getLog(1: i32 offset),
+ bool isTimeDownload(),
+ bool isTimeReconnect(),
+ bool toggleReconnect(),
+ void scanDownloadFolder(),
+
+ // downloads - information
+ list<ProgressInfo> getProgressInfo(),
+
+ ///////////////////////
+ // Configuration
+ ///////////////////////
+
+ string getConfigValue(1: string section, 2: string option),
+ void setConfigValue(1: string section, 2: string option, 3: string value),
+ map<string, ConfigSection> getConfig(),
+ map<PluginName, ConfigSection> getPluginConfig(),
+ ConfigSection configureSection(1: string section),
+ void setConfigHandler(1: PluginName plugin, 2: InteractionID iid, 3: ValueString value),
+
+ ///////////////////////
+ // Download Preparing
+ ///////////////////////
+
+ map<PluginName, LinkList> checkURLs(1: LinkList urls),
+ map<PluginName, LinkList> parseURLs(1: string html, 2: string url),
+ // packagename - urls
+
+ // parses results and generates packages
+ OnlineCheck checkOnlineStatus(1: LinkList urls),
+ OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
+
+ // poll results from previously started online check
+ OnlineCheck pollResults(1: ResultID rid),
+
+ map<string, LinkList> generatePackages(1: LinkList links),
+
+ ///////////////////////
+ // Adding/Deleting
+ ///////////////////////
+
+ list<PackageID> generateAndAddPackages(1: LinkList links, 2: bool paused),
+ list<FileID> autoAddLinks(1: LinkList links),
+
+ PackageID createPackage(1: string name, 2: string folder, 3: PackageID root, 4: string password,
+ 5: string site, 6: string comment, 7: bool paused),
+
+ PackageID addPackage(1: string name, 2: LinkList links, 3: string password),
+ // same as above with paused attribute
+ PackageID addPackageP(1: string name, 2: LinkList links, 3: string password, 4: bool paused),
+
+ // pid -1 is toplevel
+ PackageID addPackageChild(1: string name, 2: LinkList links, 3: string password, 4: PackageID root, 5: bool paused),
+
+ PackageID uploadContainer(1: string filename, 2: binary data),
+
+ void addLinks(1: PackageID pid, 2: LinkList links) throws (1: PackageDoesNotExists e),
+
+ // these are real file operations and WILL delete files on disk
+ void deleteFiles(1: list<FileID> fids),
+ void deletePackages(1: list<PackageID> pids),
+
+ ///////////////////////
+ // Collector
+ ///////////////////////
+
+ list<LinkStatus> getCollector(),
+
+ void addToCollector(1: LinkList links),
+ PackageID addFromCollector(1: string name, 2: bool paused),
+ void renameCollPack(1: string name, 2: string new_name),
+ void deleteCollPack(1: string name),
+ void deleteCollLink(1: string url),
+
+ ////////////////////////////
+ // File Information retrival
+ ////////////////////////////
+
+ PackageView getAllFiles(),
+ PackageView getAllUnfinishedFiles(),
+
+ // pid -1 for root, full=False only delivers first level in tree
+ PackageView getFileTree(1: PackageID pid, 2: bool full),
+ PackageView getUnfinishedFileTree(1: PackageID pid, 2: bool full),
+
+ // same as above with full=False
+ PackageView getPackageContent(1: PackageID pid),
+
+ PackageInfo getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ FileInfo getFileInfo(1: FileID fid) throws (1: FileDoesNotExists e),
+ map<FileID, FileInfo> findFiles(1: string pattern),
+
+ ///////////////////////
+ // Modify Downloads
+ ///////////////////////
+
+ void restartPackage(1: PackageID pid),
+ void restartFile(1: FileID fid),
+ void recheckPackage(1: PackageID pid),
+ void stopDownloads(1: list<FileID> fids),
+ void stopAllDownloads(),
+ void restartFailed(),
+
+ /////////////////////////
+ // Modify Files/Packages
+ /////////////////////////
+
+ void setFilePaused(1: FileID fid, 2: bool paused) throws (1: FileDoesNotExists e),
+
+ // moving package while downloading is not possible, so they will return bool to indicate success
+ void setPackagePaused(1: PackageID pid, 2: bool paused) throws (1: PackageDoesNotExists e),
+ bool setPackageFolder(1: PackageID pid, 2: string path) throws (1: PackageDoesNotExists e),
+ void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
+
+ // as above, this will move files on disk
+ bool movePackage(1: PackageID pid, 2: PackageID root) throws (1: PackageDoesNotExists e),
+ bool moveFiles(1: list<FileID> fids, 2: PackageID pid) throws (1: PackageDoesNotExists e),
+
+ void orderPackage(1: list<PackageID> pids, 2: i16 position),
+ void orderFiles(1: list<FileID> fids, 2: PackageID pid, 3: i16 position),
+
+ ///////////////////////
+ // User Interaction
+ ///////////////////////
+
+ // mode = Output types binary ORed
+ bool isInteractionWaiting(1: i16 mode),
+ InteractionTask getInteractionTask(1: i16 mode),
+ void setInteractionResult(1: InteractionID iid, 2: ValueString result),
+
+ // generate a download link, everybody can download the file until timeout reached
+ string generateDownloadLink(1: FileID fid, 2: i16 timeout),
+
+ list<InteractionTask> getNotifications(),
+
+ map<PluginName, list<AddonService>> getAddonHandler(),
+ void callAddonHandler(1: PluginName plugin, 2: string func, 3: PackageID pid_or_fid),
+
+ ///////////////////////
+ // Event Handling
+ ///////////////////////
+
+ list<EventInfo> getEvents(1: string uuid),
+
+ ///////////////////////
+ // Account Methods
+ ///////////////////////
+
+ 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+User Information
+ /////////////////////////
+
+ bool login(1: string username, 2: string password),
+ UserData getUserData(1: string username, 2: string password) throws (1: UserDoesNotExists ex),
+ map<string, UserData> getAllUserData(),
+
+ ///////////////////////
+ // Addon Methods
+ ///////////////////////
+
+ map<PluginName, list<AddonService>> getServices(),
+ bool hasService(1: PluginName plugin, 2: string func),
+
+ // empty string or json encoded list as args
+ string call(1: PluginName plugin, 2: string func, 3: string arguments) throws (1: ServiceDoesNotExists ex, 2: ServiceException e),
+
+ map<PluginName, list<AddonInfo>> getAllInfo(),
+ list<AddonInfo> getInfoByPlugin(1: PluginName plugin),
+
+ //scheduler
+
+ // TODO
+
+ }
+ .. [[[end]]]
+
diff --git a/docs/api/json_api.rst b/docs/api/json_api.rst
new file mode 100644
index 000000000..bcf546dc0
--- /dev/null
+++ b/docs/api/json_api.rst
@@ -0,0 +1,106 @@
+.. _json_api:
+
+========
+JSON API
+========
+
+JSON [1]_ is a lightweight object notation and wrappers exist for nearly every programming language. Every
+modern browser is able to load JSON objects with JavaScript. Unlike to thrift you don't need to generate or precompile
+any stub methods, the JSON :class:`Api <module.Api.Api>` is ready to be used in most languages. The library is really lightweight (at least in python)
+and you can build very lightweight scripts with it. Because of the builtin support, JSON is the first choice for all browser
+applications.
+
+In our case JSON is just the output format, you have exactly the same methods available as with the thrift backend. The only
+difference is the underlying protocol.
+
+Are there still reasons to choose the original :doc:`thrift <thrift_api>` backend in favor to JSON? Yes, since it
+uses a binary protocol the performance will be better (when generating the objects), traffic will be smaller and
+therefore the transfer faster.
+In most IDEs you will get code completion, because of the pre-generated classes, which can make work much easier.
+
+If you intend to write a full client you should prefer thrift if the language is supported, for lightweight scripts and
+in browser environments JSON will be the better choice.
+
+Login
+-----
+
+First you need to authenticate, if you are using this within the web interface and the user is logged in, the API is also accessible,
+since they share the same cookie/session.
+
+However, if you are building an external client and want to authenticate manually
+you have to send your credentials ``username`` and ``password`` as
+POST parameter to ``http://pyload-core/api/login``.
+
+The result will be your session id. If you are using cookies, it will be set and you can use the API now.
+In case you don't have cookies enabled you can pass the session id as ``session`` POST parameter
+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
+the thrift backend.
+
+Access works simply via ``http://pyload-core/api/methodName``, where ``pyload-core`` is the ip address
+or hostname including the web interface port. By default on local access this would be `localhost:8000`.
+
+The return value will be formatted in JSON, complex data types as dictionaries. Definition for data types can be found
+:doc:`here <datatypes>`
+
+Passing parameters
+------------------
+
+To pass arguments you have two choices:
+Either use positional arguments, e.g.: ``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>` documentation.
+
+It is important that *all* arguments are in JSON format. So ``http://pyload-core/api/getFileData/1`` is valid because
+1 represents an integer in json format. On the other hand if the method is expecting strings, this would be correct:
+``http://pyload-core/api/getUserData/"username"/"password"``.
+
+Strings are wrapped in double qoutes, because `"username"` represents a string in JSON format. It's not limited to
+strings and integers, 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 has to be urlencoded at last. (Most libraries will do that automatically)
+
+Example
+-------
+
+Here is a little python script that is able to send commands to the pyload api::
+
+ #!/usr/bin/env python
+ # -*- coding: utf-8 -*-
+
+ from urllib import urlopen, urlencode
+ from json import dumps
+
+ URL = "http://localhost:8001/api/%s"
+
+ def login(user, pw):
+ params = {"username": user, "password": pw}
+ ret = urlopen(URL % "login", urlencode(params))
+ return ret.read().strip("\"")
+
+ # send arbitrary command to pyload api, parameter as keyword argument
+ def send(session, command, **kwargs):
+ # convert arguments to json format
+ params = dict([(k, dumps(v)) for k,v in kwargs.iteritems()])
+ params["session"] = session
+ ret = urlopen(URL % command, urlencode(params))
+ return ret.read()
+
+ if __name__ == "__main__":
+ session = login("User", "pwhere")
+ print "Session id:", session
+
+ result = send(session, "setCaptchaResult", tid=0, result="some string")
+ print result
+
+
+
+.. rubric:: Footnotes
+
+.. [1] http://de.wikipedia.org/wiki/JavaScript_Object_Notation
diff --git a/docs/api/overview.rst b/docs/api/overview.rst
new file mode 100644
index 000000000..605cf4c97
--- /dev/null
+++ b/docs/api/overview.rst
@@ -0,0 +1,36 @@
+.. _overview:
+
+=======================================
+API - Application Programming Interface
+=======================================
+
+From Wikipedia, the free encyclopedia [1]_:
+
+ An application programming interface (API) is a source code based specification intended to be used as an interface
+ by software components to communicate with each other. An API may include specifications for routines,
+ data structures, object classes, and variables.
+
+.. rubric:: Motivation
+
+The idea of the centralized pyLoad :class:`Api <module.Api.Api>` is to give uniform access to all integral parts
+and plugins in pyLoad to other clients written in arbitrary programming languages.
+Most of the :class:`Api <module.Api.Api>` functionality is exposed via RPC [2]_ and accessible via thrift [3]_ or
+simple JSON objects [4]_. In conclusion the :class:`Api <module.Api.Api>` is accessible via many programming language,
+over network from remote machines and over browser with javascript.
+
+
+.. rubric:: Contents
+
+.. toctree::
+
+ thrift_api.rst
+ json_api.rst
+ datatypes.rst
+
+
+.. rubric:: Footnotes
+
+.. [1] http://en.wikipedia.org/wiki/Application_programming_interface
+.. [2] http://en.wikipedia.org/wiki/Remote_procedure_call
+.. [3] `<http://en.wikipedia.org/wiki/Thrift_(protocol)>`_
+.. [4] http://en.wikipedia.org/wiki/Json \ No newline at end of file
diff --git a/docs/api/thrift_api.rst b/docs/api/thrift_api.rst
new file mode 100644
index 000000000..cd1d2f23c
--- /dev/null
+++ b/docs/api/thrift_api.rst
@@ -0,0 +1,74 @@
+.. _thrift_api:
+
+==========
+Thrift API
+==========
+
+Thrift [1]_ was first developed in-house at facebook, but later published to public domain and developed at Apache Incubator.
+It includes a binary protocol for remote calls, which has a much better performance than other data formats like XML, additionally
+it is available for numerous languages and therefore we choose it as primary backend for our API.
+
+First of all, you need to know what you can do with our API. It lets you do all common task like
+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>`.
+
+Of course its possible to access the ``core.api`` attribute in plugins and hooks, but much more
+interesting is the possibility 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.
+More information about thrift can be found in their wiki [2]_.
+
+
+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 at :file:`module/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.
+You can also look at it :doc:`here <datatypes>`
+
+Assuming you want to use the API in any other language than python than check if it is supported [3]_.
+
+Now install thrift, for instructions see [4]_.
+If every thing went fine you are ready to generate the method stubs, the command basically looks like this. ::
+
+ $ thrift --gen (language) pyload.thrift
+
+You find now a directory named :file:`gen-(language)`. For instruction how to use the generated files consider the docs
+at the thrift wiki, as well at the examples [5]_.
+
+
+Example
+-------
+
+In case you want to use python, pyload has already all files included to access the api over rpc.
+
+A basic script that prints out some information: ::
+
+ from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
+
+ try:
+ client = ThriftClient(host="127.0.0.1", port=7227, user="User", password="yourpw")
+ except:
+ print "Login was wrong"
+ exit()
+
+ print "Server version:", client.getServerVersion()
+ print client.statusDownloads()
+ q = client.getQueue()
+ for p in q:
+ data = client.getPackageData(p.pid)
+ print "Package Name: ", data.name
+
+That's all for now, pretty easy isn't it?
+If you still have open questions come around in irc or post them at our pyload forum.
+
+.. rubric:: Footnotes
+
+.. [1] http://en.wikipedia.org/wiki/Thrift_(protocol)
+.. [2] http://wiki.apache.org/thrift/
+.. [3] http://wiki.apache.org/thrift/LibraryFeatures?action=show&redirect=LanguageSupport
+.. [4] http://wiki.apache.org/thrift/ThriftInstallation
+.. [5] http://wiki.apache.org/thrift/ThriftUsage \ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 9d2cf98f9..4961fc910 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -27,11 +27,12 @@ sys.path.append(join(dir_name, "module", "lib"))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+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']
+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']
@@ -52,7 +53,7 @@ master_doc = 'index'
# General information about the project.
project = u'pyLoad'
-copyright = u'2011, pyLoad Team'
+copyright = u'2012, 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
@@ -65,8 +66,8 @@ v = options.version.split(".")
cog.outl("version = '%s'" % ".".join(v[:2]))
cog.outl("release = '%s'" % ".".join(v))
]]]"""
-version = '0.4'
-release = '0.4.9'
+version = '0.5'
+release = '0.5.0'
# [[[end]]]
@@ -196,8 +197,8 @@ htmlhelp_basename = 'pyLoaddoc'
# 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'),
+ ('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
@@ -235,4 +236,4 @@ man_pages = [
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
+intersphinx_mapping = {'http://docs.python.org/': None} \ No newline at end of file
diff --git a/docs/extend_pyload.rst b/docs/extend_pyload.rst
deleted file mode 100755
index 337cb6854..000000000
--- a/docs/extend_pyload.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-.. _extend_pyload:
-
-********************
-How to extend pyLoad
-********************
-
-In general there a two different plugin types. These allow everybody to write powerful, modular plugins without knowing
-every detail of the pyLoad core. However you should have some basic knowledge of python.
-
-.. toctree::
-
- write_hooks.rst
- write_plugins.rst \ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 757fd7537..b18f068f2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,27 +1,49 @@
-.. pyLoad documentation master file, created by
- sphinx-quickstart on Sat Jun 4 11:54:34 2011.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
+.. pyLoad documentation master file
-Welcome to pyLoad's documentation!
-==================================
+=====================
+pyLoad Documentation
+=====================
-Great that you found your way to the pyLoad documentation!
+.. image:: _static/logo.png
+ :height: 144
+ :width: 412
-We have collected some information here to help developer writing plugins and understandig our code.
-If you want to help us developing visit us in our IRC channel #pyload on freenode.net or leave a message in our forum.
-Contents:
+Great that you found your way to the pyLoad [1]_ documentation!
+
+This is the ultimate document to get started extending or accessing pyLoad in your own way.
+We will cover on how to access the API so you can write your own client to pyLoad. In the next step you will be given
+an idea on how to extend pyLoad and write your own powerful plugins, which perfectly integrate into our system.
+
+The complete pyLoad source and this documentation is available at bitbucket [2]_. If you would like to contribute
+come around in our irc channel [3]_ or open a pull request.
+In case you still have questions, ask at our forum [4]_ or in our official irc channel #pyload @ irc.freenode.net
+
+We wish you happy programming!
+
+-- the pyLoad Team
+
+Contents
+--------
.. toctree::
:maxdepth: 2
- access_api.rst
- extend_pyload.rst
+ api/overview.rst
+ plugins/overview.rst
+ system/overview.rst
+
module_overview.rst
.. currentmodule:: module
+.. rubric:: Footnotes
+
+.. [1] http://pyload.org
+.. [2] http://pyload.org/irc
+.. [3] http://bitbucket.org/spoob/pyload/overview
+.. [4] http://forum.pyload.org
+
==================
* :ref:`genindex`
diff --git a/docs/module_overview.rst b/docs/module_overview.rst
index d51202c88..b2b98313b 100644
--- a/docs/module_overview.rst
+++ b/docs/module_overview.rst
@@ -1,17 +1,24 @@
+
Module Overview
===============
-You can find an overview of some important classes here:
+A little selection of most important modules in pyLoad.
.. autosummary::
:toctree: module
module.Api.Api
- module.plugins.Plugin.Base
- module.plugins.Plugin.Plugin
+ module.plugins.Base.Base
+ module.plugins.Hoster.Hoster
+ module.plugins.internal.SimpleHoster.SimpleHoster
module.plugins.Crypter.Crypter
+ module.plugins.internal.SimpleCrypter.SimpleCrypter
+ module.plugins.Addon.Addon
module.plugins.Account.Account
- module.plugins.Hook.Hook
- module.HookManager.HookManager
+ module.plugins.MultiHoster.MultiHoster
+ module.AddonManager.AddonManager
+ module.interaction.EventManager.EventManager
+ module.interaction.InteractionManager.InteractionManager
+ module.interaction.InteractionTask.InteractionTask
module.PyFile.PyFile
module.PyPackage.PyPackage
diff --git a/docs/plugins/account_plugin.rst b/docs/plugins/account_plugin.rst
new file mode 100644
index 000000000..e683f1604
--- /dev/null
+++ b/docs/plugins/account_plugin.rst
@@ -0,0 +1,11 @@
+.. _account_plugin:
+
+Account - Premium Access
+========================
+
+Example
+-------
+
+MultiHoster
+-----------
+
diff --git a/docs/write_hooks.rst b/docs/plugins/addon_plugin.rst
index dd60367b7..c2258f2aa 100644
--- a/docs/write_hooks.rst
+++ b/docs/plugins/addon_plugin.rst
@@ -1,20 +1,20 @@
-.. _write_hooks:
+.. _write_addons:
-Hooks
-=====
+Addon - Add new functionality
+=============================
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.
+do something completely 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 implemented 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!
+Your hook needs to subclass :class:`Hook <module.plugins.Hook.Hook>` and will inherit all of its methods, so make sure to check it's documentation!
-All Hooks should start with something like this: ::
+All hooks should start with something like this: ::
from module.plugins.Hook import Hook
@@ -28,7 +28,7 @@ All Hooks should start with something like this: ::
__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.
+hook on and off. Don't overwrite the ``init`` method if not necessary, use ``setup`` instead.
Using the Config
----------------
@@ -41,16 +41,16 @@ When everything went right you can access the config values with ``self.getConfi
Interacting on Events
---------------------
-The next step is to think about where your Hook action takes places.
+The next step is to think about where your Hook action takes place.
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.
+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 the relevant documentation to know how to access it's great power and manipulate them.
-A basic excerpt would look like: ::
+What a basic excerpt would look like: ::
from module.plugins.Hook import Hook
@@ -66,13 +66,13 @@ A basic excerpt would look like: ::
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.
+in a thread, in order to not block the main thread. This should be used for all kinds of long lived processing tasks.
-Another and more flexible and powerful way is to use event listener.
+Another and more flexible and powerful way is to use the 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.
+at :class:`HookManager <module.HookManager.HookManager>`. Keep in mind that you can define your own events and other people may listen on them.
-For your convenience it's possible to register listeners automatical via the ``event_map`` attribute.
+For your convenience it's possible to register listeners automatically 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
@@ -99,14 +99,15 @@ Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name",
: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
+We introduced events because it scales better if there is a huge amount of events and hooks. So all future interactions will be exclusively
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
+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.
+You may have 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: ::
diff --git a/docs/plugins/base_plugin.rst b/docs/plugins/base_plugin.rst
new file mode 100644
index 000000000..f6813cf40
--- /dev/null
+++ b/docs/plugins/base_plugin.rst
@@ -0,0 +1,117 @@
+.. _base_plugin:
+
+Base Plugin - And here it begins...
+===================================
+
+A Plugin in pyLoad is a python file located at one of the subfolders in :file:`module/plugins/`.
+All different plugin types inherit from :class:`Base <module.plugins.Base.Base>`, which defines basic methods
+and meta data. You should read this section carefully, because it's the base for all plugin development. It
+is also a good idea to look at the class diagram [1]_ for all plugin types to get an overview.
+At last you should look at several already existing plugin to get a more detailed idea of how
+they have to look like and what is possible with them.
+
+Meta Data
+---------
+
+All important data which must be known by pyLoad is set using class attributes pre- and suffixed with ``__``.
+An overview of acceptable values can be found in :class:`Base <module.plugins.Base.Base>` source code.
+Unneeded attributes can be left out, except ``__version__``. Nevertheless please fill out most information
+as you can, when you want to submit your plugin to the public repository.
+
+Additionally :class:`Crypter <module.plugins.Crypter.Crypter>` and :class:`Crypter <module.plugins.Hoster.Hoster>`
+needs to have a specific regexp [2]_ ``__pattern__``. This will be matched against input url's and if a suited
+plugin is found it is selected to handle the url.
+
+For localization pyLoad supports gettext [3]_, to mark strings for translation surround them with ``_("...")``.
+
+You don't need to subclass :class:`Base <module.plugins.Base.Base>` directly, but the
+intermediate type according to your plugin. As an example we choose a hoster plugin, but the same is true for all
+plugin types.
+
+How a basic hoster plugin header could look like::
+
+ from module.plugin.Hoster import Hoster
+
+ class MyFileHoster(Hoster):
+ __version__ = "0.1"
+ __description__ = _("Short description of the plugin")
+ __long_description = _("""An even longer description
+ is not needed for hoster plugins,
+ but the hook plugin should have it, so the users know what they are doing.""")
+
+In future examples the meta data will be left out, but remember it's required in every plugin!
+
+Config Entries
+--------------
+
+Every plugin is allowed to add entries to the configuration. These are defined via ``__config__`` and consist
+of a list with tuples in the format of ``(name, type, verbose_name, default_value)`` or
+``(name, type, verbose_name, short_description, default_value)``.
+
+Example from Youtube plugin::
+
+ class YoutubeCom:
+ __config__ = [("quality", "sd;hd;fullhd", _("Quality Setting"), "hd"),
+ ("fmt", "int", _("FMT Number 0-45"), _("Desired FMT number, look them up at wikipedia"), 0),
+ (".mp4", "bool", _("Allow .mp4"), True)]
+
+
+At runtime the desired config values can be retrieved with ``self.getConfig(name)`` and set with
+``self.setConfig(name, value)``.
+
+Tagging Guidelines
+------------------
+
+To categorize a plugin, a list of keywords can be assigned via ``__tags__`` attribute. You may add arbitrary
+tags as you like, but please look at this table first to choose your tags. With standardised keywords we can generate
+a better overview of the plugins and provide some search criteria.
+
+=============== =================================================================
+Keyword Meaning
+=============== =================================================================
+image Anything related to image(hoster)
+video Anything related to video(hoster)
+captcha A plugin that needs captcha decrypting
+interaction A plugin that makes use of interaction with the user
+free A hoster without any premium service
+premium_only A hoster only usable with account
+ip_check A hoster that checks ip, that can be avoided with reconnect
+=============== =================================================================
+
+Basic Methods
+-------------
+
+All methods can be looked up at :class:`Base <module.plugins.Base.Base>`. To note some important ones:
+
+The pyload core instance is accessible at ``self.core`` attribute
+and the :class:`Api <module.Api.Api>` at ``self.core.api``
+
+With ``self.load(...)`` you can load any url and get the result. This method is only available to Hoster and Crypter.
+For other plugins use ``getURL(...)`` or ``getRequest()``.
+
+Use ``self.store(...)`` and ``self.retrieve(...)`` to store data persistently into the database.
+
+Make use of ``logInfo, logError, logWarning, logDebug`` for logging purposes.
+
+Debugging
+---------
+
+One of the most important aspects in software programming is debugging. It is especially important
+for plugins which heavily rely on external input, which is true for all hoster and crypter plugins.
+To enable debugging functionality start pyLoad with the ``-d`` option or enable it in the config.
+
+You should use ``self.logDebug(msg)`` when ever it is reasonable. It is a good pratice to log server output
+or the calculation of results and then check in the log if it really is what you are expecting.
+
+For further debugging you can install ipython [4]_, and set breakpoints with ``self.core.breakpoint()``.
+It will open the python debugger [5]_ and pause the plugin thread.
+To open a ipython shell in the running programm use ``self.shell()``.
+These methods are useful to gain access to the code flow at runtime and check or modify variables.
+
+
+.. rubric:: Footnotes
+.. [1] :ref:`plugin_hierarchy`
+.. [2] http://docs.python.org/library/re.html
+.. [3] http://docs.python.org/library/gettext.html
+.. [4] http://ipython.org/
+.. [5] http://docs.python.org/library/pdb.html \ No newline at end of file
diff --git a/docs/plugins/crypter_plugin.rst b/docs/plugins/crypter_plugin.rst
new file mode 100644
index 000000000..8c54dccb1
--- /dev/null
+++ b/docs/plugins/crypter_plugin.rst
@@ -0,0 +1,69 @@
+.. _crypter_plugin:
+
+Crypter - Extract links from pages
+==================================
+
+We are starting with the simplest plugin, the :class:`Crypter <module.plugins.Crypter.Crypter>`.
+It's job is to take an url as input and generate a new package or links, for example by filtering the urls or
+loading a page and extracting links from the html code. You need to define the ``__pattern__`` to match
+target urls and subclass from :class:`Crypter <module.plugins.Crypter.Crypter>`. ::
+
+ from module.plugin.Crypter import Crypter
+
+ class MyFileCrypter(Crypter):
+ __pattern__ = r"mycrypter.com/id/([0-9]+)"
+
+ def decryptURL(self, url):
+
+ urls = ["http://get.pyload.org/src", "http://get.pyload.org/debian", "http://get.pyload.org/win"]
+ return urls
+
+You have to overwrite at least one of ``.decryptFile``, ``.decryptURL``, ``.decryptURLs``. The first one
+is only useful for container files, whereas the last is useful when it's possible to handle a bunch of urls
+at once. If in doubt, just overwrite `decryptURL`.
+
+Generating Packages
+-------------------
+
+When finished with decrypting just return the urls as list and they will be added to the package. You can also
+create new Packages if needed by instantiating a :class:`Package` instance, which will look like the following::
+
+ from module.plugin.Crypter import Crypter, Package
+
+ class MyFileCrypter(Crypter):
+
+ def decryptURL(self, url):
+
+ html = self.load(url)
+
+ # .decrypt_from_content is only an example method here and will return a list of urls
+ urls = self.decrypt_from_content(html)
+ return Package("my new package", urls)
+
+And that's basically all you need to know. Just as a little side-note if you want to use decrypter in
+your code you can use::
+
+ plugin = self.core.pluginManager.loadClass("crypter", "NameOfThePlugin")
+ # Core instance is needed for decrypting
+ # decrypted will be a list of urls
+ decrypted = plugin.decrypt(core, urls)
+
+
+SimpleCrypter
+-------------
+
+For simple crypter services there is the :class:`SimpleCrypter <module.plugins.internal.SimpleCrypter.SimpleCrypter>` class which handles most of the workflow. Only the regexp
+pattern has to be defined.
+
+Exmaple::
+
+ from module.plugins.internal.SimpleCrypter import SimpleCrypter
+
+ class MyFileCrypter(SimpleCrypter):
+
+Testing
+-------
+
+Please append a test link at :file:`tests/crypterlinks.txt` followed by `||xy`, where xy is the number of
+expected links/packages to extract.
+Our testrunner will be able to check your plugin periodical for functionality. \ No newline at end of file
diff --git a/docs/plugins/hoster_plugin.rst b/docs/plugins/hoster_plugin.rst
new file mode 100644
index 000000000..9cd99a1f5
--- /dev/null
+++ b/docs/plugins/hoster_plugin.rst
@@ -0,0 +1,57 @@
+.. _hoster_plugin:
+
+Hoster - Load files to disk
+===========================
+
+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
+
+ class MyFileHoster(Hoster):
+ """
+ plugin code
+ """
+
+ def setup():
+ #TODO
+
+ def process(self, pyfile):
+ 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)
+ parsed_url = self.myFunctionToParseUrl(html)
+
+ # 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 a parameter to every pyfile.
+Some tasks your plugin should handle: check if the file is online, get filename, wait if needed, download the file, etc..
+
+Common Tasks
+----------
+
+Some hosters require you to wait a specific time. Just set the time with ``self.setWait(seconds)`` or
+``self.setWait(seconds, True)`` if you want pyLoad to perform a reconnect if needed.
+
+To handle captcha input just use ``self.decryptCaptcha(url, ...)``, it will be send to clients
+or handled by :class:`Addon <module.plugins.Addon.Addon>` plugins
+
+
+Online status fetching
+----------------------
+
+SimpleHoster
+------------
+
+
+Testing
+-------
+
+
+Examples
+--------
+
+The best examples are the already existing plugins in :file:`module/plugins/`. \ No newline at end of file
diff --git a/docs/plugins/overview.rst b/docs/plugins/overview.rst
new file mode 100755
index 000000000..bbea86756
--- /dev/null
+++ b/docs/plugins/overview.rst
@@ -0,0 +1,33 @@
+.. _overview:
+
+================
+Extending pyLoad
+================
+
+.. pull-quote::
+ Any sufficiently advanced technology is indistinguishable from magic.
+
+ -- Arthur C. Clarke
+
+
+.. rubric:: Motivation
+
+pyLoad offers a comfortable and powerful plugin system to make extensions possible. With it you only need to have some
+python knowledge and can just start right away writing your own plugins. This document gives you an overview about the
+conceptual part. You should not leave out the :doc:`Base <base_plugin>` part, since it contains basic functionality for all plugin types.
+A class diagram visualizing the relationship can be found below [1]_
+
+.. rubric:: Contents
+
+.. toctree::
+
+ base_plugin.rst
+ crypter_plugin.rst
+ hoster_plugin.rst
+ account_plugin.rst
+ addon_plugin.rst
+
+
+.. rubric:: Footnotes
+
+.. [1] :ref:`plugin_hierarchy` \ No newline at end of file
diff --git a/docs/system/hoster_diagrams.rst b/docs/system/hoster_diagrams.rst
new file mode 100644
index 000000000..313f75c57
--- /dev/null
+++ b/docs/system/hoster_diagrams.rst
@@ -0,0 +1,16 @@
+.. _hoster_diagrams:
+
+===============
+Hoster Workflow
+===============
+
+The basic workflow of a hoster plugin. This is only a generalization, in most cases it is more complex
+and order will differ.
+
+Activity Diagram
+----------------
+.. image:: pyload_ad_Hoster.png
+
+Sequence Diagram
+----------------
+.. image:: pyload_sd_Hoster.png \ No newline at end of file
diff --git a/docs/system/overview.rst b/docs/system/overview.rst
new file mode 100755
index 000000000..00e439f45
--- /dev/null
+++ b/docs/system/overview.rst
@@ -0,0 +1,25 @@
+.. _overview:
+
+=============
+System Design
+=============
+
+.. pull-quote::
+ Programs must be written for people to read, and only incidentally for machines to execute.
+
+ -- H. Abelson and G. Sussman
+
+
+.. rubric:: Motivation
+
+In this section you will find information and diagrams to better understand the concept of pyload.
+
+.. rubric:: Contents
+
+.. toctree::
+
+ plugin_hierarchy.rst
+ hoster_diagrams.rst
+
+
+.. rubric:: Footnotes \ No newline at end of file
diff --git a/docs/system/plugin_hierarchy.rst b/docs/system/plugin_hierarchy.rst
new file mode 100644
index 000000000..0e10664c0
--- /dev/null
+++ b/docs/system/plugin_hierarchy.rst
@@ -0,0 +1,13 @@
+.. _plugin_hierarchy:
+
+================
+Plugin Hierarchy
+================
+
+Class diagram that describes plugin relationships.
+
+.. image:: pyload_PluginHierarchy.png
+
+Class diagram showing the relationship of api/thrift datatypes.
+
+.. image:: pyload_DataLayout.png \ No newline at end of file
diff --git a/docs/system/pyload_DataLayout.png b/docs/system/pyload_DataLayout.png
new file mode 100644
index 000000000..98ab31a69
--- /dev/null
+++ b/docs/system/pyload_DataLayout.png
Binary files differ
diff --git a/docs/system/pyload_PluginHierarchy.png b/docs/system/pyload_PluginHierarchy.png
new file mode 100644
index 000000000..118d3a7a8
--- /dev/null
+++ b/docs/system/pyload_PluginHierarchy.png
Binary files differ
diff --git a/docs/system/pyload_ad_Hoster.png b/docs/system/pyload_ad_Hoster.png
new file mode 100644
index 000000000..0ee064edc
--- /dev/null
+++ b/docs/system/pyload_ad_Hoster.png
Binary files differ
diff --git a/docs/system/pyload_sd_Hoster.png b/docs/system/pyload_sd_Hoster.png
new file mode 100644
index 000000000..e629a1949
--- /dev/null
+++ b/docs/system/pyload_sd_Hoster.png
Binary files differ
diff --git a/docs/write_plugins.rst b/docs/write_plugins.rst
deleted file mode 100644
index b513a5978..000000000
--- a/docs/write_plugins.rst
+++ /dev/null
@@ -1,103 +0,0 @@
-.. _write_plugins:
-
-Plugins
-=======
-
-A Plugin is a python file located at one of the subfolders in :file:`module/plugins/`. Either :file:`hoster`, :file:`crypter`
-or :file:`container`, depending of it's type.
-
-There are three kinds of different plugins: **Hoster**, **Crypter**, **Container**.
-All kind of plugins inherit from the base :class:`Plugin <module.plugins.Plugin.Plugin>`. You should know its
-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
-pyLoad begins to download/decrypt the url.
-
-Plugin header
--------------
-
-How basic hoster plugin header could look like: ::
-
- from module.plugin.Hoster import Hoster
-
- class MyFileHoster(Hoster):
- __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__)``.
-
-Just like :ref:`write_hooks` 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.
-
-
-Hoster plugins
---------------
-
-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
-
- class MyFileHoster(Hoster):
- """
- plugin code
- """
-
- def process(self, pyfile):
- 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)
- parsed_url = self.myFunctionToParseUrl(html)
-
- # 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.
-Some tasks your plugin should handle: proof if file is online, get filename, wait if needed, download the file, etc..
-
-Wait times
-__________
-
-Some hoster require you to wait a specific time. Just set the time with ``self.setWait(seconds)`` or
-``self.setWait(seconds, True)`` if you want pyLoad to perform a reconnect if needed.
-
-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
-
-Crypter
--------
-
-What about Decrypter and Container plugins?
-Well, they work nearly the same, only that the function they have to provide is named ``decrypt``
-
-Example: ::
-
- from module.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
-
-They can access all the methods from :class:`Plugin <module.plugins.Plugin.Plugin>`, but the important thing is they
-have to append all packages they parsed to the `self.packages` list. Simply append tuples with `(name, urls, folder)`,
-where urls is the list of urls contained in the packages. Thats all of your work, pyLoad will know what to do with them.
-
-Examples
---------
-
-Best examples are already existing plugins in :file:`module/plugins/`. \ No newline at end of file
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/AccountManager.py b/module/AccountManager.py
new file mode 100644
index 000000000..45b4eef95
--- /dev/null
+++ b/module/AccountManager.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN, mkaay
+###############################################################################
+
+from threading import Lock
+from random import choice
+
+from module.common.json_layer import json
+from module.utils import lock
+
+class AccountManager:
+ """manages all accounts"""
+
+ def __init__(self, core):
+ """Constructor"""
+
+ self.core = core
+ self.lock = Lock()
+
+ self.loadAccounts()
+
+ def loadAccounts(self):
+ """loads all accounts available"""
+
+ self.accounts = {}
+
+ for plugin, loginname, activated, password, options in self.core.db.loadAccounts():
+ # put into options as used in other context
+ options = json.loads(options) if options else {}
+ options["activated"] = activated
+
+ self.createAccount(plugin, loginname, password, options)
+
+ return
+
+ def iterAccounts(self):
+ """ yields login, account for all accounts"""
+ for name, data in self.accounts.iteritems():
+ for login, account in data.iteritems():
+ yield login, account
+
+ def saveAccounts(self):
+ """save all account information"""
+
+ data = []
+ for name, plugin in self.accounts.iteritems():
+ data.extend([(name, acc.loginname, acc.activated, acc.password, json.dumps(acc.options)) for acc in
+ plugin.itervalues()])
+ self.core.db.saveAccounts(data)
+
+ def createAccount(self, plugin, loginname, password, options):
+ klass = self.core.pluginManager.loadClass("accounts", plugin)
+ if not klass:
+ self.core.log.warning(_("Unknown account plugin %s") % plugin)
+ return
+
+ if plugin not in self.accounts:
+ self.accounts[plugin] = {}
+
+ self.core.log.debug("Create account %s:%s" % (plugin, loginname))
+
+ self.accounts[plugin][loginname] = klass(self, loginname, password, options)
+
+
+ def getAccount(self, plugin, user):
+ return self.accounts[plugin].get(user, None)
+
+ @lock
+ def updateAccount(self, plugin, user, password=None, options={}):
+ """add or update account"""
+ if plugin in self.accounts and user in self.accounts[plugin]:
+ acc = self.accounts[plugin][user]
+ updated = acc.update(password, options)
+
+ self.saveAccounts()
+ if updated: acc.scheduleRefresh(force=True)
+ else:
+ self.createAccount(plugin, user, password, options)
+ self.saveAccounts()
+
+ self.sendChange(plugin, user)
+
+ @lock
+ def removeAccount(self, plugin, user):
+ """remove account"""
+ if plugin in self.accounts and user in self.accounts[plugin]:
+ del self.accounts[plugin][user]
+ self.core.db.removeAccount(plugin, user)
+ self.core.eventManager.dispatchEvent("accountDeleted", plugin, user)
+ else:
+ self.core.log.debug("Remove non existing account %s %s" % (plugin, user))
+
+
+ @lock
+ def getAccountForPlugin(self, plugin):
+ if plugin in self.accounts:
+ accs = [x for x in self.accounts[plugin].values() if x.isUsable()]
+ if accs: return choice(accs)
+
+ return None
+
+ @lock
+ def getAllAccounts(self, refresh=False):
+ """ Return account info, refresh afterwards if needed
+
+ :param refresh:
+ :return:
+ """
+ if refresh:
+ self.core.scheduler.addJob(0, self.core.accountManager.getAllAccounts)
+
+ # load unavailable account info
+ for p_dict in self.accounts.itervalues():
+ for acc in p_dict.itervalues():
+ acc.getAccountInfo()
+
+ return self.accounts
+
+ def refreshAllAccounts(self):
+ """ Force a refresh of every account """
+ for p in self.accounts.itervalues():
+ for acc in p.itervalues():
+ acc.getAccountInfo(True)
+
+ def sendChange(self, plugin, name):
+ self.core.eventManager.dispatchEvent("accountUpdated", plugin, name) \ No newline at end of file
diff --git a/module/AddonManager.py b/module/AddonManager.py
new file mode 100644
index 000000000..4283b4652
--- /dev/null
+++ b/module/AddonManager.py
@@ -0,0 +1,257 @@
+# -*- 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
+"""
+import __builtin__
+
+from traceback import print_exc
+from thread import start_new_thread
+from threading import RLock
+
+from types import MethodType
+
+from module.threads.AddonThread import AddonThread
+from module.PluginManager import literal_eval
+from utils import lock, to_string
+
+class AddonManager:
+ """ Manages addons, loading, unloading. """
+
+ def __init__(self, core):
+ self.core = core
+ self.config = self.core.config
+
+ __builtin__.addonManager = self #needed to let addons register themselves
+
+ self.log = self.core.log
+ self.plugins = {}
+ self.methods = {} # dict of names and list of methods usable by rpc
+ self.events = {} # Contains event that will be registered
+
+ self.lock = RLock()
+ self.createIndex()
+
+ #registering callback for config event
+ self.config.changeCB = MethodType(self.dispatchEvent, "configChanged", basestring)
+
+ # manage addons an config change
+ self.addEvent("configChanged", self.manageAddons)
+
+ @lock
+ def callInHooks(self, event, *args):
+ """ Calls a method in all addons and catch / log errors"""
+ for plugin in self.plugins.itervalues():
+ self.call(plugin, event, *args)
+ self.dispatchEvent(event, *args)
+
+ def call(self, addon, f, *args):
+ try:
+ func = getattr(addon, f)
+ return func(*args)
+ except Exception, e:
+ addon.logError(_("Error when executing %s" % f), e)
+ if self.core.debug:
+ print_exc()
+
+ def addRPC(self, plugin, func, doc):
+ 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):
+ if not args: args = []
+ else:
+ args = literal_eval(args)
+
+ plugin = self.plugins[plugin]
+ f = getattr(plugin, func)
+ return f(*args)
+
+ @lock
+ def createIndex(self):
+ active = []
+ deactive = []
+
+ for pluginname in self.core.pluginManager.getPlugins("addons"):
+ try:
+ # check first for builtin plugin
+ attrs = self.core.pluginManager.loadAttributes("addons", pluginname)
+ internal = attrs.get("internal", False)
+
+ if internal or self.core.config.get(pluginname, "activated"):
+ pluginClass = self.core.pluginManager.loadClass("addons", pluginname)
+
+ if not pluginClass: continue
+
+ plugin = pluginClass(self.core, self)
+ self.plugins[pluginClass.__name__] = plugin
+
+ # hide internals from printing
+ if not internal and plugin.isActivated():
+ active.append(pluginClass.__name__)
+ else:
+ self.log.debug("Loaded internal plugin: %s" % pluginClass.__name__)
+ else:
+ deactive.append(pluginname)
+
+
+ except:
+ self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
+ if self.core.debug:
+ print_exc()
+
+ self.log.info(_("Activated addons: %s") % ", ".join(sorted(active)))
+ self.log.info(_("Deactivate addons: %s") % ", ".join(sorted(deactive)))
+
+ def manageAddons(self, plugin, name, value):
+ # check if section was a plugin
+ if plugin not in self.core.pluginManager.getPlugins("addons"):
+ return
+
+ if name == "activated" and value:
+ self.activateAddon(plugin)
+ elif name == "activated" and not value:
+ self.deactivateAddon(plugin)
+
+ @lock
+ def activateAddon(self, plugin):
+ #check if already loaded
+ if plugin in self.plugins:
+ return
+
+ pluginClass = self.core.pluginManager.loadClass("addons", plugin)
+
+ if not pluginClass: return
+
+ self.log.debug("Plugin loaded: %s" % plugin)
+
+ plugin = pluginClass(self.core, self)
+ self.plugins[pluginClass.__name__] = plugin
+
+ # active the addon in new thread
+ start_new_thread(plugin.activate, tuple())
+ self.registerEvents() # TODO: BUG: events will be destroyed and not re-registered
+
+ @lock
+ def deactivateAddon(self, plugin):
+ if plugin not in self.plugins:
+ return
+ else:
+ addon = self.plugins[plugin]
+
+ if addon.__internal__: return
+
+ self.call(addon, "deactivate")
+ self.log.debug("Plugin deactivated: %s" % plugin)
+
+ #remove periodic call
+ self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(addon.cb))
+ del self.plugins[addon.__name__]
+
+ #remove event listener
+ for f in dir(addon):
+ if f.startswith("__") or type(getattr(addon, f)) != MethodType:
+ continue
+ self.core.eventManager.removeFromEvents(getattr(addon, f))
+
+ def activateAddons(self):
+ self.log.info(_("Activating Plugins..."))
+ for plugin in self.plugins.itervalues():
+ if plugin.isActivated():
+ self.call(plugin, "activate")
+
+ self.registerEvents()
+
+ def deactivateAddons(self):
+ """ Called when core is shutting down """
+ self.log.info(_("Deactivating Plugins..."))
+ for plugin in self.plugins.itervalues():
+ self.call(plugin, "deactivate")
+
+ def downloadPreparing(self, pyfile):
+ self.callInHooks("downloadPreparing", pyfile)
+
+ def downloadFinished(self, pyfile):
+ self.callInHooks("downloadFinished", pyfile)
+
+ def downloadFailed(self, pyfile):
+ self.callInHooks("downloadFailed", pyfile)
+
+ def packageFinished(self, package):
+ self.callInHooks("packageFinished", package)
+
+ def beforeReconnecting(self, ip):
+ self.callInHooks("beforeReconnecting", ip)
+
+ def afterReconnecting(self, ip):
+ self.callInHooks("afterReconnecting", ip)
+
+ @lock
+ def startThread(self, function, *args, **kwargs):
+ AddonThread(self.core.threadManager, function, args, kwargs)
+
+ def activePlugins(self):
+ """ returns all active plugins """
+ return [x for x in self.plugins.itervalues() if x.isActivated()]
+
+ def getAllInfo(self):
+ """returns info stored by addon plugins"""
+ info = {}
+ for name, plugin in self.plugins.iteritems():
+ if plugin.info:
+ #copy and convert so str
+ info[name] = dict(
+ [(x, to_string(y)) for x, y in plugin.info.iteritems()])
+ return info
+
+ def getInfo(self, plugin):
+ info = {}
+ if plugin in self.plugins and self.plugins[plugin].info:
+ info = dict([(x, to_string(y))
+ for x, y in self.plugins[plugin].info.iteritems()])
+
+ return info
+
+ def addEventListener(self, plugin, func, event):
+ """ add the event to the list """
+
+
+ if plugin not in self.events:
+ self.events[plugin] = []
+ self.events[plugin].append((func, event))
+
+ def registerEvents(self):
+ """ actually register all saved events """
+ for name, plugin in self.plugins.iteritems():
+ if name in self.events:
+ for func, event in self.events[name]:
+ self.addEvent(event, getattr(plugin, func))
+ # clean up
+ del self.events[name]
+
+ def addConfigHandler(self, plugin, func):
+ pass #TODO
+
+ def addEvent(self, *args):
+ self.core.eventManager.addEvent(*args)
+
+ def dispatchEvent(self, *args):
+ self.core.eventManager.dispatchEvent(*args)
+
diff --git a/module/Api.py b/module/Api.py
index f0bf5e264..dec1526b2 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -1,37 +1,35 @@
#!/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
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
import re
+from os.path import join, isabs
+from itertools import chain
+from functools import partial
+from new import code
+from dis import opmap
-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"
@@ -39,54 +37,103 @@ if activated:
else:
from remote.socketbackend.ttypes import *
+from datatypes.PyFile import PyFile
+from utils import compare_time, to_string, bits_set, get_index
+from utils.fs import free_space
+from common.packagetools import parseNames
+from network.RequestFactory import getURL
+
# contains function names mapped to their permissions
# unlisted functions are for admins only
-permMap = {}
+perm_map = {}
+
+# store which methods needs user context
+user_context = {}
# decorator only called on init, never initialized, so has no effect on runtime
-def permission(bits):
+def RequirePerm(bits):
class _Dec(object):
def __new__(cls, func, *args, **kwargs):
- permMap[func.__name__] = bits
+ perm_map[func.__name__] = bits
return func
-
+
return _Dec
+# we will byte-hacking the method to add user as keyword argument
+class UserContext(object):
+ """Decorator to mark methods that require a specific user"""
+
+ def __new__(cls, f, *args, **kwargs):
+ fc = f.func_code
+
+ try:
+ i = get_index(fc.co_names, "user")
+ except ValueError: # functions does not uses user, so no need to modify
+ return f
+
+ user_context[f.__name__] = True
+ new_names = tuple([x for x in fc.co_names if f != "user"])
+ new_varnames = tuple([x for x in fc.co_varnames] + ["user"])
+ new_code = fc.co_code
+
+ # subtract 1 from higher LOAD_GLOBAL
+ for x in range(i + 1, len(fc.co_names)):
+ new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(x), chr(opmap['LOAD_GLOBAL']) + chr(x - 1))
+
+ # load argument instead of global
+ new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(i), chr(opmap['LOAD_FAST']) + chr(fc.co_argcount))
+
+ new_fc = code(fc.co_argcount + 1, fc.co_nlocals + 1, fc.co_stacksize, fc.co_flags, new_code, fc.co_consts,
+ new_names, new_varnames, fc.co_filename, fc.co_name, fc.co_firstlineno, fc.co_lnotab, fc.co_freevars,
+ fc.co_cellvars)
+
+ f.func_code = new_fc
+
+ # None as default argument for user
+ if f.func_defaults:
+ f.func_defaults = tuple([x for x in f.func_defaults] + [None])
+ else:
+ f.func_defaults = (None,)
+
+ return f
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(userPermission, Permission):
+ return bits_set(Permission, userPermission)
+
+from datatypes.User import User
+
+class UserApi(object):
+ """ Proxy object for api that provides all methods in user context """
+
+ def __init__(self, api, user):
+ self.api = api
+ self._user = user
-def has_permission(userperms, perms):
- # bytewise or perms before if needed
- return perms == (userperms & perms)
+ def __getattr__(self, item):
+ f = self.api.__getattribute__(item)
+ if f.func_name in user_context:
+ return partial(f, user=self._user)
+ return f
+
+ @property
+ def user(self):
+ return self._user
class Api(Iface):
"""
**pyLoads API**
- This is accessible either internal via core.api or via thrift backend.
+ This is accessible either internal via core.api, thrift backend or json api.
see Thrift specification file remote/thriftbackend/pyload.thrift\
- for information about data structures and what methods are usuable with rpc.
+ for information about data structures and what methods are usable with rpc.
Most methods requires specific permissions, please look at the source code if you need to know.\
- These can be configured via webinterface.
+ These can be configured via web interface.
Admin user have all permissions, and are the only ones who can access the methods with no specific permission.
"""
@@ -94,111 +141,60 @@ class Api(Iface):
def __init__(self, core):
self.core = core
+ self.user_apis = {}
- 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.
+ def withUserContext(self, uid):
+ """ Returns a proxy version of the api, to call method in user context
- :param category:
- :param option:
- :param value: new config value
- :param section: 'plugin' or 'core
+ :param uid: user or userData instance or uid
+ :return: :class:`UserApi`
"""
- self.core.hookManager.dispatchEvent("configChanged", category, option, value, section)
+ if isinstance(uid, User):
+ uid = uid.uid
- if section == "core":
- self.core.config[category][option] = value
+ if uid not in self.user_apis:
+ user = self.core.db.getUserData(uid=uid)
+ if not user: #TODO: anonymous user?
+ return None
- if option in ("limit_speed", "max_speed"): #not so nice to update the limit
- self.core.requestFactory.updateBucket()
+ self.user_apis[uid] = UserApi(self, User.fromUserData(self, user))
- elif section == "plugin":
- self.core.config.setPlugin(category, option, value)
+ return self.user_apis[uid]
- @permission(PERMS.SETTINGS)
- def getConfig(self):
- """Retrieves complete config of core.
-
- :return: list of `ConfigSection`
- """
- return self._convertConfigFormat(self.core.config.config)
+ ##########################
+ # Server Status
+ ##########################
- def getConfigDict(self):
- """Retrieves complete config in dict format, not for RPC.
-
- :return: dict
- """
- return self.core.config.config
+ @RequirePerm(Permission.All)
+ def getServerVersion(self):
+ """pyLoad Core version """
+ return self.core.version
- @permission(PERMS.SETTINGS)
- def getPluginConfig(self):
- """Retrieves complete config for all plugins.
+ @RequirePerm(Permission.All)
+ def statusServer(self):
+ """Some general information about the current status of pyLoad.
- :return: list of `ConfigSection`
+ :return: `ServerStatus`
"""
- return self._convertConfigFormat(self.core.config.plugin)
-
- def getPluginConfigDict(self):
- """Plugin config as dict, not for RPC.
+ 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())
- :return: dict
- """
- return self.core.config.plugin
+ 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 pauseServer(self):
- """Pause server: Tt wont start any new downloads, but nothing gets aborted."""
+ """Pause server: It won't 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.
@@ -207,7 +203,6 @@ class Api(Iface):
self.core.threadManager.pause ^= True
return self.core.threadManager.pause
- @permission(PERMS.STATUS)
def toggleReconnect(self):
"""Toggle reconnect activation.
@@ -216,31 +211,10 @@ class Api(Iface):
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"])
+ return free_space(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"""
@@ -250,7 +224,6 @@ class Api(Iface):
"""Restart pyload core"""
self.core.do_restart = True
- @permission(PERMS.LOGS)
def getLog(self, offset=0):
"""Returns most recent log entries.
@@ -268,7 +241,7 @@ class Api(Iface):
except:
return ['No log available']
- @permission(PERMS.STATUS)
+ @RequirePerm(Permission.All)
def isTimeDownload(self):
"""Checks if pyload will start new downloads according to time in config.
@@ -278,7 +251,7 @@ class Api(Iface):
end = self.core.config['downloadTime']['end'].split(":")
return compare_time(start, end)
- @permission(PERMS.STATUS)
+ @RequirePerm(Permission.All)
def isTimeReconnect(self):
"""Checks if pyload will try to make a reconnect
@@ -288,54 +261,114 @@ class Api(Iface):
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.
+ @RequirePerm(Permission.All)
+ def getProgressInfo(self):
+ """ Status of all currently running tasks
- :return: list of `DownloadStatus`
+ :return: list of `ProgressInfo`
"""
- data = []
- for pyfile in self.core.threadManager.getActiveFiles():
- if not isinstance(pyfile, PyFile):
- continue
+ pass
- 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))
+ ##########################
+ # Configuration
+ ##########################
- return data
+ def getConfigValue(self, section, option):
+ """Retrieve config value.
- @permission(PERMS.ADD)
- def addPackage(self, name, links, dest=Destination.Queue):
- """Adds a package, with links to desired destination.
+ :param section: name of category, or plugin
+ :param option: config option
+ :return: config value as string
+ """
+ value = self.core.config.get(section, option)
+ return to_string(value)
- :param name: name of the new package
- :param links: list of urls
- :param dest: `Destination`
- :return: package id of the new package
+ def setConfigValue(self, section, option, value):
+ """Set new config value.
+
+ :param section:
+ :param option:
+ :param value: new config value
"""
- if self.core.config['general']['folder_per_package']:
- folder = name
- else:
- folder = ""
+ if option in ("limit_speed", "max_speed"): #not so nice to update the limit
+ self.core.requestFactory.updateBucket()
- folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_")
+ self.core.config.set(section, option, value)
- pid = self.core.files.addPackage(name, folder, dest)
+ def getConfig(self):
+ """Retrieves complete config of core.
- self.core.files.addLinks(links, pid)
+ :return: map of `ConfigHolder`
+ """
+ # TODO
+ return dict([(section, ConfigHolder(section, data.name, data.description, data.long_desc, [
+ ConfigItem(option, d.name, d.description, d.type, to_string(d.default),
+ to_string(self.core.config.get(section, option))) for
+ option, d in data.config.iteritems()])) for
+ section, data in self.core.config.getBaseSections()])
- self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)})
- self.core.files.save()
+ def getConfigRef(self):
+ """Config instance, not for RPC"""
+ return self.core.config
- return pid
+ def getGlobalPlugins(self):
+ """All global plugins/addons, only admin can use this
+
+ :return: list of `ConfigInfo`
+ """
+ pass
+
+ @UserContext
+ @RequirePerm(Permission.Plugins)
+ def getUserPlugins(self):
+ """List of plugins every user can configure for himself
+
+ :return: list of `ConfigInfo`
+ """
+ pass
+
+ @UserContext
+ @RequirePerm(Permission.Plugins)
+ def configurePlugin(self, plugin):
+ """Get complete config options for an plugin
- @permission(PERMS.ADD)
+ :param plugin: Name of the plugin to configure
+ :return: :class:`ConfigHolder`
+ """
+
+ pass
+
+ @UserContext
+ @RequirePerm(Permission.Plugins)
+ def saveConfig(self, config):
+ """Used to save a configuration, core config can only be saved by admins
+
+ :param config: :class:`ConfigHolder
+ """
+ pass
+
+ @UserContext
+ @RequirePerm(Permission.Plugins)
+ def deleteConfig(self, plugin):
+ """Deletes modified config
+
+ :param plugin: plugin name
+ :return:
+ """
+ pass
+
+ @RequirePerm(Permission.Plugins)
+ def setConfigHandler(self, plugin, iid, value):
+ pass
+
+ ##########################
+ # Download Preparing
+ ##########################
+
+ @RequirePerm(Permission.Add)
def parseURLs(self, html=None, url=None):
- """Parses html content or any arbitaty text for links and returns result of `checkURLs`
+ """Parses html content or any arbitrary text for links and returns result of `checkURLs`
:param html: html source
:return:
@@ -353,17 +386,17 @@ class Api(Iface):
return self.checkURLs(set(urls))
- @permission(PERMS.ADD)
+ @RequirePerm(Permission.Add)
def checkURLs(self, urls):
- """ Gets urls and returns pluginname mapped to list of matches urls.
+ """ Gets urls and returns pluginname mapped to list of matching urls.
:param urls:
:return: {plugin: urls}
"""
- data = self.core.pluginManager.parseUrls(urls)
+ data, crypter = self.core.pluginManager.parseUrls(urls)
plugins = {}
- for url, plugin in data:
+ for url, plugin in chain(data, crypter):
if plugin in plugins:
plugins[plugin].append(url)
else:
@@ -371,18 +404,17 @@ class Api(Iface):
return plugins
- @permission(PERMS.ADD)
+ @RequirePerm(Permission.Add)
def checkOnlineStatus(self, urls):
- """ initiates online status check
+ """ initiates online status check, will also decrypt files.
:param urls:
- :return: initial set of data as `OnlineCheck` instance containing the result id
+ :return: initial set of data as :class:`OnlineCheck` instance containing the result id
"""
- data = self.core.pluginManager.parseUrls(urls)
+ data, crypter = 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]
+ # initial result does not contain the crypter links
+ tmp = [(url, (url, LinkStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
data = parseNames(tmp)
result = {}
@@ -391,29 +423,32 @@ class Api(Iface):
status.packagename = k
result[url] = status
+ data.update(crypter) # hoster and crypter will be processed
+ rid = self.core.threadManager.createResultThread(data, False)
+
return OnlineCheck(rid, result)
- @permission(PERMS.ADD)
+ @RequirePerm(Permission.Add)
def checkOnlineStatusContainer(self, urls, container, data):
- """ checks online status of urls and a submited container file
+ """ checks online status of urls and a submitted container file
:param urls: list of urls
:param container: container file name
:param data: file content
- :return: online check
+ :return: :class:`OnlineCheck`
"""
th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
th.write(str(data))
th.close()
+ urls.append(th.name)
+ return self.checkOnlineStatus(urls)
- return self.checkOnlineStatus(urls + [th.name])
-
- @permission(PERMS.ADD)
+ @RequirePerm(Permission.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
+ :return: `OnlineCheck`, if rid is -1 then there is no more data available
"""
result = self.core.threadManager.getInfoResult(rid)
@@ -424,7 +459,7 @@ class Api(Iface):
return OnlineCheck(rid, result)
- @permission(PERMS.ADD)
+ @RequirePerm(Permission.Add)
def generatePackages(self, links):
""" Parses links, generates packages names from urls
@@ -434,292 +469,336 @@ class Api(Iface):
result = parseNames((x, x) for x in links)
return result
- @permission(PERMS.ADD)
- def generateAndAddPackages(self, links, dest=Destination.Queue):
+ ##########################
+ # Adding/Deleting
+ ##########################
+
+ @RequirePerm(Permission.Add)
+ def generateAndAddPackages(self, links, paused=False):
"""Generates and add packages
:param links: list of urls
- :param dest: `Destination`
+ :param paused: paused package
:return: list of package ids
"""
- return [self.addPackage(name, urls, dest) for name, urls
+ return [self.addPackageP(name, urls, "", paused) 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.
+ @RequirePerm(Permission.Add)
+ def createPackage(self, name, folder, root, password="", site="", comment="", paused=False):
+ """Create a new package.
- :param links: list of urls
- :param dest: `Destination`
- :return: None
+ :param name: display name of the package
+ :param folder: folder name or relative path, abs path are not allowed
+ :param root: package id of root package, -1 for top level package
+ :param password: single pw or list of passwords separated with new line
+ :param site: arbitrary url to site for more information
+ :param comment: arbitrary comment
+ :param paused: No downloads will be started when True
+ :return: pid of newly created package
"""
- data = self.core.pluginManager.parseUrls(links)
- self.core.threadManager.createResultThread(data, True)
+ if isabs(folder):
+ folder = folder.replace("/", "_")
- @permission(PERMS.LIST)
- def getPackageData(self, pid):
- """Returns complete information about package, and included files.
+ folder = folder.replace("http://", "").replace(":", "").replace("\\", "_").replace("..", "")
- :param pid: package id
- :return: `PackageData` with .links attribute
+ self.core.log.info(_("Added package %(name)s as folder %(folder)s") % {"name": name, "folder": folder})
+ pid = self.core.files.addPackage(name, folder, root, password, site, comment, paused)
+
+ return pid
+
+
+ @RequirePerm(Permission.Add)
+ def addPackage(self, name, links, password=""):
+ """Convenient method to add a package to the top-level and for adding links.
+
+ :return: package id
"""
- data = self.core.files.getPackageData(int(pid))
+ return self.addPackageChild(name, links, password, -1, False)
- if not data:
- raise PackageDoesNotExists(pid)
+ @RequirePerm(Permission.Add)
+ def addPackageP(self, name, links, password, paused):
+ """ Same as above with additional paused attribute. """
+ return self.addPackageChild(name, links, password, -1, paused)
+
+ @RequirePerm(Permission.Add)
+ def addPackageChild(self, name, links, password, root, paused):
+ """Adds a package, with links to desired package.
+
+ :param root: parents package id
+ :return: package id of the new package
+ """
+ if self.core.config['general']['folder_per_package']:
+ folder = name
+ else:
+ folder = ""
- 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()])
+ pid = self.createPackage(name, folder, root, password)
+ self.addLinks(pid, links)
- return pdata
+ return pid
- @permission(PERMS.LIST)
- def getPackageInfo(self, pid):
- """Returns information about package, without detailed information about containing files
+ @RequirePerm(Permission.Add)
+ def addLinks(self, pid, links):
+ """Adds links to specific package. Initiates online status fetching.
:param pid: package id
- :return: `PackageData` with .fid attribute
+ :param links: list of urls
"""
- data = self.core.files.getPackageData(int(pid))
-
- if not data:
- raise PackageDoesNotExists(pid)
+ hoster, crypter = self.core.pluginManager.parseUrls(links)
- pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- fids=[int(x) for x in data["links"]])
+ if hoster:
+ self.core.files.addLinks(hoster, pid)
+ self.core.threadManager.createInfoThread(hoster, pid)
- return pdata
+ self.core.threadManager.createDecryptThread(crypter, pid)
- @permission(PERMS.LIST)
- def getFileData(self, fid):
- """Get complete information about a specific file.
+ self.core.log.info((_("Added %d links to package") + " #%d" % pid) % len(hoster))
+ self.core.files.save()
- :param fid: file id
- :return: `FileData`
+ @RequirePerm(Permission.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
"""
- info = self.core.files.getFileData(int(fid))
- if not info:
- raise FileDoesNotExists(fid)
+ th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
+ th.write(str(data))
+ th.close()
- fdata = self._convertPyFile(info.values()[0])
- return fdata
+ return self.addPackage(th.name, [th.name])
- @permission(PERMS.DELETE)
+ @RequirePerm(Permission.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))
+ for fid in fids:
+ self.core.files.deleteFile(fid)
self.core.files.save()
- @permission(PERMS.DELETE)
+ @RequirePerm(Permission.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))
+ for pid in pids:
+ self.core.files.deletePackage(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()]
+ ##########################
+ # Collector
+ ##########################
- @permission(PERMS.LIST)
+ @RequirePerm(Permission.All)
def getCollector(self):
- """same as `getQueue` for collector.
+ pass
- :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()]
+ @RequirePerm(Permission.Add)
+ def addToCollector(self, links):
+ pass
+
+ @RequirePerm(Permission.Add)
+ def addFromCollector(self, name, new_name):
+ pass
+
+ @RequirePerm(Permission.Delete)
+ def deleteCollPack(self, name):
+ pass
- @permission(PERMS.LIST)
- def getCollectorData(self):
- """same as `getQueueData` for collector.
+ @RequirePerm(Permission.Add)
+ def renameCollPack(self, name, new_name):
+ pass
+
+ @RequirePerm(Permission.Delete)
+ def deleteCollLink(self, url):
+ pass
+
+ #############################
+ # File Information retrieval
+ #############################
+
+ @RequirePerm(Permission.All)
+ def getAllFiles(self):
+ """ same as `getFileTree` for toplevel root and full tree"""
+ return self.getFileTree(-1, True)
- :return: list of `PackageInfo`
+ @RequirePerm(Permission.All)
+ def getAllUnfinishedFiles(self):
+ """ same as `getUnfinishedFileTree for toplevel root and full tree"""
+ return self.getUnfinishedFileTree(-1, True)
+
+ @RequirePerm(Permission.All)
+ def getFileTree(self, pid, full):
+ """ Retrieve data for specific package. full=True will retrieve all data available
+ and can result in greater delays.
+
+ :param pid: package id
+ :param full: go down the complete tree or only the first layer
+ :return: :class:`TreeCollection`
"""
- 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()]
+ return self.core.files.getTree(pid, full, False)
+ @RequirePerm(Permission.All)
+ def getUnfinishedFileTree(self, pid, full):
+ """ Same as `getFileTree` but only contains unfinished files.
- @permission(PERMS.ADD)
- def addFiles(self, pid, links):
- """Adds files to specific package.
-
:param pid: package id
- :param links: list of urls
+ :param full: go down the complete tree or only the first layer
+ :return: :class:`TreeCollection`
"""
- self.core.files.addLinks(links, int(pid))
+ return self.core.files.getTree(pid, full, False)
- self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
- self.core.files.save()
+ @RequirePerm(Permission.All)
+ def getPackageContent(self, pid):
+ """ Only retrieve content of a specific package. see `getFileTree`"""
+ return self.getFileTree(pid, False)
- @permission(PERMS.MODIFY)
- def pushToQueue(self, pid):
- """Moves package from Collector to Queue.
+ @RequirePerm(Permission.All)
+ def getPackageInfo(self, pid):
+ """Returns information about package, without detailed information about containing files
:param pid: package id
+ :raises PackageDoesNotExists:
+ :return: :class:`PackageInfo`
"""
- self.core.files.setPackageLocation(pid, Destination.Queue)
+ info = self.core.files.getPackageInfo(pid)
+ if not info:
+ raise PackageDoesNotExists(pid)
+ return info
- @permission(PERMS.MODIFY)
- def pullFromQueue(self, pid):
- """Moves package from Queue to Collector.
+ @RequirePerm(Permission.All)
+ def getFileInfo(self, fid):
+ """ Info for specific file
+
+ :param fid: file id
+ :raises FileDoesNotExists:
+ :return: :class:`FileInfo`
- :param pid: package id
"""
- self.core.files.setPackageLocation(pid, Destination.Collector)
+ info = self.core.files.getFileInfo(fid)
+ if not info:
+ raise FileDoesNotExists(fid)
+ return info
+
+ @RequirePerm(Permission.All)
+ def findFiles(self, pattern):
+ pass
- @permission(PERMS.MODIFY)
+ #############################
+ # Modify Downloads
+ #############################
+
+ @RequirePerm(Permission.Modify)
def restartPackage(self, pid):
"""Restarts a package, resets every containing files.
:param pid: package id
"""
- self.core.files.restartPackage(int(pid))
+ self.core.files.restartPackage(pid)
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def restartFile(self, fid):
"""Resets file status, so it will be downloaded again.
- :param fid: file id
+ :param fid: file id
"""
- self.core.files.restartFile(int(fid))
+ self.core.files.restartFile(fid)
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def recheckPackage(self, pid):
- """Proofes online status of all files in a package, also a default action when package is added.
+ """Check online status of all files in a package, also a default action when package is added. """
+ self.core.files.reCheckPackage(pid)
- :param pid:
- :return:
- """
- self.core.files.reCheckPackage(int(pid))
+ @RequirePerm(Permission.Modify)
+ def restartFailed(self):
+ """Restarts all failed failes."""
+ self.core.files.restartFailed()
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def stopAllDownloads(self):
"""Aborts all running downloads."""
- pyfiles = self.core.files.cache.values()
+ pyfiles = self.core.files.cachedFiles()
for pyfile in pyfiles:
pyfile.abortDownload()
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def stopDownloads(self, fids):
"""Aborts specific downloads.
:param fids: list of file ids
:return:
"""
- pyfiles = self.core.files.cache.values()
-
+ pyfiles = self.core.files.cachedFiles()
for pyfile in pyfiles:
if pyfile.id in fids:
pyfile.abortDownload()
- @permission(PERMS.MODIFY)
- def setPackageName(self, pid, name):
- """Renames a package.
+ #############################
+ # Modify Files/Packages
+ #############################
- :param pid: package id
- :param name: new package name
- """
- pack = self.core.files.getPackage(pid)
- pack.name = name
- pack.sync()
+ @RequirePerm(Permission.Modify)
+ def setPackagePaused(self, pid, paused):
+ pass
+
+ @RequirePerm(Permission.Modify)
+ def setPackageFolder(self, pid, path):
+ pass
- @permission(PERMS.MODIFY)
- def movePackage(self, destination, pid):
- """Set a new package location.
+ @RequirePerm(Permission.Modify)
+ def movePackage(self, pid, root):
+ """ Set a new root for specific package. This will also moves the files on disk\
+ and will only work when no file is currently downloading.
- :param destination: `Destination`
:param pid: package id
+ :param root: package id of new root
+ :raises PackageDoesNotExists: When pid or root is missing
+ :return: False if package can't be moved
"""
- if destination not in (0, 1): return
- self.core.files.setPackageLocation(pid, destination)
+ return self.core.files.movePackage(pid, root)
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def moveFiles(self, fids, pid):
- """Move multiple files to another package
+ """Move multiple files to another package. This will move the files on disk and\
+ only work when files are not downloading. All files needs to be continuous ordered
+ in the current package.
:param fids: list of file ids
:param pid: destination package
- :return:
+ :return: False if files can't be moved
"""
- #TODO: implement
- pass
+ return self.core.files.moveFiles(fids, pid)
-
- @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)
+ @RequirePerm(Permission.Modify)
def orderPackage(self, pid, position):
- """Gives a package a new position.
+ """Set new position for a package.
:param pid: package id
- :param position:
+ :param position: new position, 0 for very beginning
"""
- self.core.files.reorderPackage(pid, position)
+ self.core.files.orderPackage(pid, position)
- @permission(PERMS.MODIFY)
- def orderFile(self, fid, position):
- """Gives a new position to a file within its package.
+ @RequirePerm(Permission.Modify)
+ def orderFiles(self, fids, pid, position):
+ """ Set a new position for a bunch of files within a package.
+ All files have to be in the same package and must be **continuous**\
+ in the package. That means no gaps between them.
- :param fid: file id
- :param position:
+ :param fids: list of file ids
+ :param pid: package id of parent package
+ :param position: new position: 0 for very beginning
"""
- self.core.files.reorderFile(fid, position)
+ self.core.files.orderFiles(fids, pid, position)
- @permission(PERMS.MODIFY)
+ @RequirePerm(Permission.Modify)
def setPackageData(self, pid, data):
"""Allows to modify several package attributes.
@@ -736,164 +815,108 @@ class Api(Iface):
p.sync()
self.core.files.save()
- @permission(PERMS.DELETE)
- def deleteFinished(self):
- """Deletes all finished files and completly finished packages.
+ #############################
+ # User Interaction
+ #############################
- :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.
+ @RequirePerm(Permission.Interaction)
+ def isInteractionWaiting(self, mode):
+ """ Check if task is waiting.
- :param destination: `Destination`
- :return: dict mapping order to package id
+ :param mode: binary or'ed output type
+ :return: boolean
"""
+ return self.core.interactionManager.isTaskWaiting(mode)
- 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
+ @RequirePerm(Permission.Interaction)
+ def getInteractionTask(self, mode):
+ """Retrieve task for specific mode.
- @permission(PERMS.LIST)
- def getFileOrder(self, pid):
- """Information about file order within package.
-
- :param pid:
- :return: dict mapping order to file id
+ :param mode: binary or'ed output type
+ :return: :class:`InteractionTask`
"""
- 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
-
+ task = self.core.interactionManager.getTask(mode)
+ return InteractionTask(-1) if not task else task
- @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
+ @RequirePerm(Permission.Interaction)
+ def setInteractionResult(self, iid, result):
+ """Set Result for a interaction task. It will be immediately removed from task queue afterwards
- :param exclusive: unused
- :return: `CaptchaTask`
+ :param iid: interaction id
+ :param result: result as string
"""
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTask()
+ task = self.core.interactionManager.getTaskByID(iid)
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)
+ task.setResult(result)
- @permission(PERMS.STATUS)
- def getCaptchaTaskStatus(self, tid):
- """Get information about captcha task
+ @RequirePerm(Permission.Interaction)
+ def getNotifications(self):
+ """List of all available notifcations. They stay in queue for some time, client should\
+ save which notifications it already has seen.
- :param tid: task id
- :return: string
+ :return: list of :class:`InteractionTask`
"""
- self.core.lastClientConnected = time()
- t = self.core.captchaManager.getTaskByID(tid)
- return t.getStatus() if t else ""
+ return self.core.interactionManager.getNotifications()
+
+ @RequirePerm(Permission.Interaction)
+ def getAddonHandler(self):
+ pass
- @permission(PERMS.STATUS)
- def setCaptchaResult(self, tid, result):
- """Set result for a captcha task
+ @RequirePerm(Permission.Interaction)
+ def callAddonHandler(self, plugin, func, pid_or_fid):
+ pass
- :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)
+ @RequirePerm(Permission.Download)
+ def generateDownloadLink(self, fid, timeout):
+ pass
+ #############################
+ # Event Handling
+ #############################
- @permission(PERMS.STATUS)
def getEvents(self, uuid):
- """Lists occured events, may be affected to changes in future.
+ """Lists occurred events, may be affected to changes in future.
- :param uuid:
+ :param uuid: self assigned string uuid which has to be unique
: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)
+ # TODO: permissions?
+ # TODO
+ pass
+
+ #############################
+ # Account Methods
+ #############################
+
+ @RequirePerm(Permission.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)
+ accs = self.core.accountManager.getAllAccounts(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])
+ for plugin in accs.itervalues():
+ accounts.extend(plugin.values())
+
return accounts
- @permission(PERMS.ALL)
+ @RequirePerm(Permission.All)
def getAccountTypes(self):
"""All available account types.
- :return: list
+ :return: string list
"""
- return self.core.accountManager.accounts.keys()
+ return self.core.pluginManager.getPlugins("accounts").keys()
- @permission(PERMS.ACCOUNTS)
+ @RequirePerm(Permission.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)
+ @RequirePerm(Permission.Accounts)
def removeAccount(self, plugin, account):
"""Remove account from pyload.
@@ -902,7 +925,13 @@ class Api(Iface):
"""
self.core.accountManager.removeAccount(plugin, account)
- @permission(PERMS.ALL)
+ #############################
+ # Auth+User Information
+ #############################
+
+ # TODO
+
+ @RequirePerm(Permission.All)
def login(self, username, password, remoteip=None):
"""Login into pyLoad, this **must** be called when using rpc before any methods can be used.
@@ -923,111 +952,98 @@ class Api(Iface):
"""
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"
+
+ self.core.log.info(_("User '%s' tried to log in") % username)
return self.core.db.checkAuth(username, password)
- def isAuthorized(self, func, userdata):
+ def isAuthorized(self, func, user):
"""checks if the user is authorized for specific method
:param func: function name
- :param userdata: dictionary of user data
+ :param user: `User`
:return: boolean
"""
- if userdata == "local" or userdata["role"] == ROLE.ADMIN:
+ if user.isAdmin():
return True
- elif func in permMap and has_permission(userdata["permission"], permMap[func]):
+ elif func in perm_map and user.hasPermission(perm_map[func]):
return True
else:
return False
-
- @permission(PERMS.ALL)
+ # TODO
+ @RequirePerm(Permission.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()
+ user = self.checkAuth(username, password)
+ if not user:
+ raise UserDoesNotExists(username)
+ return user.toUserData()
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 self.core.db.getAllUserData()
- return res
+ def changePassword(self, username, oldpw, newpw):
+ """ changes password for specific user """
+ return self.core.db.changePassword(username, oldpw, newpw)
- @permission(PERMS.STATUS)
+ def setUserPermission(self, user, permission, role):
+ self.core.db.setPermission(user, permission)
+ self.core.db.setRole(user, role)
+
+ #############################
+ # RPC Plugin Methods
+ #############################
+
+ # TODO: obsolete
+
+ @RequirePerm(Permission.Interaction)
def getServices(self):
- """ A dict of available services, these can be defined by hook plugins.
+ """ A dict of available services, these can be defined by addon plugins.
:return: dict with this style: {"plugin": {"method": "description"}}
"""
data = {}
- for plugin, funcs in self.core.hookManager.methods.iteritems():
+ for plugin, funcs in self.core.addonManager.methods.iteritems():
data[plugin] = funcs
return data
- @permission(PERMS.STATUS)
+ @RequirePerm(Permission.Interaction)
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]
+ pass
- @permission(PERMS.STATUS)
- def call(self, info):
- """Calls a service (a method in hook plugin).
+ @RequirePerm(Permission.Interaction)
+ def call(self, plugin, func, arguments):
+ """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.hookManager.callRPC(plugin, func, args, parse)
- return str(ret)
+ ret = self.core.addonManager.callRPC(plugin, func, arguments)
+ return to_string(ret)
except Exception, e:
raise ServiceException(e.message)
- @permission(PERMS.STATUS)
+
+ #TODO: permissions
def getAllInfo(self):
- """Returns all information stored by hook plugins. Values are always strings
+ """Returns all information stored by addon plugins. Values are always strings
:return: {"plugin": {"name": value } }
"""
- return self.core.hookManager.getAllInfo()
+ 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.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
+ return self.core.addonManager.getInfo(plugin)
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/FileManager.py b/module/FileManager.py
new file mode 100644
index 000000000..60fa9dbb3
--- /dev/null
+++ b/module/FileManager.py
@@ -0,0 +1,583 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
+from time import time
+from threading import RLock
+
+from module.utils import lock
+
+from Api import PackageStatus, DownloadStatus as DS, TreeCollection, PackageDoesNotExists
+from datatypes.PyFile import PyFile
+from datatypes.PyPackage import PyPackage, RootPackage
+
+# invalidates the cache
+def invalidate(func):
+ def new(*args):
+ args[0].filecount = -1
+ args[0].queuecount = -1
+ args[0].jobCache = {}
+ return func(*args)
+
+ return new
+
+# TODO: needs to be replaced later
+OWNER = 0
+
+class FileManager:
+ """Handles all request made to obtain information,
+ modify status or other request for links or packages"""
+
+ ROOT_PACKAGE = -1
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+ self.evm = core.eventManager
+
+ # translations
+ self.statusMsg = [_("none"), _("offline"), _("online"), _("queued"), _("paused"),
+ _("finished"), _("skipped"), _("failed"), _("starting"),
+ _("waiting"), _("downloading"), _("temp. offline"), _("aborted"),
+ _("decrypting"), _("processing"), _("custom"), _("unknown")]
+
+ self.files = {} # holds instances for files
+ self.packages = {} # same for packages
+
+ self.jobCache = {}
+
+ # locking the cache, db is already locked implicit
+ self.lock = RLock()
+ #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.db = self.core.db
+
+ def save(self):
+ """saves all data to backend"""
+ self.db.commit()
+
+ @lock
+ def syncSave(self):
+ """saves all data to backend and waits until all data are written"""
+ for pyfile in self.files.values():
+ pyfile.sync()
+
+ for pypack in self.packages.values():
+ pypack.sync()
+
+ self.db.syncSave()
+
+ def cachedFiles(self):
+ return self.files.values()
+
+ def cachedPackages(self):
+ return self.packages.values()
+
+ def getCollector(self):
+ pass
+
+ @invalidate
+ def addLinks(self, data, package):
+ """Add links, data = (plugin, url) tuple. Internal method should use API."""
+ self.db.addLinks(data, package, OWNER)
+ self.evm.dispatchEvent("packageUpdated", package)
+
+
+ @invalidate
+ def addPackage(self, name, folder, root, password, site, comment, paused):
+ """Adds a package to database"""
+ pid = self.db.addPackage(name, folder, root, password, site, comment,
+ PackageStatus.Paused if paused else PackageStatus.Ok, OWNER)
+ p = self.db.getPackageInfo(pid)
+
+ self.evm.dispatchEvent("packageInserted", pid, p.root, p.packageorder)
+ return pid
+
+
+ @lock
+ def getPackage(self, pid):
+ """return package instance"""
+ if pid == self.ROOT_PACKAGE:
+ return RootPackage(self, OWNER)
+ elif pid in self.packages:
+ pack = self.packages[pid]
+ pack.timestamp = time()
+ return pack
+ else:
+ info = self.db.getPackageInfo(pid, False)
+ if not info: return None
+
+ pack = PyPackage.fromInfoData(self, info)
+ self.packages[pid] = pack
+
+ return pack
+
+ @lock
+ def getPackageInfo(self, pid):
+ """returns dict with package information"""
+ if pid == self.ROOT_PACKAGE:
+ pack = RootPackage(self, OWNER).toInfoData()
+ elif pid in self.packages:
+ pack = self.packages[pid].toInfoData()
+ pack.stats = self.db.getStatsForPackage(pid)
+ else:
+ pack = self.db.getPackageInfo(pid)
+
+ if not pack: return None
+
+ # todo: what does this todo mean?!
+ #todo: fill child packs and files
+ packs = self.db.getAllPackages(root=pid)
+ if pid in packs: del packs[pid]
+ pack.pids = packs.keys()
+
+ files = self.db.getAllFiles(package=pid)
+ pack.fids = files.keys()
+
+ return pack
+
+ @lock
+ def getFile(self, fid):
+ """returns pyfile instance"""
+ if fid in self.files:
+ return self.files[fid]
+ else:
+ info = self.db.getFileInfo(fid)
+ if not info: return None
+
+ f = PyFile.fromInfoData(self, info)
+ self.files[fid] = f
+ return f
+
+ @lock
+ def getFileInfo(self, fid):
+ """returns dict with file information"""
+ if fid in self.files:
+ return self.files[fid].toInfoData()
+
+ return self.db.getFileInfo(fid)
+
+ @lock
+ def getTree(self, pid, full, unfinished):
+ """ return a TreeCollection and fill the info data of containing packages.
+ optional filter only unfnished files
+ """
+ view = TreeCollection(pid)
+
+ # for depth=1, we don't need to retrieve all files/packages
+ root = pid if not full else None
+
+ packs = self.db.getAllPackages(root)
+ files = self.db.getAllFiles(package=root, unfinished=unfinished)
+
+ # updating from cache
+ for fid, f in self.files.iteritems():
+ if fid in files:
+ files[fid] = f.toInfoData()
+
+ # foreign pid, don't overwrite local pid !
+ for fpid, p in self.packages.iteritems():
+ if fpid in packs:
+ # copy the stats data
+ stats = packs[fpid].stats
+ packs[fpid] = p.toInfoData()
+ packs[fpid].stats = stats
+
+ # root package is not in database, create an instance
+ if pid == self.ROOT_PACKAGE:
+ view.root = RootPackage(self, OWNER).toInfoData()
+ packs[self.ROOT_PACKAGE] = view.root
+ elif pid in packs:
+ view.root = packs[pid]
+ else: # package does not exists
+ return view
+
+ # linear traversal over all data
+ for fpid, p in packs.iteritems():
+ if p.fids is None: p.fids = []
+ if p.pids is None: p.pids = []
+
+ root = packs.get(p.root, None)
+ if root:
+ if root.pids is None: root.pids = []
+ root.pids.append(fpid)
+
+ for fid, f in files.iteritems():
+ p = packs.get(f.package, None)
+ if p: p.fids.append(fid)
+
+
+ # cutting of tree is not good in runtime, only saves bandwidth
+ # need to remove some entries
+ if full and pid > -1:
+ keep = []
+ queue = [pid]
+ while queue:
+ fpid = queue.pop()
+ keep.append(fpid)
+ queue.extend(packs[fpid].pids)
+
+ # now remove unneeded data
+ for fpid in packs.keys():
+ if fpid not in keep:
+ del packs[fpid]
+
+ for fid, f in files.items():
+ if f.package not in keep:
+ del files[fid]
+
+ #remove root
+ del packs[pid]
+ view.files = files
+ view.packages = packs
+
+ return view
+
+
+ @lock
+ def getJob(self, occ):
+ """get suitable job"""
+
+ #TODO needs to be approved for new database
+ #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())
+
+
+ return pyfile
+
+
+ def getFileCount(self):
+ """returns number of files"""
+
+ if self.filecount == -1:
+ self.filecount = self.db.filecount()
+
+ 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()
+
+ return self.queuecount
+
+ def scanDownloadFolder(self):
+ pass
+
+ def searchFile(self, pattern):
+ return self.db.getAllFiles(search=pattern)
+
+ @lock
+ @invalidate
+ def deletePackage(self, pid):
+ """delete package and all contained links"""
+
+ p = self.getPackage(pid)
+ if not p: return
+
+ oldorder = p.packageorder
+ root = p.root
+
+ for pyfile in self.cachedFiles():
+ if pyfile.packageid == pid:
+ pyfile.abortDownload()
+
+ # TODO: delete child packages
+ # TODO: delete folder
+
+ self.db.deletePackage(pid)
+ self.releasePackage(pid)
+
+ for pack in self.cachedPackages():
+ if pack.root == root and pack.packageorder > oldorder:
+ pack.packageorder -= 1
+
+ self.evm.dispatchEvent("packageDeleted", pid)
+
+ @lock
+ @invalidate
+ def deleteFile(self, fid):
+ """deletes links"""
+
+ f = self.getFile(fid)
+ if not f: return
+
+ pid = f.packageid
+ order = f.fileorder
+
+ if fid in self.core.threadManager.processingIds():
+ f.abortDownload()
+
+ # TODO: delete real file
+
+ self.db.deleteFile(fid, f.fileorder, f.packageid)
+ self.releaseFile(fid)
+
+ for pyfile in self.files.itervalues():
+ if pyfile.packageid == pid and pyfile.fileorder > order:
+ pyfile.fileorder -= 1
+
+ self.evm.dispatchEvent("fileDeleted", fid, pid)
+
+ @lock
+ def releaseFile(self, fid):
+ """removes pyfile from cache"""
+ if fid in self.files:
+ del self.files[fid]
+
+ @lock
+ def releasePackage(self, pid):
+ """removes package from cache"""
+ if pid in self.packages:
+ del self.packages[pid]
+
+ def updateFile(self, pyfile):
+ """updates file"""
+ self.db.updateFile(pyfile)
+ self.evm.dispatchEvent("fileUpdated", pyfile.fid, pyfile.packageid)
+
+ def updatePackage(self, pypack):
+ """updates a package"""
+ self.db.updatePackage(pypack)
+ self.evm.dispatchEvent("packageUpdated", pypack.pid)
+
+ @invalidate
+ def updateFileInfo(self, data, pid):
+ """ updates file info (name, size, status,[ hash,] url)"""
+ self.db.updateLinkInfo(data)
+ self.evm.dispatchEvent("packageUpdated", pid)
+
+ def checkAllLinksFinished(self):
+ """checks if all files are finished and dispatch event"""
+
+ if not self.getQueueCount(True):
+ self.core.addonManager.dispatchEvent("allDownloadsFinished")
+ self.core.log.debug("All downloads finished")
+ return True
+
+ return False
+
+ def checkAllLinksProcessed(self, fid=-1):
+ """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(fid):
+ self.core.addonManager.dispatchEvent("allDownloadsProcessed")
+ self.core.log.debug("All downloads processed")
+ return True
+
+ return False
+
+ 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 resetCount(self):
+ self.queuecount = -1
+
+ @lock
+ @invalidate
+ def restartPackage(self, pid):
+ """restart package"""
+ for pyfile in self.cachedFiles():
+ if pyfile.packageid == pid:
+ self.restartFile(pyfile.id)
+
+ self.db.restartPackage(pid)
+
+ if pid in self.packages:
+ self.packages[pid].setFinished = False
+
+ self.evm.dispatchEvent("packageUpdated", pid)
+
+ @lock
+ @invalidate
+ def restartFile(self, fid):
+ """ restart file"""
+ if fid in self.files:
+ f = self.files[fid]
+ f.status = DS.Queued
+ f.name = f.url
+ f.error = ""
+ f.abortDownload()
+
+ self.db.restartFile(fid)
+ self.evm.dispatchEvent("fileUpdated", fid)
+
+
+ @lock
+ @invalidate
+ def orderPackage(self, pid, position):
+
+ p = self.getPackageInfo(pid)
+ self.db.orderPackage(pid, p.root, p.packageorder, position)
+
+ for pack in self.packages.itervalues():
+ if pack.root != p.root or pack.packageorder < 0: continue
+ if pack.pid == pid:
+ pack.packageorder = position
+ if p.packageorder > position:
+ if position <= pack.packageorder < p.packageorder:
+ pack.packageorder += 1
+ elif p.order < position:
+ if position >= pack.packageorder > p.packageorder:
+ pack.packageorder -= 1
+
+ self.db.commit()
+
+ self.evm.dispatchEvent("packageReordered", pid, position, p.root)
+
+ @lock
+ @invalidate
+ def orderFiles(self, fids, pid, position):
+
+ files = [self.getFileInfo(fid) for fid in fids]
+ orders = [f.fileorder for f in files]
+ if min(orders) + len(files) != max(orders) + 1:
+ raise Exception("Tried to reorder non continous block of files")
+
+ # minimum fileorder
+ f = reduce(lambda x,y: x if x.fileorder < y.fileorder else y, files)
+ order = f.fileorder
+
+ self.db.orderFiles(pid, fids, order, position)
+ diff = len(fids)
+
+ if f.fileorder > position:
+ for pyfile in self.files.itervalues():
+ if pyfile.packageid != f.package or pyfile.fileorder < 0: continue
+ if position <= pyfile.fileorder < f.fileorder:
+ pyfile.fileorder += diff
+
+ for i, fid in enumerate(fids):
+ if fid in self.files:
+ self.files[fid].fileorder = position + i
+
+ elif f.fileorder < position:
+ for pyfile in self.files.itervalues():
+ if pyfile.packageid != f.package or pyfile.fileorder < 0: continue
+ if position >= pyfile.fileorder >= f.fileorder+diff:
+ pyfile.fileorder -= diff
+
+ for i, fid in enumerate(fids):
+ if fid in self.files:
+ self.files[fid].fileorder = position -diff + i + 1
+
+ self.db.commit()
+
+ self.evm.dispatchEvent("filesReordered", pid)
+
+ @lock
+ @invalidate
+ def movePackage(self, pid, root):
+ """ move pid - root """
+
+ p = self.getPackageInfo(pid)
+ dest = self.getPackageInfo(root)
+ if not p: raise PackageDoesNotExists(pid)
+ if not dest: raise PackageDoesNotExists(root)
+
+ # cantor won't be happy if we put the package in itself
+ if pid == root or p.root == root: return False
+
+ # TODO move real folders
+
+ # we assume pack is not in use anyway, so we can release it
+ self.releasePackage(pid)
+ self.db.movePackage(p.root, p.packageorder, pid, root)
+
+ return True
+
+
+
+ @lock
+ @invalidate
+ def moveFiles(self, fids, pid):
+ """ move all fids to pid """
+
+ f = self.getFileInfo(fids[0])
+ if not f or f.package == pid:
+ return False
+ if not self.getPackageInfo(pid):
+ raise PackageDoesNotExists(pid)
+
+ # TODO move real files
+
+ self.db.moveFiles(f.package, fids, pid)
+
+ return 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 (DS.NA, DS.Finished, DS.Skipped):
+ urls.append((pyfile.url, pyfile.pluginname))
+
+ self.core.threadManager.createInfoThread(urls, pid)
+
+
+ @invalidate
+ def restartFailed(self):
+ """ restart all failed links """
+ # failed should not be in cache anymore, so working on db is sufficient
+ self.db.restartFailed()
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
index 156c9f932..14207847e 100644
--- a/module/InitHomeDir.py
+++ b/module/InitHomeDir.py
@@ -52,7 +52,7 @@ else:
__builtin__.homedir = homedir
-args = " ".join(argv[1:])
+args = " ".join(argv)
# dirty method to set configdir from commandline arguments
if "--configdir=" in args:
@@ -63,6 +63,10 @@ if "--configdir=" in args:
configdir = args[pos + 12:].strip()
else:
configdir = args[pos + 12:end].strip()
+elif "nosetests" in args:
+ print "Running in test mode"
+ configdir = join(pypath, "tests", "config")
+
elif path.exists(path.join(pypath, "module", "config", "configdir")):
f = open(path.join(pypath, "module", "config", "configdir"), "rb")
c = f.read().strip()
diff --git a/module/PluginManager.py b/module/PluginManager.py
new file mode 100644
index 000000000..81a5ee81c
--- /dev/null
+++ b/module/PluginManager.py
@@ -0,0 +1,403 @@
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN, mkaay
+###############################################################################
+
+import re
+import sys
+
+from os import listdir, makedirs
+from os.path import isfile, join, exists, abspath, basename
+from sys import version_info
+from time import time
+
+from module.lib.SafeEval import const_eval as literal_eval
+from module.plugins.Base import Base
+
+from new_collections import namedtuple
+
+#TODO: ignores not updatable
+
+# ignore these plugin configs, mainly because plugins were wiped out
+IGNORE = (
+ "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('addons', 'UnRar'),
+ 'EasyShareCom', 'FlyshareCz'
+ )
+
+PluginTuple = namedtuple("PluginTuple", "version re deps user path")
+
+class PluginManager:
+ ROOT = "module.plugins."
+ USERROOT = "userplugins."
+ TYPES = ("crypter", "hoster", "accounts", "addons", "internal")
+
+ BUILTIN = re.compile(r'__(?P<attr>[a-z0-9_]+)__\s*=\s?(True|False|None|[0-9x.]+)', re.I)
+ SINGLE = re.compile(r'__(?P<attr>[a-z0-9_]+)__\s*=\s*(?:r|u|_)?((?:(?<!")"(?!")|\'|\().*(?:(?<!")"(?!")|\'|\)))',
+ re.I)
+ # note the nongreedy character: that means we can not embed list and dicts
+ MULTI = re.compile(r'__(?P<attr>[a-z0-9_]+)__\s*=\s*((?:\{|\[|"{3}).*?(?:"""|\}|\]))', re.DOTALL | re.M | re.I)
+
+ def __init__(self, core):
+ self.core = core
+
+ #self.config = self.core.config
+ self.log = core.log
+
+ self.plugins = {}
+ self.modules = {} # cached modules
+ self.history = [] # match history to speedup parsing (type, name)
+ self.createIndex()
+
+ self.core.config.parseValues(self.core.config.PLUGIN)
+
+ #register for import addon
+ sys.meta_path.append(self)
+
+
+ def logDebug(self, type, plugin, msg):
+ self.log.debug("Plugin %s | %s: %s" % (type, plugin, msg))
+
+ def createIndex(self):
+ """create information for all plugins available"""
+ # add to path, so we can import from userplugins
+ 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()
+
+ a = time()
+ for type in self.TYPES:
+ self.plugins[type] = self.parse(type)
+
+ self.log.debug("Created index of plugins in %.2f ms", (time() - a) * 1000)
+
+ def parse(self, folder, home=None):
+ """ Analyze and parses all plugins in folder """
+ 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("_"):
+ 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
+
+ # replace suffix and version tag
+ name = f[:-3]
+ if name[-1] == ".": name = name[:-4]
+
+ plugin = self.parsePlugin(join(pfolder, f), folder, name, home)
+ if plugin:
+ plugins[name] = plugin
+
+ if not home:
+ temp = self.parse(folder, plugins)
+ plugins.update(temp)
+
+ return plugins
+
+ def parseAttributes(self, filename, name, folder=""):
+ """ Parse attribute dict from plugin"""
+ data = open(filename, "rb")
+ content = data.read()
+ data.close()
+
+ attrs = {}
+ for m in self.BUILTIN.findall(content) + self.SINGLE.findall(content) + self.MULTI.findall(content):
+ #replace gettext function and eval result
+ try:
+ attrs[m[0]] = literal_eval(m[-1].replace("_(", "("))
+ except:
+ self.logDebug(folder, name, "Error when parsing: %s" % m[-1])
+ self.core.print_exc()
+
+ if not hasattr(Base, "__%s__" % m[0]):
+ if m[0] != "type": #TODO remove type from all plugins, its not needed
+ self.logDebug(folder, name, "Unknown attribute '%s'" % m[0])
+
+ return attrs
+
+ def parsePlugin(self, filename, folder, name, home=None):
+ """ Parses a plugin from disk, folder means plugin type in this context. Also sets config.
+
+ :arg home: dict with plugins, of which the found one will be matched against (according version)
+ :returns PluginTuple"""
+
+ attrs = self.parseAttributes(filename, name, folder)
+ if not attrs: return
+
+ version = 0
+
+ if "version" in attrs:
+ try:
+ version = float(attrs["version"])
+ except ValueError:
+ self.logDebug(folder, name, "Invalid version %s" % attrs["version"])
+ version = 9 #TODO remove when plugins are fixed, causing update loops
+ else:
+ self.logDebug(folder, name, "No version attribute")
+
+ # home contains plugins from pyload root
+ if home and name in home:
+ if home[name].version >= version:
+ return
+
+ if name in IGNORE or (folder, name) in IGNORE:
+ return
+
+ if "pattern" in attrs and attrs["pattern"]:
+ try:
+ plugin_re = re.compile(attrs["pattern"])
+ except:
+ self.logDebug(folder, name, "Invalid regexp pattern '%s'" % attrs["pattern"])
+ plugin_re = None
+ else: plugin_re = None
+
+ deps = attrs.get("dependencies", None)
+
+ # create plugin tuple
+ plugin = PluginTuple(version, plugin_re, deps, bool(home), filename)
+
+
+ # internals have no config
+ if folder == "internal":
+ return plugin
+
+ if folder == "addons" and "config" not in attrs and not attrs.get("internal", False):
+ attrs["config"] = (["activated", "bool", "Activated", False],)
+
+ if "config" in attrs and attrs["config"]:
+ config = attrs["config"]
+ desc = attrs.get("description", "")
+ long_desc = attrs.get("long_description", "")
+
+ if type(config[0]) == tuple:
+ config = [list(x) for x in config]
+ else:
+ config = [list(config)]
+
+ if folder == "addons" and not attrs.get("internal", False):
+ for item in config:
+ if item[0] == "activated": break
+ else: # activated flag missing
+ config.insert(0, ("activated", "bool", "Activated", False))
+
+ try:
+ self.core.config.addConfigSection(name, name, desc, long_desc, config)
+ except:
+ self.logDebug(folder, name, "Invalid config %s" % config)
+
+ return plugin
+
+
+ def parseUrls(self, urls):
+ """parse plugins for given list of urls, separate to crypter and hoster"""
+
+ res = {"hoster": [], "crypter": []} # tupels of (url, plugin)
+
+ for url in urls:
+ if type(url) not in (str, unicode, buffer):
+ self.log.debug("Parsing invalid type %s" % type(url))
+ continue
+
+ found = False
+
+ for ptype, name in self.history:
+ if self.plugins[ptype][name].re.match(url):
+ res[ptype].append((url, name))
+ found = (ptype, name)
+ break # need to exit this loop first
+
+ if found: # found match
+ if self.history[0] != found: #update history
+ self.history.remove(found)
+ self.history.insert(0, found)
+ continue
+
+ for ptype in ("crypter", "hoster"):
+ for name, plugin in self.plugins[ptype].iteritems():
+ if plugin.re.match(url):
+ res[ptype].append((url, name))
+ self.history.insert(0, (ptype, name))
+ del self.history[10:] # cut down to size of 10
+ found = True
+ break
+
+ if not found:
+ res["hoster"].append((url, "BasePlugin"))
+
+ return res["hoster"], res["crypter"]
+
+ def getPlugins(self, type):
+ return self.plugins.get(type, None)
+
+ def findPlugin(self, name, pluginlist=("hoster", "crypter")):
+ for ptype in pluginlist:
+ if name in self.plugins[ptype]:
+ return ptype, self.plugins[ptype][name]
+ return None, None
+
+ def getPluginModule(self, name):
+ """ Decprecated: return plugin module from hoster|crypter"""
+ self.log.debug("Deprecated method: .getPluginModule()")
+ type, plugin = self.findPlugin(name)
+ return self.loadModule(type, name)
+
+ def getPluginClass(self, name):
+ """ return plugin class from hoster|crypter, always the not overwritten one """
+ type, plugin = self.findPlugin(name)
+ return self.loadClass(type, name)
+
+ # MultiHoster will overwrite this
+ getPlugin = getPluginClass
+
+
+ def loadAttributes(self, type, name):
+ plugin = self.plugins[type][name]
+ return self.parseAttributes(plugin.path, name, type)
+
+
+ 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 (type, name) in self.modules: return self.modules[(type, name)]
+ try:
+ # convert path to python recognizable import
+ path = basename(plugins[name].path).replace(".pyc", "").replace(".py", "")
+ module = __import__(self.ROOT + "%s.%s" % (type, path), globals(), locals(), path)
+ self.modules[(type, name)] = module # cache import, maybe unneeded
+ return module
+ except Exception, e:
+ self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)})
+ self.core.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 find_module(self, fullname, path=None):
+ #redirecting imports if necesarry
+ if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #separate 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 addons or internals, would cause to much side effects
+ if "addons" 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 (type, plugin) in self.modules:
+ self.log.debug("Reloading %s" % plugin)
+ reload(self.modules[(type, plugin)])
+
+ # index re-creation
+ for type in ("crypter", "container", "hoster", "captcha", "accounts"):
+ self.plugins[type] = self.parse(type)
+
+ 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
+
+ def loadIcons(self):
+ """Loads all icons from plugins, plugin type is not in result, because its not important here.
+
+ :return: Dict of names mapped to icons
+ """
+ pass
+
+ def loadIcon(self, type, name):
+ """ load icon for single plugin, base64 encoded"""
+ pass
+
+ def checkDependencies(self, type, name):
+ """ Check deps for given plugin
+
+ :return: List of unfullfilled dependencies
+ """
+ pass
+
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/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/UserManager.py b/module/UserManager.py
new file mode 100644
index 000000000..9d5e8c5db
--- /dev/null
+++ b/module/UserManager.py
@@ -0,0 +1,23 @@
+#!/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
+"""
+
+class UserManager:
+ """
+ Manager class to handle all user related stuff
+ """ \ 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/cli/ManageFiles.py b/module/cli/ManageFiles.py
index 4d0377d9d..c133d9959 100644
--- a/module/cli/ManageFiles.py
+++ b/module/cli/ManageFiles.py
@@ -32,7 +32,7 @@ class ManageFiles(Handler):
def init(self):
self.target = Destination.Queue
self.pos = 0 #position in queue
- self.package = -1 #choosen package
+ self.package = -1 #chosen package
self.mode = "" # move/delete/restart
self.cache = None
@@ -107,10 +107,10 @@ class ManageFiles(Handler):
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.")
+ println(line + 1, "Enter a single number, comma separated numbers or ranges. e.g.: 1,2,3 or 1-3.")
line += 2
else:
- println(line, _("Choose what yout want to do or enter package number."))
+ println(line, _("Choose what you 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
diff --git a/module/common/APIExerciser.py b/module/common/APIExerciser.py
index 96f5ce9cf..ac6bc6c15 100644
--- a/module/common/APIExerciser.py
+++ b/module/common/APIExerciser.py
@@ -59,7 +59,7 @@ class APIExerciser(Thread):
def run(self):
- self.core.log.info("API Excerciser started %d" % self.id)
+ self.core.log.info("API Exerciser started %d" % self.id)
out = open("error.log", "ab")
#core errors are not logged of course
@@ -70,7 +70,7 @@ class APIExerciser(Thread):
try:
self.testAPI()
except Exception:
- self.core.log.error("Excerciser %d throw an execption" % self.id)
+ self.core.log.error("Exerciser %d throw an exception" % self.id)
print_exc()
out.write(format_exc() + 2 * "\n")
out.flush()
@@ -114,7 +114,7 @@ class APIExerciser(Thread):
name = "".join(sample(string.ascii_letters, 10))
urls = createURLs()
- self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]))
+ self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]), "")
def deleteFiles(self):
@@ -154,4 +154,4 @@ class APIExerciser(Thread):
self.api.getAccounts(False)
def getCaptchaTask(self):
- self.api.getCaptchaTask(False) \ No newline at end of file
+ self.api.getCaptchaTask(False)
diff --git a/module/common/packagetools.py b/module/common/packagetools.py
index 5bfbcba95..791a46d51 100644
--- a/module/common/packagetools.py
+++ b/module/common/packagetools.py
@@ -21,7 +21,7 @@ def parseNames(files):
""" Generates packages names from name, data lists
:param files: list of (name, data)
- :return: packagenames mapt to data lists (eg. urls)
+ :return: packagenames mapped to data lists (eg. urls)
"""
packs = {}
@@ -64,7 +64,7 @@ def parseNames(files):
if len(split) > 1:
name = split.pop(1)
- #check if a already existing package may be ok for this file
+ #check if an already existing package may be ok for this file
# found = False
# for pack in packs:
# if pack in file:
diff --git a/module/config/ConfigParser.py b/module/config/ConfigParser.py
new file mode 100644
index 000000000..3cad67bc2
--- /dev/null
+++ b/module/config/ConfigParser.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+from time import sleep
+from os.path import exists
+from gettext import gettext
+from new_collections import namedtuple, OrderedDict
+
+from module.utils import from_string
+from module.utils.fs import chmod
+
+from default import make_config
+
+CONF_VERSION = 2
+SectionTuple = namedtuple("SectionTuple", "name description long_desc config")
+ConfigData = namedtuple("ConfigData", "name type description default")
+
+class ConfigParser:
+ """
+ Holds and manages the configuration + meta data for core and every user.
+ """
+
+ CONFIG = "pyload.conf"
+ PLUGIN = "plugin.conf"
+
+ def __init__(self):
+ """Constructor"""
+
+ # core config sections from pyload
+ self.baseSections = []
+
+ # Meta data information
+ self.config = OrderedDict()
+ # The actual config values
+ self.values = {}
+
+ self.changeCB = None # callback when config value was changed
+
+ self.checkVersion()
+
+ self.loadDefault()
+ self.parseValues(self.CONFIG)
+
+ def loadDefault(self):
+ make_config(self)
+
+ def checkVersion(self):
+ """Determines if config needs to be deleted"""
+ e = None
+ # workaround conflict, with GUI (which also accesses the config) so try read in 3 times
+ for i in range(0, 3):
+ try:
+ for conf in (self.CONFIG, self.PLUGIN):
+ if exists(conf):
+ f = open(conf, "rb")
+ v = f.readline()
+ f.close()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ f = open(conf, "wb")
+ f.write("version: " + str(CONF_VERSION))
+ f.close()
+ print "Old version of %s deleted" % conf
+ else:
+ f = open(conf, "wb")
+ f.write("version:" + str(CONF_VERSION))
+ f.close()
+
+ except Exception, ex:
+ e = ex
+ sleep(0.3)
+ if e: raise e
+
+
+ def parseValues(self, filename):
+ """read config values from file"""
+ f = open(filename, "rb")
+ config = f.readlines()[1:]
+
+ # save the current section
+ section = ""
+
+ for line in config:
+ line = line.strip()
+
+ # comment line, different variants
+ if not line or line.startswith("#") or line.startswith("//") or line.startswith(";"): continue
+
+ if line.startswith("["):
+ section = line.replace("[", "").replace("]", "")
+
+ if section not in self.config:
+ print "Unrecognized section", section
+ section = ""
+
+ else:
+ name, non, value = line.rpartition("=")
+ name = name.strip()
+ value = value.strip()
+
+ if not section:
+ print "Value without section", name
+ continue
+
+ if name in self.config[section].config:
+ self.set(section, name, value, sync=False)
+ else:
+ print "Unrecognized option", section, name
+
+
+ def save(self):
+ """saves config to filename"""
+
+ # separate pyload and plugin conf
+ configs = []
+ for c in (self.CONFIG, self.PLUGIN):
+ f = open(c, "wb")
+ configs.append(f)
+ chmod(c, 0600)
+ f.write("version: %i\n\n" % CONF_VERSION)
+
+
+ # write on 2 files
+ for section, data in self.config.iteritems():
+ f = configs[0] if section in self.baseSections else configs[1]
+
+ f.write("[%s]\n" % section)
+
+ for option, data in data.config.iteritems():
+ value = self.get(section, option)
+ if type(value) == unicode: value = value.encode("utf8")
+ else: value = str(value)
+
+ f.write('%s = %s\n' % (option, value))
+
+ f.write("\n")
+
+ [f.close() for f in configs]
+
+ def __getitem__(self, section):
+ """provides dictionary like access: c['section']['option']"""
+ return Section(self, section)
+
+ def get(self, section, option):
+ """get value"""
+ if option in self.values[section]:
+ return self.values[section][option]
+ else:
+ return self.config[section].config[option].default
+
+ def set(self, section, option, value, sync=True):
+ """set value"""
+
+ data = self.config[section].config[option]
+ value = from_string(value, data.type)
+
+ # only save when different to default values
+ if value != data.default or (option in self.values[section] and value != self.values[section][option]):
+ self.values[section][option] = value
+ if sync:
+ if self.changeCB: self.changeCB(section, option, value)
+ self.save()
+
+ def getPlugin(self, *args):
+ """gets a value for a plugin"""
+ ret = self.get(*args)
+ print "Deprecated method getPlugin%s -> %s" % (str(args), ret)
+ return ret
+
+ def setPlugin(self, *args):
+ """sets a value for a plugin"""
+ print "Deprecated method setPlugin%s" % str(args)
+ self.set(*args)
+
+ def getMetaData(self, section, option):
+ """ get all config data for an option """
+ return self.config[section].config[option]
+
+ def getBaseSections(self):
+ for section, data in self.config.iteritems():
+ if section in self.baseSections:
+ yield section, data
+ return
+
+ def getPluginSections(self):
+ for section, data in self.config.iteritems():
+ if section not in self.baseSections:
+ yield section, data
+ return
+
+ def addConfigSection(self, section, name, desc, long_desc, config, base=False):
+ """Adds a section to the config. `config` is a list of config tuples as used in plugin api defined as:
+ Either (name, type, verbose_name, default_value) or
+ (name, type, verbose_name, short_description, default_value)
+ The order of the config elements is preserved with OrderedDict
+ """
+ d = OrderedDict()
+
+ for entry in config:
+ if len(entry) == 5:
+ conf_name, type, conf_desc, conf_verbose, default = entry
+ else: # config options without tooltip / description
+ conf_name, type, conf_desc, default = entry
+ conf_verbose = ""
+
+ d[conf_name] = ConfigData(gettext(conf_desc), type, gettext(conf_verbose), from_string(default, type))
+
+ if base:
+ if section not in self.baseSections: self.baseSections.append(section)
+ elif section in self.baseSections:
+ return # would overwrite base section
+
+ data = SectionTuple(gettext(name), gettext(desc), gettext(long_desc), d)
+ self.config[section] = data
+
+ if section not in self.values:
+ self.values[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)
+
+ 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/config/__init__.py b/module/config/__init__.py
new file mode 100644
index 000000000..4b31e848b
--- /dev/null
+++ b/module/config/__init__.py
@@ -0,0 +1 @@
+__author__ = 'christian'
diff --git a/module/config/default.conf b/module/config/default.conf
deleted file mode 100644
index 335ca10fe..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/default.py b/module/config/default.py
new file mode 100644
index 000000000..a9fad675c
--- /dev/null
+++ b/module/config/default.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+"""
+Configuration layout for default base config
+"""
+
+#TODO: write tooltips and descriptions
+
+def make_config(config):
+ # Check if gettext is installed
+ _ = lambda x: x
+
+ config.addConfigSection("remote", _("Remote"), _("Description"), _("Long description"),
+ [
+ ("nolocalauth", "bool", _("No authentication on local connections"), _("Tooltip"), True),
+ ("activated", "bool", _("Activated"), _("Tooltip"), True),
+ ("port", "int", _("Port"), _("Tooltip"), 7227),
+ ("listenaddr", "ip", _("Adress"), _("Tooltip"), "0.0.0.0"),
+ ],
+ True)
+
+ config.addConfigSection("log", _("Log"), _("Description"), _("Long description"),
+ [
+ ("log_size", "int", _("Size in kb"), _("Tooltip"), 100),
+ ("log_folder", "folder", _("Folder"), _("Tooltip"), "Logs"),
+ ("file_log", "bool", _("File Log"), _("Tooltip"), True),
+ ("log_count", "int", _("Count"), _("Tooltip"), 5),
+ ("log_rotate", "bool", _("Log Rotate"), _("Tooltip"), True),
+ ],
+ True)
+
+ config.addConfigSection("permission", _("Permissions"), _("Description"), _("Long description"),
+ [
+ ("group", "str", _("Groupname"), _("Tooltip"), "users"),
+ ("change_dl", "bool", _("Change Group and User of Downloads"), _("Tooltip"), False),
+ ("change_file", "bool", _("Change file mode of downloads"), _("Tooltip"), False),
+ ("user", "str", _("Username"), _("Tooltip"), "user"),
+ ("file", "str", _("Filemode for Downloads"), _("Tooltip"), "0644"),
+ ("change_group", "bool", _("Change group of running process"), _("Tooltip"), False),
+ ("folder", "str", _("Folder Permission mode"), _("Tooltip"), "0755"),
+ ("change_user", "bool", _("Change user of running process"), _("Tooltip"), False),
+ ],
+ True)
+
+ config.addConfigSection("general", _("General"), _("Description"), _("Long description"),
+ [
+ ("language", "en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR", _("Language"), _("Tooltip"), "en"),
+ ("download_folder", "folder", _("Download Folder"), _("Tooltip"), "Downloads"),
+ ("checksum", "bool", _("Use Checksum"), _("Tooltip"), False),
+ ("folder_per_package", "bool", _("Create folder for each package"), _("Tooltip"), True),
+ ("debug_mode", "bool", _("Debug Mode"), _("Tooltip"), False),
+ ("min_free_space", "int", _("Min Free Space (MB)"), _("Tooltip"), 200),
+ ("renice", "int", _("CPU Priority"), _("Tooltip"), 0),
+ ],
+ True)
+
+ config.addConfigSection("ssl", _("SSL"), _("Description"), _("Long description"),
+ [
+ ("cert", "file", _("SSL Certificate"), _("Tooltip"), "ssl.crt"),
+ ("activated", "bool", _("Activated"), _("Tooltip"), False),
+ ("key", "file", _("SSL Key"), _("Tooltip"), "ssl.key"),
+ ],
+ True)
+
+ config.addConfigSection("webinterface", _("Webinterface"), _("Description"), _("Long description"),
+ [
+ ("template", "str", _("Template"), _("Tooltip"), "default"),
+ ("activated", "bool", _("Activated"), _("Tooltip"), True),
+ ("prefix", "str", _("Path Prefix"), _("Tooltip"), ""),
+ ("server", "threaded;fastcgi;fallback;lightweight", _("Server"), _("Tooltip"), "threaded"),
+ ("host", "ip", _("IP"), _("Tooltip"), "0.0.0.0"),
+ ("https", "bool", _("Use HTTPS"), _("Tooltip"), False),
+ ("port", "int", _("Port"), _("Tooltip"), 8001),
+ ],
+ True)
+
+ config.addConfigSection("proxy", _("Proxy"), _("Description"), _("Long description"),
+ [
+ ("username", "str", _("Username"), _("Tooltip"), ""),
+ ("proxy", "bool", _("Use Proxy"), _("Tooltip"), False),
+ ("address", "str", _("Address"), _("Tooltip"), "localhost"),
+ ("password", "password", _("Password"), _("Tooltip"), ""),
+ ("type", "http;socks4;socks5", _("Protocol"), _("Tooltip"), "http"),
+ ("port", "int", _("Port"), _("Tooltip"), 7070),
+ ],
+ True)
+
+ config.addConfigSection("reconnect", _("Reconnect"), _("Description"), _("Long description"),
+ [
+ ("endTime", "time", _("End"), _("Tooltip"), "0:00"),
+ ("activated", "bool", _("Use Reconnect"), _("Tooltip"), False),
+ ("method", "str", _("Method"), _("Tooltip"), "./reconnect.sh"),
+ ("startTime", "time", _("Start"), _("Tooltip"), "0:00"),
+ ],
+ True)
+
+ config.addConfigSection("download", _("Download"), _("Description"), _("Long description"),
+ [
+ ("max_downloads", "int", _("Max Parallel Downloads"), _("Tooltip"), 3),
+ ("limit_speed", "bool", _("Limit Download Speed"), _("Tooltip"), False),
+ ("interface", "str", _("Download interface to bind (ip or Name)"), _("Tooltip"), ""),
+ ("skip_existing", "bool", _("Skip already existing files"), _("Tooltip"), False),
+ ("max_speed", "int", _("Max Download Speed in kb/s"), _("Tooltip"), -1),
+ ("ipv6", "bool", _("Allow IPv6"), _("Tooltip"), False),
+ ("chunks", "int", _("Max connections for one download"), _("Tooltip"), 3),
+ ("restart_failed", "bool", _("Restart failed downloads on startup"), _("Tooltip"), False),
+ ],
+ True)
+
+ config.addConfigSection("downloadTime", _("Download Time"), _("Description"), _("Long description"),
+ [
+ ("start", "time", _("Start"), _("Tooltip"), "0:00"),
+ ("end", "time", _("End"), _("Tooltip"), "0:00"),
+ ],
+ True)
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/AccountDatabase.py b/module/database/AccountDatabase.py
new file mode 100644
index 000000000..1602451fa
--- /dev/null
+++ b/module/database/AccountDatabase.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from module.database import queue, async
+from module.database import DatabaseBackend
+
+class AccountMethods:
+
+ @queue
+ def loadAccounts(db):
+ db.c.execute('SELECT plugin, loginname, activated, password, options FROM accounts;')
+ return db.c.fetchall()
+
+ @async
+ def saveAccounts(db, data):
+ db.c.executemany('INSERT INTO accounts(plugin, loginname, activated, password, options) VALUES(?,?,?,?,?)', data)
+
+ @async
+ def removeAccount(db, plugin, loginname):
+ db.c.execute('DELETE FROM accounts WHERE plugin=? AND loginname=?', (plugin, loginname))
+
+DatabaseBackend.registerSub(AccountMethods) \ No newline at end of file
diff --git a/module/database/ConfigDatabase.py b/module/database/ConfigDatabase.py
new file mode 100644
index 000000000..198ae0173
--- /dev/null
+++ b/module/database/ConfigDatabase.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.database import DatabaseMethods, queue, async
+
+class ConfigMethods(DatabaseMethods):
+
+ @async
+ def saveConfig(self, plugin, config, user=None):
+ if user is None:
+ self.c.execute('INSERT INTO settings(plugin, config) VALUES(?,?)', (plugin, config))
+ else:
+ self.c.execute('INSERT INTO settings(plugin, config, user) VALUES(?,?,?)', (plugin, config, user))
+
+
+ @queue
+ def loadConfig(self, plugin, user=None):
+ if user is None:
+ self.c.execute('SELECT config FROM settings WHERE plugin=?', (plugin, ))
+ else:
+ self.c.execute('SELECT config FROM settings WHERE plugin=? AND user=?', (plugin, user))
+
+ return self.c.fetchone()[0]
+
+ @async
+ def deleteConfig(self, plugin, user=None):
+ if user is None:
+ self.c.execute('DELETE FROM settings WHERE plugin=?', (plugin, ))
+ else:
+ self.c.execute('DELETE FROM settings WHERE plugin=? AND user=?', (plugin, user))
+
+ @queue
+ def loadAllConfigs(self):
+ self.c.execute('SELECT user, plugin, config FROM settings')
+ configs = {}
+ for r in self.c:
+ if r[0] in configs:
+ configs[r[0]][r[1]] = r[2]
+ else:
+ configs[r[0]] = {r[1]: r[2]}
+
+ return configs
+
+ @async
+ def clearAllConfigs(self):
+ self.c.execute('DELETE FROM settings')
+
+
+ConfigMethods.register() \ No newline at end of file
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py
index 9530390c3..58e1e74d8 100644
--- a/module/database/DatabaseBackend.py
+++ b/module/database/DatabaseBackend.py
@@ -1,86 +1,98 @@
#!/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
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN, mkaay
+###############################################################################
+
+from threading import Thread, Event
from shutil import move
from Queue import Queue
from traceback import print_exc
-from module.utils import chmod
+from module.utils.fs import chmod, exists, remove
try:
from pysqlite2 import dbapi2 as sqlite3
except:
import sqlite3
-DB_VERSION = 4
+DB = None
+DB_VERSION = 5
+
+def set_DB(db):
+ global DB
+ DB = db
+
+
+def queue(f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if DB:
+ return DB.queue(f, *args, **kwargs)
+
+ return x
+
+
+def async(f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if DB:
+ return DB.async(f, *args, **kwargs)
+
+ return x
+
+
+def inner(f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if DB:
+ return f(DB, *args, **kwargs)
+
+ return x
+
+
+class DatabaseMethods:
+ # stubs for autocompletion
+ core = None
+ manager = None
+ conn = None
+ c = None
-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
+ def register(cls):
+ DatabaseBackend.registerSub(cls)
+
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()
+ # 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):
@@ -104,46 +116,76 @@ class DatabaseJob():
self.exception = e
finally:
self.done.set()
-
+
def wait(self):
self.done.wait()
+
class DatabaseBackend(Thread):
subs = []
+
+ DB_FILE = "pyload.db"
+ VERSION_FILE = "db.version"
+
def __init__(self, core):
Thread.__init__(self)
self.setDaemon(True)
self.core = core
+ self.manager = None # set later
+ self.running = Event()
self.jobs = Queue()
-
- self.setuplock = Event()
-
- style.setDB(self)
-
+
+ set_DB(self)
+
def setup(self):
+ """ *MUST* be called before db can be used !"""
self.start()
- self.setuplock.wait()
-
- def run(self):
+ self.running.wait()
+
+ def init(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()
+ version = self._checkVersion()
+
+ self.conn = sqlite3.connect(self.DB_FILE)
+ chmod(self.DB_FILE, 0600)
+
+ self.c = self.conn.cursor()
+
+ if version is not None and version < DB_VERSION:
+ success = self._convertDB(version)
+
+ # delete database
+ if not success:
+ self.c.close()
+ self.conn.close()
+
+ try:
+ self.manager.core.log.warning(_("File database was deleted due to incompatible version."))
+ except:
+ print "File database was deleted due to incompatible version."
+
+ remove(self.VERSION_FILE)
+ move(self.DB_FILE, self.DB_FILE + ".backup")
+ f = open(self.VERSION_FILE, "wb")
+ f.write(str(DB_VERSION))
+ f.close()
+
+ self.conn = sqlite3.connect(self.DB_FILE)
+ chmod(self.DB_FILE, 0600)
+ self.c = self.conn.cursor()
+
+ self._createTables()
self.conn.commit()
-
- self.setuplock.set()
-
+
+
+ def run(self):
+ try:
+ self.init()
+ finally:
+ self.running.set()
+
while True:
j = self.jobs.get()
if j == "quit":
@@ -152,201 +194,298 @@ class DatabaseBackend(Thread):
break
j.processJob()
- @style.queue
+
def shutdown(self):
+ self.running.clear()
+ self._shutdown()
+
+ @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")
+ """ get db version"""
+ if not exists(self.VERSION_FILE):
+ f = open(self.VERSION_FILE, "wb")
f.write(str(DB_VERSION))
f.close()
return
-
- f = open("files.version", "rb")
+
+ f = open(self.VERSION_FILE, "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
-
+
+ return v
+
def _convertDB(self, v):
try:
- getattr(self, "_convertV%i" % v)()
+ return getattr(self, "_convertV%i" % v)()
except:
- try:
- self.core.log.error(_("Filedatabase could NOT be converted."))
- except:
- print "Filedatabase could NOT be converted."
-
+ return False
+
#--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."
-
+
+ def _convertV5(self):
+ return False
+
#--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')
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "packages" ('
+ '"pid" INTEGER PRIMARY KEY AUTOINCREMENT, '
+ '"name" TEXT NOT NULL, '
+ '"folder" TEXT DEFAULT "" NOT NULL, '
+ '"site" TEXT DEFAULT "" NOT NULL, '
+ '"comment" TEXT DEFAULT "" NOT NULL, '
+ '"password" TEXT DEFAULT "" NOT NULL, '
+ '"added" INTEGER DEFAULT 0 NOT NULL,' # set by trigger
+ '"status" INTEGER DEFAULT 0 NOT NULL,'
+ '"packageorder" INTEGER DEFAULT -1 NOT NULL,' #incremented by trigger
+ '"root" INTEGER DEFAULT -1 NOT NULL, '
+ '"owner" INTEGER NOT NULL, '
+ 'FOREIGN KEY(owner) REFERENCES users(uid), '
+ 'CHECK (root != pid)'
+ ')'
+ )
+
+ self.c.execute(
+ 'CREATE TRIGGER IF NOT EXISTS "insert_package" AFTER INSERT ON "packages"'
+ 'BEGIN '
+ 'UPDATE packages SET added = strftime("%s", "now"), '
+ 'packageorder = (SELECT max(p.packageorder) + 1 FROM packages p WHERE p.root=new.root) '
+ 'WHERE rowid = new.rowid;'
+ 'END'
+ )
+
+ self.c.execute(
+ 'CREATE TRIGGER IF NOT EXISTS "delete_package" AFTER DELETE ON "packages"'
+ 'BEGIN '
+ 'DELETE FROM files WHERE package = old.pid;'
+ 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > old.packageorder AND root=old.pid;'
+ 'END'
+ )
+ self.c.execute('CREATE INDEX IF NOT EXISTS "package_index" ON packages(root, owner)')
+ self.c.execute('CREATE INDEX IF NOT EXISTS "package_owner" ON packages(owner)')
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "files" ('
+ '"fid" INTEGER PRIMARY KEY AUTOINCREMENT, '
+ '"name" TEXT NOT NULL, '
+ '"size" INTEGER DEFAULT 0 NOT NULL, '
+ '"status" INTEGER DEFAULT 0 NOT NULL, '
+ '"media" INTEGER DEFAULT 1 NOT NULL,'
+ '"added" INTEGER DEFAULT 0 NOT NULL,'
+ '"fileorder" INTEGER DEFAULT -1 NOT NULL, '
+ '"url" TEXT DEFAULT "" NOT NULL, '
+ '"plugin" TEXT DEFAULT "" NOT NULL, '
+ '"hash" TEXT DEFAULT "" NOT NULL, '
+ '"dlstatus" INTEGER DEFAULT 0 NOT NULL, '
+ '"error" TEXT DEFAULT "" NOT NULL, '
+ '"package" INTEGER NOT NULL, '
+ '"owner" INTEGER NOT NULL, '
+ 'FOREIGN KEY(owner) REFERENCES users(uid), '
+ 'FOREIGN KEY(package) REFERENCES packages(id)'
+ ')'
+ )
+ self.c.execute('CREATE INDEX IF NOT EXISTS "file_index" ON files(package, owner)')
+ self.c.execute('CREATE INDEX IF NOT EXISTS "file_owner" ON files(owner)')
+
+ self.c.execute(
+ 'CREATE TRIGGER IF NOT EXISTS "insert_file" AFTER INSERT ON "files"'
+ 'BEGIN '
+ 'UPDATE files SET added = strftime("%s", "now"), '
+ 'fileorder = (SELECT max(f.fileorder) + 1 FROM files f WHERE f.package=new.package) '
+ 'WHERE rowid = new.rowid;'
+ 'END'
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "collector" ('
+ '"owner" INTEGER NOT NULL, '
+ '"data" TEXT NOT NULL, '
+ 'FOREIGN KEY(owner) REFERENCES users(uid), '
+ 'PRIMARY KEY(owner) ON CONFLICT REPLACE'
+ ') '
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "storage" ('
+ '"identifier" TEXT NOT NULL, '
+ '"key" TEXT NOT NULL, '
+ '"value" TEXT DEFAULT "", '
+ 'PRIMARY KEY (identifier, key) ON CONFLICT REPLACE'
+ ')'
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "users" ('
+ '"uid" INTEGER PRIMARY KEY AUTOINCREMENT, '
+ '"name" TEXT NOT NULL UNIQUE, '
+ '"email" TEXT DEFAULT "" NOT NULL, '
+ '"password" TEXT NOT NULL, '
+ '"role" INTEGER DEFAULT 0 NOT NULL, '
+ '"permission" INTEGER DEFAULT 0 NOT NULL, '
+ '"folder" TEXT DEFAULT "" NOT NULL, '
+ '"traffic" INTEGER DEFAULT -1 NOT NULL, '
+ '"dllimit" INTEGER DEFAULT -1 NOT NULL, '
+ '"dlquota" TEXT DEFAULT "" NOT NULL, '
+ '"hddquota" INTEGER DEFAULT -1 NOT NULL, '
+ '"template" TEXT DEFAULT "default" NOT NULL, '
+ '"user" INTEGER DEFAULT -1 NOT NULL, ' # set by trigger to self
+ 'FOREIGN KEY(user) REFERENCES users(uid)'
+ ')'
+ )
+ self.c.execute('CREATE INDEX IF NOT EXISTS "username_index" ON users(name)')
+
+ self.c.execute(
+ 'CREATE TRIGGER IF NOT EXISTS "insert_user" AFTER INSERT ON "users"'
+ 'BEGIN '
+ 'UPDATE users SET user = new.uid, folder=new.name '
+ 'WHERE rowid = new.rowid;'
+ 'END'
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "settings" ('
+ '"plugin" TEXT NOT NULL, '
+ '"user" INTEGER DEFAULT -1 NOT NULL, '
+ '"config" TEXT NOT NULL, '
+ 'FOREIGN KEY(user) REFERENCES users(uid), '
+ 'PRIMARY KEY (plugin, user) ON CONFLICT REPLACE'
+ ')'
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "accounts" ('
+ '"plugin" TEXT NOT NULL, '
+ '"loginname" TEXT NOT NULL, '
+ '"owner", INTEGER NOT NULL, '
+ '"activated" INTEGER DEFAULT 1, '
+ '"password" TEXT DEFAULT "", '
+ '"shared" INTEGER DEFAULT 0, '
+ '"options" TEXT DEFAULT "", '
+ 'FOREIGN KEY(owner) REFERENCES users(uid), '
+ 'PRIMARY KEY (plugin, loginname, owner) ON CONFLICT REPLACE'
+ ')'
+ )
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "stats" ('
+ '"user" INTEGER NOT NULL, '
+ '"plugin" TEXT NOT NULL, '
+ '"time" INTEGER NOT NULL, '
+ '"premium" INTEGER DEFAULT 0 NOT NULL, '
+ '"amount" INTEGER DEFAULT 0 NOT NULL, '
+ 'FOREIGN KEY(user) REFERENCES users(uid), '
+ 'PRIMARY KEY(user, plugin, time)'
+ ')'
+ )
+ self.c.execute('CREATE INDEX IF NOT EXISTS "stats_time" ON stats(time)')
#try to lower ids
- self.c.execute('SELECT max(id) FROM LINKS')
+ self.c.execute('SELECT max(fid) FROM files')
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"))
+ fid = int(fid) if fid else 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "files"))
-
- self.c.execute('SELECT max(id) FROM packages')
+ self.c.execute('SELECT max(pid) FROM packages')
pid = self.c.fetchone()[0]
- if pid:
- pid = int(pid)
- else:
- pid = 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 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
+
+ @async
def commit(self):
self.conn.commit()
- @style.queue
+ @queue
def syncSave(self):
self.conn.commit()
-
- @style.async
+
+ @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()
+ # only wait when db is running
+ if self.running.isSet(): 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)
+ raise AttributeError(attr)
if __name__ == "__main__":
db = DatabaseBackend()
db.setup()
-
+
class Test():
- @style.queue
+ @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
+
+ @async
def insert2(db):
c = db.createCursor()
- for i in range(1000*1000):
+ for i in range(1000 * 1000):
c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar"))
-
- @style.queue
+
+ @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
+
+ @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
-
+ print end - start
+
start = time()
db.insert2()
end = time()
- print end-start
-
+ print end - start
+
db.error()
diff --git a/module/database/FileDatabase.py b/module/database/FileDatabase.py
index 7e7efb028..e065b84e2 100644
--- a/module/database/FileDatabase.py
+++ b/module/database/FileDatabase.py
@@ -1,944 +1,391 @@
#!/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"""
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
+from new_collections import OrderedDict
+
+from module.Api import DownloadInfo, FileInfo, PackageInfo, PackageStats
+from module.database import DatabaseMethods, queue, async, inner
+
+zero_stats = PackageStats(0, 0, 0, 0)
+
+class FileMethods(DatabaseMethods):
+ @queue
+ def filecount(self, user=None):
+ """returns number of files"""
+ self.c.execute("SELECT COUNT(*) FROM files")
+ return self.c.fetchone()[0]
- p = self.getPackage(id)
- if not p:
- if id in self.packageCache: del self.packageCache[id]
- return
+ @queue
+ def queuecount(self, user=None):
+ """ number of files in queue not finished yet"""
+ # status not in NA, finished, skipped
+ self.c.execute("SELECT COUNT(*) FROM files WHERE dlstatus NOT IN (0,5,6)")
+ return self.c.fetchone()[0]
- oldorder = p.order
- queue = p.queue
+ @queue
+ def processcount(self, fid, user=None):
+ """ number of files which have to be processed """
+ # status in online, queued, starting, waiting, downloading
+ self.c.execute("SELECT COUNT(*) FROM files WHERE dlstatus IN (2,3,8,9,10) AND fid != ?", (fid, ))
+ return self.c.fetchone()[0]
- 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()
+ # TODO: think about multiuser side effects on *count methods
- self.db.deletePackage(p)
- self.core.pullManager.addEvent(e)
- self.core.hookManager.dispatchEvent("packageDeleted", id)
+ @queue
+ def addLink(self, url, name, plugin, package, owner):
+ # mark file status initially as missing, dlstatus - queued
+ self.c.execute('INSERT INTO files(url, name, plugin, status, dlstatus, package, owner) VALUES(?,?,?,1,3,?,?)',
+ (url, name, plugin, package, owner))
+ return self.c.lastrowid
- if id in self.packageCache:
- del self.packageCache[id]
+ @async
+ def addLinks(self, links, package, owner):
+ """ links is a list of tuples (url, plugin)"""
+ links = [(x[0], x[0], x[1], package, owner) for x in links]
+ self.c.executemany('INSERT INTO files(url, name, plugin, status, dlstatus, package, owner) VALUES(?,?,?,1,3,?,?)',
+ links)
+
+ @queue
+ def addFile(self, name, size, media, package, owner):
+ # file status - ok, dl status NA
+ self.c.execute('INSERT INTO files(name, size, media, package, owner) VALUES(?,?,?,?,?)',
+ (name, size, media, package, owner))
+ return self.c.lastrowid
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue == queue and pack.order > oldorder:
- pack.order -= 1
- pack.notifyChange()
+ @queue
+ def addPackage(self, name, folder, root, password, site, comment, status, owner):
+ self.c.execute(
+ 'INSERT INTO packages(name, folder, root, password, site, comment, status, owner) VALUES(?,?,?,?,?,?,?,?)'
+ , (name, folder, root, password, site, comment, status, owner))
+ return self.c.lastrowid
- #----------------------------------------------------------------------
- @lock
- @change
- def deleteLink(self, id):
- """deletes links"""
+ @async
+ def deletePackage(self, pid, owner=None):
+ # order updated by trigger, as well as links deleted
+ if owner is None:
+ self.c.execute('DELETE FROM packages WHERE pid=?', (pid,))
+ else:
+ self.c.execute('DELETE FROM packages WHERE pid=? AND owner=?', (pid, owner))
+
+ @async
+ def deleteFile(self, fid, order, package, owner=None):
+ """ To delete a file order and package of it is needed """
+ if owner is None:
+ self.c.execute('DELETE FROM files WHERE fid=?', (fid,))
+ self.c.execute('UPDATE files SET fileorder=fileorder-1 WHERE fileorder > ? AND package=?',
+ (order, package))
+ else:
+ self.c.execute('DELETE FROM files WHERE fid=? AND owner=?', (fid, owner))
+ self.c.execute('UPDATE files SET fileorder=fileorder-1 WHERE fileorder > ? AND package=? AND owner=?',
+ (order, package, owner))
+
+ @async
+ def saveCollector(self, owner, data):
+ """ simply save the json string to database """
+ self.c.execute("INSERT INTO collector(owner, data) VALUES (?,?)", (owner, data))
+
+ @queue
+ def retrieveCollector(self, owner):
+ """ retrieve the saved string """
+ self.c.execute('SELECT data FROM collector WHERE owner=?', (owner,))
+ r = self.c.fetchone()
+ if not r: return None
+ return r[0]
- f = self.getFile(id)
- if not f:
- return None
+ @async
+ def deleteCollector(self, owner):
+ """ drop saved user collector """
+ self.c.execute('DELETE FROM collector WHERE owner=?', (owner,))
- 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)
+ @queue
+ def getAllFiles(self, package=None, search=None, unfinished=False, owner=None):
+ """ Return dict with file information
- #----------------------------------------------------------------------
- def getPackageData(self, id):
- """returns dict with package information"""
- pack = self.getPackage(id)
+ :param package: optional package to filter out
+ :param search: or search string for file name
+ :param unfinished: filter by dlstatus not finished
+ :param owner: only specific owner
+ """
+ qry = ('SELECT fid, name, owner, size, status, media, added, fileorder, '
+ 'url, plugin, hash, dlstatus, error, package FROM files WHERE ')
- if not pack:
- return None
+ arg = []
- pack = pack.toDict()[id]
+ if unfinished:
+ qry += 'dlstatus NOT IN (0, 5, 6) AND '
+ if owner is not None:
+ qry += 'owner=? AND '
+ arg.append(owner)
- data = self.db.getPackageData(id)
+ if package is not None:
+ arg.append(package)
+ qry += 'package=? AND '
+ if search is not None:
+ search = "%%%s%%" % search.strip("%")
+ arg.append(search)
+ qry += "name LIKE ? "
- tmplist = []
+ # make qry valid
+ if qry.endswith("WHERE "): qry = qry[:-6]
+ if qry.endswith("AND "): qry = qry[:-4]
- 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)
+ self.c.execute(qry + "ORDER BY package, fileorder", arg)
- pack["links"] = data
+ data = OrderedDict()
+ for r in self.c:
+ f = FileInfo(r[0], r[1], r[13], r[2], r[3], r[4], r[5], r[6], r[7])
+ if r[11] > 0: # dl status != NA
+ f.download = DownloadInfo(r[8], r[9], r[10], r[11], self.manager.statusMsg[r[11]], r[12])
- return pack
+ data[r[0]] = f
- #----------------------------------------------------------------------
- def getFileData(self, id):
- """returns dict with file information"""
- if id in self.cache:
- return self.cache[id].toDbDict()
+ return data
- return self.db.getLinkData(id)
+ @queue
+ def getAllPackages(self, root=None, owner=None):
+ """ Return dict with package information
- #----------------------------------------------------------------------
- def getFile(self, id):
- """returns pyfile instance"""
- if id in self.cache:
- return self.cache[id]
- else:
- return self.db.getFile(id)
+ :param root: optional root to filter
+ """
+ qry = ('SELECT pid, name, folder, root, owner, site, comment, password, added, status, packageorder '
+ 'FROM packages%s ORDER BY root, packageorder')
- #----------------------------------------------------------------------
- @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)
+ if root is None:
+ stats = self.getPackageStats(owner=owner)
+ if owner is None:
+ self.c.execute(qry % "")
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())
-
+ self.c.execute(qry % " WHERE owner=?", (owner,))
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
+ stats = self.getPackageStats(root=root, owner=owner)
+ if owner is None:
+ self.c.execute(qry % ' WHERE root=? OR pid=?', (root, root))
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"""
+ self.c.execute(qry % ' WHERE (root=? OR pid=?) AND owner=?', (root, root, owner))
- 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]
+ data = OrderedDict()
+ for r in self.c:
+ data[r[0]] = PackageInfo(
+ r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats)
+ )
- @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]
+ return data
- @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
+ @inner
+ def getPackageStats(self, pid=None, root=None, owner=None):
+ qry = ("SELECT p.pid, SUM(f.size) AS sizetotal, COUNT(f.fid) AS linkstotal, sizedone, linksdone "
+ "FROM packages p JOIN files f ON p.pid = f.package AND f.dlstatus > 0 %(sub)s LEFT OUTER JOIN "
+ "(SELECT p.pid AS pid, SUM(f.size) AS sizedone, COUNT(f.fid) AS linksdone "
+ "FROM packages p JOIN files f ON p.pid = f.package %(sub)s AND f.dlstatus in (5,6) GROUP BY p.pid) s ON s.pid = p.pid "
+ "GROUP BY p.pid")
+
+ # status in (finished, skipped, processing)
+
+ if root is not None:
+ self.c.execute(qry % {"sub": "AND (p.root=:root OR p.pid=:root)"}, locals())
+ elif pid is not None:
+ self.c.execute(qry % {"sub": "AND p.pid=:pid"}, locals())
+ elif owner is not None:
+ self.c.execute(qry % {"sub": "AND p.owner=:owner"}, locals())
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(qry % {"sub": ""})
- 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],
- }
+ data[r[0]] = PackageStats(
+ r[2] if r[2] else 0,
+ r[4] if r[4] else 0,
+ int(r[1]) if r[1] else 0,
+ int(r[3]) if r[3] else 0,
+ )
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
+ @queue
+ def getStatsForPackage(self, pid):
+ return self.getPackageStats(pid=pid)[pid]
- 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 = {}
+ @queue
+ def getFileInfo(self, fid, force=False):
+ """get data for specific file, when force is true download info will be appended"""
+ self.c.execute('SELECT fid, name, owner, size, status, media, added, fileorder, '
+ 'url, plugin, hash, dlstatus, error, package FROM files '
+ 'WHERE fid=?', (fid,))
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
+ else:
+ f = FileInfo(r[0], r[1], r[13], r[2], r[3], r[4], r[5], r[6], r[7])
+ if r[11] > 0 or force:
+ f.download = DownloadInfo(r[8], r[9], r[10], r[11], self.manager.statusMsg[r[11]], r[12])
- @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), ))
+ return f
- 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],
- }
+ @queue
+ def getPackageInfo(self, pid, stats=True):
+ """get data for a specific package, optionally with package stats"""
+ if stats:
+ stats = self.getPackageStats(pid=pid)
- return data
+ self.c.execute('SELECT pid, name, folder, root, owner, site, comment, password, added, status, packageorder '
+ 'FROM packages WHERE pid=?', (pid,))
+ r = self.c.fetchone()
+ if not r:
+ return None
+ else:
+ return PackageInfo(
+ r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats) if stats else None
+ )
+
+ @async
+ def updateLinkInfo(self, data, owner):
+ """ data is list of tuples (name, size, status,[ hash,] url)"""
+ if data and len(data[0]) == 4:
+ self.c.executemany('UPDATE files SET name=?, size=?, dlstatus=? WHERE url=? AND dlstatus IN (0,1,2,3,14)',
+ data)
+ else:
+ self.c.executemany(
+ 'UPDATE files SET name=?, size=?, dlstatus=?, hash=? WHERE url=? AND dlstatus IN (0,1,2,3,14)', 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)))
+ @async
+ def updateFile(self, f):
+ self.c.execute('UPDATE files SET name=?, size=?, status=?,'
+ 'media=?, url=?, hash=?, dlstatus=?, error=? WHERE fid=?',
+ (f.name, f.size, f.filestatus, f.media, f.url,
+ f.hash, f.status, f.error, f.fid))
- @style.queue
+ @async
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), ))
+ self.c.execute('UPDATE packages SET name=?, folder=?, site=?, comment=?, password=?, status=? WHERE pid=?',
+ (p.name, p.folder, p.site, p.comment, p.password, p.status, p.pid))
+
+ # TODO: most modifying methods needs owner argument to avoid checking beforehand
+ @async
+ def orderPackage(self, pid, root, oldorder, order):
+ if oldorder > order: # package moved upwards
+ self.c.execute(
+ 'UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND root=? AND packageorder >= 0'
+ , (order, oldorder, root))
+ elif oldorder < order: # moved downwards
+ self.c.execute(
+ 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND root=? AND packageorder >= 0'
+ , (order, oldorder, root))
+
+ self.c.execute('UPDATE packages SET packageorder=? WHERE pid=?', (order, pid))
+
+ @async
+ def orderFiles(self, pid, fids, oldorder, order):
+ diff = len(fids)
+ data = []
+
+ if oldorder > order: # moved upwards
+ self.c.execute('UPDATE files SET fileorder=fileorder+? WHERE fileorder >= ? AND fileorder < ? AND package=?'
+ , (diff, order, oldorder, pid))
+ data = [(order + i, fid) for i, fid in enumerate(fids)]
+ elif oldorder < order:
+ self.c.execute(
+ 'UPDATE files SET fileorder=fileorder-? WHERE fileorder <= ? AND fileorder >= ? AND package=?'
+ , (diff, order, oldorder + diff, pid))
+ data = [(order - diff + i + 1, fid) for i, fid in enumerate(fids)]
+
+ self.c.executemany('UPDATE files SET fileorder=? WHERE fid=?', data)
+
+ @async
+ def moveFiles(self, pid, fids, package):
+ self.c.execute('SELECT max(fileorder) FROM files WHERE package=?', (package,))
r = self.c.fetchone()
- if not r: return None
- return PyPackage(self.manager, id, * r)
+ order = (r[0] if r[0] else 0) + 1
+
+ self.c.execute('UPDATE files SET fileorder=fileorder-? WHERE fileorder > ? AND package=?',
+ (len(fids), order, pid))
- #----------------------------------------------------------------------
- @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), ))
+ data = [(package, order + i, fid) for i, fid in enumerate(fids)]
+ self.c.executemany('UPDATE files SET package=?, fileorder=? WHERE fid=?', data)
+
+ @async
+ def movePackage(self, root, order, pid, dpid):
+ self.c.execute('SELECT max(packageorder) FROM packages WHERE root=?', (dpid,))
r = self.c.fetchone()
- if not r: return None
- return PyFile(self.manager, id, * r)
+ max = (r[0] if r[0] else 0) + 1
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND root=?',
+ (order, root))
- @style.queue
- def getJob(self, occ):
- """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
+ self.c.execute('UPDATE packages SET root=?, packageorder=? WHERE pid=?', (dpid, max, pid))
- #@TODO improve this hardcoded method
- pre = "('DLC', 'LinkList', 'SerienjunkiesOrg', 'CCF', 'RSDF')" #plugins which are processed in collector
+ @async
+ def restartFile(self, fid):
+ # status -> queued
+ self.c.execute('UPDATE files SET dlstatus=3, error="" WHERE fid=?', (fid,))
- cmd = "("
- for i, item in enumerate(occ):
- if i: cmd += ", "
- cmd += "'%s'" % item
-
- cmd += ")"
+ @async
+ def restartPackage(self, pid):
+ # status -> queued
+ self.c.execute('UPDATE files SET status=3 WHERE package=?', (pid,))
- 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]
+ # TODO: multi user approach
+ @queue
+ def getJob(self, occ):
+ """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
+ cmd = "(%s)" % ", ".join(["'%s'" % x for x in occ])
+ #TODO
- @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
+ # dlstatus in online, queued | package status = ok
+ cmd = ("SELECT f.fid FROM files as f INNER JOIN packages as p ON f.package=p.pid "
+ "WHERE f.plugin NOT IN %s AND f.dlstatus IN (2,3) AND p.status=0 "
+ "ORDER BY p.packageorder ASC, f.fileorder ASC LIMIT 5") % cmd
self.c.execute(cmd) # very bad!
return [x[0] for x in self.c]
- @style.queue
+ @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)")
+ # status in finished, skipped, processing
+ self.c.execute("SELECT fid FROM files WHERE package=? AND dlstatus NOT IN (5, 6, 14) LIMIT 3", (pid,))
+ return [r[0] for r in self.c]
- @style.queue
- def restartFailed(self):
- self.c.execute("UPDATE links SET status=3,error='' WHERE status IN (6, 8, 9)")
+ @queue
+ def restartFailed(self, owner):
+ # status=queued, where status in failed, aborted, temp offline
+ self.c.execute("UPDATE files SET dlstatus=3, error='' WHERE dlstatus IN (7, 11, 12)")
- @style.queue
+ @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))
+ # TODO
+ self.c.execute(
+ "SELECT l.plugin FROM files f INNER JOIN packages as p ON f.package=p.pid AND p.folder=? WHERE f.fid!=? AND l.status=0 AND l.name=?"
+ , (folder, id, filename))
return self.c.fetchone()
- @style.queue
+ @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
+ # fstatus = missing
+ self.c.execute("DELETE FROM files WHERE status == 1")
+
+ @queue
+ def purgeAll(self): # only used for debugging
+ self.c.execute("DELETE FROM packages")
+ self.c.execute("DELETE FROM files")
+ self.c.execute("DELETE FROM collector")
+
+FileMethods.register() \ No newline at end of file
diff --git a/module/database/StatisticDatabase.py b/module/database/StatisticDatabase.py
new file mode 100644
index 000000000..10619eb5b
--- /dev/null
+++ b/module/database/StatisticDatabase.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.database import DatabaseMethods, queue, async, inner
+
+# TODO
+
+class StatisticMethods(DatabaseMethods):
+ pass
+
+
+
+StatisticMethods.register() \ No newline at end of file
diff --git a/module/database/StorageDatabase.py b/module/database/StorageDatabase.py
index 3ed29625f..ffaf51763 100644
--- a/module/database/StorageDatabase.py
+++ b/module/database/StorageDatabase.py
@@ -16,11 +16,10 @@
@author: mkaay
"""
-from module.database import style
-from module.database import DatabaseBackend
+from module.database import DatabaseBackend, queue
class StorageMethods():
- @style.queue
+ @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:
@@ -28,7 +27,7 @@ class StorageMethods():
else:
db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value))
- @style.queue
+ @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))
@@ -42,7 +41,7 @@ class StorageMethods():
d[row[0]] = row[1]
return d
- @style.queue
+ @queue
def delStorage(db, identifier, key):
db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key))
diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py
index 0c781057d..0df94e0eb 100644
--- a/module/database/UserDatabase.py
+++ b/module/database/UserDatabase.py
@@ -1,63 +1,96 @@
# -*- 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.
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
- 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 hashlib import sha1
+from string import letters, digits
+from random import choice
- @author: mkaay
-"""
+alphnum = letters+digits
-from hashlib import sha1
-import random
+from module.Api import UserData
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
+from DatabaseBackend import DatabaseMethods, queue, async
-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 {}
+def random_salt():
+ return "".join(choice(alphnum) for x in range(0,5))
- 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 {}
+class UserMethods(DatabaseMethods):
- @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)])
+ @queue
+ def addUser(self, user, password):
+ salt = random_salt()
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))
+ self.c.execute('SELECT name FROM users WHERE name=?', (user, ))
+ if self.c.fetchone() is not None:
+ self.c.execute('UPDATE users SET password=? WHERE name=?', (password, user))
else:
- c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
+ self.c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
+
+ @queue
+ def getUserData(self, name=None, uid=None):
+ qry = ('SELECT uid, name, email, role, permission, folder, traffic, dllimit, dlquota, '
+ 'hddquota, user, template FROM "users" WHERE ')
+
+ if name is not None:
+ self.c.execute(qry + "name=?", (name,))
+ r = self.c.fetchone()
+ if r:
+ return UserData(*r)
+
+ elif uid is not None:
+ self.c.execute(qry + "uid=?", (uid,))
+ r = self.c.fetchone()
+ if r:
+ return UserData(*r)
+
+ return None
+
+ @queue
+ def getAllUserData(self):
+ self.c.execute('SELECT uid, name, email, role, permission, folder, traffic, dllimit, dlquota, '
+ 'hddquota, user, template FROM "users"')
+ user = {}
+ for r in self.c:
+ user[r[0]] = UserData(*r)
+
+ return user
- @style.queue
- def changePassword(db, user, oldpw, newpw):
- db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user, ))
- r = db.c.fetchone()
+ @queue
+ def checkAuth(self, user, password):
+ self.c.execute('SELECT uid, name, email, role, permission, folder, traffic, dllimit, dlquota, '
+ 'hddquota, user, template, password FROM "users" WHERE name=?', (user, ))
+ r = self.c.fetchone()
+ if not r:
+ return None
+ salt = r[-1][:5]
+ pw = r[-1][5:]
+ h = sha1(salt + password)
+ if h.hexdigest() == pw:
+ return UserData(*r[:-1])
+ else:
+ return None
+
+ @queue #TODO
+ def changePassword(self, user, oldpw, newpw):
+ self.c.execute('SELECT rowid, name, password FROM users WHERE name=?', (user, ))
+ r = self.c.fetchone()
if not r:
return False
@@ -65,44 +98,28 @@ class UserMethods():
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)])
+ salt = random_salt()
h = sha1(salt + newpw)
password = salt + h.hexdigest()
- db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
+ self.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
return True
return False
+ @async
+ def setPermission(self, user, perms):
+ self.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user))
- @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))
+ @async
+ def setRole(self, user, role):
+ self.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
+ # TODO update methods
- @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, ))
+ @async
+ def removeUser(self, uid=None):
+ # deletes user and all associated accounts
+ self.c.execute('DELETE FROM users WHERE user=?', (uid, ))
-DatabaseBackend.registerSub(UserMethods)
+UserMethods.register()
diff --git a/module/database/__init__.py b/module/database/__init__.py
index 545789c0c..d3f97fb53 100644
--- a/module/database/__init__.py
+++ b/module/database/__init__.py
@@ -1,6 +1,8 @@
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
+from DatabaseBackend import DatabaseMethods, DatabaseBackend, queue, async, inner
-from FileDatabase import FileHandler
+from FileDatabase import FileMethods
from UserDatabase import UserMethods
-from StorageDatabase import StorageMethods \ No newline at end of file
+from StorageDatabase import StorageMethods
+from AccountDatabase import AccountMethods
+from ConfigDatabase import ConfigMethods
+from StatisticDatabase import StatisticMethods \ No newline at end of file
diff --git a/module/PyFile.py b/module/datatypes/PyFile.py
index 3dede9360..1a515493c 100644
--- a/module/PyFile.py
+++ b/module/datatypes/PyFile.py
@@ -1,122 +1,159 @@
#!/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
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
from time import sleep, time
-
from threading import RLock
+from module.Api import FileInfo, DownloadInfo, DownloadStatus
+from module.utils import format_size, format_time, 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)
+ "none": 0,
+ "offline": 1,
+ "online": 2,
+ "queued": 3,
+ "paused": 4,
+ "finished": 5,
+ "skipped": 6,
+ "failed": 7,
+ "starting": 8,
+ "waiting": 9,
+ "downloading": 10,
+ "temp. offline": 11,
+ "aborted": 12,
+ "decrypting": 13,
+ "processing": 14,
+ "custom": 15,
+ "unknown": 16,
+ }
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")
+ __slots__ = ("m", "fid", "_name", "_size", "filestatus", "media", "added", "fileorder",
+ "url", "pluginname", "hash", "status", "error", "packageid", "ownerid",
+ "lock", "plugin", "waitUntil", "active", "abort", "statusname",
+ "reconnected", "progress", "maxprogress", "pluginclass")
+
+ @staticmethod
+ def fromInfoData(m, info):
+ f = PyFile(m, info.fid, info.name, info.size, info.status, info.media, info.added, info.fileorder,
+ "", "", "", DownloadStatus.NA, "", info.package, info.owner)
+ if info.download:
+ f.url = info.download.url
+ f.pluginname = info.download.plugin
+ f.hash = info.download.hash
+ f.status = info.download.status
+ f.error = info.download.error
+
+ return f
+
+ def __init__(self, manager, fid, name, size, filestatus, media, added, fileorder,
+ url, pluginname, hash, status, error, package, owner):
- def __init__(self, manager, id, url, name, size, status, error, pluginname, package, order):
self.m = manager
-
- self.id = int(id)
+
+ self.fid = int(fid)
+ self._name = name
+ self._size = size
+ self.filestatus = filestatus
+ self.media = media
+ self.added = added
+ self.fileorder = fileorder
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.hash = hash
+ self.status = status
self.error = error
- self.order = order
+ self.ownerid = owner
+ self.packageid = package #should not be used, use package() instead
# 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
+ @property
+ def id(self):
+ self.m.core.log.debug("Deprecated attr .id, use .fid instead")
+ return self.fid
+ def setSize(self, value):
+ self._size = int(value)
# will convert all sizes to ints
size = property(lambda self: self._size, setSize)
-
+
+ def getName(self):
+ try:
+ if self.plugin.req.name:
+ return self.plugin.req.name
+ else:
+ return self._name
+ except:
+ return self._name
+
+ def setName(self, name):
+ """ Only set unicode or utf8 strings as name """
+ if type(name) == str:
+ name = name.decode("utf8")
+
+ self._name = name
+
+ name = property(getName, setName)
+
def __repr__(self):
- return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname)
+ 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.pluginclass = self.m.core.pluginManager.getPlugin(self.pluginname)
self.plugin = self.pluginclass(self)
@lock
def hasPlugin(self):
- """Thread safe way to determine this file has initialized plugin attribute
-
- :return:
- """
+ """Thread safe way to determine this file has initialized plugin attribute"""
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
+ # needs to sync so status is written to database
+ self.sync()
def setCustomStatus(self, msg, status="processing"):
self.statusname = msg
@@ -127,60 +164,36 @@ class PyFile(object):
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)
+ self.m.updateFile(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
- }
- }
+ self.m.releaseFile(self.fid)
+
+
+ def toInfoData(self):
+ return FileInfo(self.fid, self.getName(), self.packageid, self.ownerid, self.getSize(), self.filestatus,
+ self.media, self.added, self.fileorder, DownloadInfo(
+ self.url, self.pluginname, self.hash, self.status, self.getStatusName(), self.error
+ )
+ )
+
+ def getPath(self):
+ pass
+
+ def move(self, pid):
+ pass
def abortDownload(self):
"""abort pyfile if possible"""
@@ -189,19 +202,19 @@ class PyFile(object):
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()
@@ -209,62 +222,50 @@ class PyFile(object):
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)
-
+ return format_time(self.waitUntil - time())
+
def formatSize(self):
""" formats size to readable format """
- return formatSize(self.getSize())
+ return format_size(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)
-
+ return format_time(self.getETA())
+
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:
+ if self.status == DownloadStatus.Downloading:
try:
return self.plugin.req.percent
except:
return 0
else:
return self.progress
-
+
def getSize(self):
""" get size of download """
try:
@@ -274,10 +275,9 @@ class PyFile(object):
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)
+ self.m.core.eventManager.dispatchEvent("linkUpdated", self.id, self.packageid)
def setProgress(self, value):
if not value == self.progress:
diff --git a/module/datatypes/PyPackage.py b/module/datatypes/PyPackage.py
new file mode 100644
index 000000000..be2f23eea
--- /dev/null
+++ b/module/datatypes/PyPackage.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
+from time import time
+
+from module.Api import PackageInfo, PackageStatus
+from module.utils.fs import join
+
+class PyPackage:
+ """
+ Represents a package object at runtime
+ """
+
+ @staticmethod
+ def fromInfoData(m, info):
+ return PyPackage(m, info.pid, info.name, info.folder, info.root, info.owner,
+ info.site, info.comment, info.password, info.added, info.status, info.packageorder)
+
+ def __init__(self, manager, pid, name, folder, root, owner, site, comment, password, added, status, packageorder):
+ self.m = manager
+
+ self.pid = pid
+ self.name = name
+ self.folder = folder
+ self.root = root
+ self.ownerid = owner
+ self.site = site
+ self.comment = comment
+ self.password = password
+ self.added = added
+ self.status = status
+ self.packageorder = packageorder
+ self.timestamp = time()
+
+ @property
+ def id(self):
+ self.m.core.log.debug("Deprecated package attr .id, use .pid instead")
+ return self.pid
+
+ def isStale(self):
+ return self.timestamp + 30 * 60 > time()
+
+ def toInfoData(self):
+ return PackageInfo(self.pid, self.name, self.folder, self.root, self.ownerid, self.site,
+ self.comment, self.password, self.added, self.status, self.packageorder
+ )
+
+ def getChildren(self):
+ """get information about contained links"""
+ return self.m.getPackageData(self.id)["links"]
+
+ def getPath(self, name=""):
+ self.timestamp = time()
+ return join(self.m.getPackage(self.root).getPath(), self.folder, name)
+
+ 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 deleteIfEmpty(self):
+ """ True if deleted """
+ if not len(self.getChildren()):
+ self.delete()
+ return True
+ return False
+
+ def notifyChange(self):
+ self.m.core.eventManager.dispatchEvent("packageUpdated", self.id)
+
+
+class RootPackage(PyPackage):
+ def __init__(self, m, owner):
+ PyPackage.__init__(self, m, -1, "root", "", owner, -2, "", "", "", 0, PackageStatus.Ok, 0)
+
+ def getPath(self, name=""):
+ return join(self.m.core.config["general"]["download_folder"], name)
+
+ # no database operations
+ def sync(self):
+ pass
+
+ def delete(self):
+ pass
+
+ def release(self):
+ pass \ No newline at end of file
diff --git a/module/datatypes/User.py b/module/datatypes/User.py
new file mode 100644
index 000000000..d48111182
--- /dev/null
+++ b/module/datatypes/User.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
+
+from module.Api import UserData, Permission, Role, has_permission
+
+#TODO: activate user
+#noinspection PyUnresolvedReferences
+class User(UserData):
+
+ @staticmethod
+ def fromUserData(api, user):
+ return User(api, user.uid, user.name, user.email, user.role, user.permission, user.folder,
+ user.traffic, user.dllimit, user.dlquota, user.hddquota, user.user, user.templateName)
+
+ def __init__(self, api, *args):
+ UserData.__init__(self, *args)
+ self.api = api
+
+
+ def toUserData(self):
+ return UserData()
+
+ def hasPermission(self, perms):
+ """ Accepts permission bit or name """
+
+ if isinstance(perms, basestring) and hasattr(Permission, perms):
+ perms = getattr(Role, perms)
+
+ return has_permission(self.permission, perms)
+
+ def hasRole(self, role):
+ if isinstance(role, basestring) and hasattr(Role, role):
+ role = getattr(Role, role)
+
+ return self.role == role
+
+ def isAdmin(self):
+ return self.hasRole(Role.Admin)
+
+ @property
+ def handle(self):
+ """ Internal user handle used for most operations (secondary share handle with primary user) """
+ if self.hasRole(Role.Admin):
+ return None
+ return self.user if self.user else self.uid
diff --git a/module/plugins/captcha/__init__.py b/module/datatypes/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/captcha/__init__.py
+++ b/module/datatypes/__init__.py
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/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/interaction/EventManager.py b/module/interaction/EventManager.py
new file mode 100644
index 000000000..976a92413
--- /dev/null
+++ b/module/interaction/EventManager.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+from threading import Lock
+from traceback import print_exc
+from time import time
+
+from module.utils import lock
+
+class EventManager:
+ """
+ Handles all event-related tasks, also stores an event queue for clients, so they can retrieve them later.
+
+ **Known Events:**
+ Most addon methods exist as events. These are some additional known events.
+
+ ===================== ================ ===========================================================
+ Name Arguments Description
+ ===================== ================ ===========================================================
+ metaEvent eventName, *args Called for every event, with eventName and original args
+ downloadPreparing fid A download was just queued and will be prepared now.
+ downloadStarts fid A plugin will immediately start the download afterwards.
+ linksAdded links, pid Someone just added links, you are able to modify these links.
+ allDownloadsProcessed All links were handled, pyLoad would idle afterwards.
+ allDownloadsFinished All downloads in the queue are finished.
+ unrarFinished folder, fname An Unrar job finished
+ configChanged sec, opt, value The config was changed.
+ ===================== ================ ===========================================================
+
+ | Notes:
+ | allDownloadsProcessed is *always* called before allDownloadsFinished.
+ | configChanged is *always* called before pluginConfigChanged.
+ """
+
+ CLIENT_EVENTS = ("packageUpdated", "packageInserted", "linkUpdated", "packageDeleted")
+
+ def __init__(self, core):
+ self.core = core
+ self.log = core.log
+
+ # uuid : list of events
+ self.clients = {}
+ self.events = {"metaEvent": []}
+
+ self.lock = Lock()
+
+ def getEvents(self, uuid):
+ """ Get accumulated events for uuid since last call, this also registers a new client """
+ if uuid not in self.clients:
+ self.clients[uuid] = Client()
+ return self.clients[uuid].get()
+
+ def addEvent(self, event, func):
+ """Adds an event listener for event name"""
+ if event in self.events:
+ if func in self.events[event]:
+ self.log.debug("Function already registered %s" % func)
+ else:
+ 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"""
+ for f in self.events["metaEvent"]:
+ try:
+ f(event, *args)
+ except Exception, e:
+ self.log.warning("Error calling event handler %s: %s, %s, %s"
+ % ("metaEvent", f, args, str(e)))
+ if self.core.debug:
+ print_exc()
+
+ 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:
+ print_exc()
+
+ self.updateClients(event, args)
+
+ @lock
+ def updateClients(self, event, args):
+ # append to client event queue
+ if event in self.CLIENT_EVENTS:
+ for uuid, client in self.clients.items():
+ if client.delete():
+ del self.clients[uuid]
+ else:
+ client.append(event, args)
+
+ def removeFromEvents(self, func):
+ """ Removes func from all known events """
+ for name, events in self.events.iteritems():
+ if func in events:
+ events.remove(func)
+
+
+
+class Client:
+
+ # delete clients after this time
+ TIMEOUT = 60 * 60
+ # max events, if this value is reached you should assume that older events were dropped
+ MAX = 30
+
+ def __init__(self):
+ self.lastActive = time()
+ self.events = []
+
+ def delete(self):
+ return self.lastActive + self.TIMEOUT < time()
+
+ def append(self, event, args):
+ ev = (event, args)
+ if ev not in self.events:
+ self.events.insert(0, ev)
+
+ del self.events[self.MAX:]
+
+
+ def get(self):
+ self.lastActive = time()
+
+ events = self.events
+ self.events = []
+
+ return [(ev, [str(x) for x in args]) for ev, args in events] \ No newline at end of file
diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py
new file mode 100644
index 000000000..1d26b1665
--- /dev/null
+++ b/module/interaction/InteractionManager.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+from threading import Lock
+from time import time
+
+from new_collections import OrderedDict
+
+from module.utils import lock, bits_set, to_list
+from module.Api import Input, Output
+
+from InteractionTask import InteractionTask
+
+class InteractionManager:
+ """
+ Class that gives ability to interact with the user.
+ Arbitrary tasks with predefined output and input types can be set off.
+ Asynchronous callbacks and default values keep the ability to fallback if no user is present.
+ """
+
+ # number of seconds a client is classified as active
+ CLIENT_THRESHOLD = 60
+
+ def __init__(self, core):
+ self.lock = Lock()
+ self.core = core
+ self.tasks = OrderedDict() #task store, for outgoing tasks only
+ self.notifications = [] #list of notifications
+
+ self.last_clients = {
+ Output.Notification : 0,
+ Output.Captcha : 0,
+ Output.Query : 0,
+ }
+
+ self.ids = 0 #only for internal purpose
+
+
+ def isClientConnected(self, mode=Output.All):
+ if mode == Output.All:
+ return max(self.last_clients.values()) + self.CLIENT_THRESHOLD <= time()
+ else:
+ self.last_clients.get(mode, 0) + self.CLIENT_THRESHOLD <= time()
+
+ def updateClient(self, mode):
+ t = time()
+ for output in self.last_clients:
+ if bits_set(output, mode):
+ self.last_clients[output] = t
+
+ @lock
+ def work(self):
+ # old notifications will be removed
+ for n in [x for x in self.notifications if x.timedOut()]:
+ self.notifications.remove(n)
+
+ # store at most 100 notifications
+ del self.notifications[50:]
+
+
+ @lock
+ def createNotification(self, title, content, desc="", plugin=""):
+ """ Creates and queues a new Notification
+
+ :param title: short title
+ :param content: text content
+ :param desc: short form of the notification
+ :param plugin: plugin name
+ :return: :class:`InteractionTask`
+ """
+ task = InteractionTask(self.ids, Input.Text, [content], Output.Notification, "", title, desc, plugin)
+ self.ids += 1
+ self.notifications.insert(0, task)
+ self.handleTask(task)
+ return task
+
+ @lock
+ def newQueryTask(self, input, data, desc, default="", plugin=""):
+ task = InteractionTask(self.ids, input, to_list(data), Output.Query, default, _("Query"), desc, plugin)
+ self.ids += 1
+ return task
+
+ @lock
+ def newCaptchaTask(self, img, format, filename, plugin="", input=Input.Text):
+ #todo: title desc plugin
+ task = InteractionTask(self.ids, input, [img, format, filename],Output.Captcha,
+ "", _("Captcha request"), _("Please solve the captcha."), plugin)
+ self.ids += 1
+ return task
+
+ @lock
+ def removeTask(self, task):
+ if task.iid in self.tasks:
+ del self.tasks[task.iid]
+
+ @lock
+ def getTask(self, mode=Output.All):
+ self.updateClient(mode)
+
+ for task in self.tasks.itervalues():
+ if mode == Output.All or bits_set(task.output, mode):
+ return task
+
+ @lock
+ def getNotifications(self):
+ """retrieves notifications, old ones are only deleted after a while\
+ client has to make sure itself to dont display it twice"""
+ for n in self.notifications:
+ n.setWaiting(self.CLIENT_THRESHOLD * 5, True)
+ #store notification for shorter period, lock the timeout
+
+ return self.notifications
+
+ def isTaskWaiting(self, mode=Output.All):
+ return self.getTask(mode) is not None
+
+ @lock
+ def getTaskByID(self, iid):
+ if iid in self.tasks:
+ task = self.tasks[iid]
+ del self.tasks[iid]
+ return task
+
+ def handleTask(self, task):
+ cli = self.isClientConnected(task.output)
+
+ if cli: #client connected -> should handle the task
+ task.setWaiting(self.CLIENT_THRESHOLD) # wait for response
+
+ if task.output == Output.Notification:
+ task.setWaiting(60 * 60 * 30) # notifications are valid for 30h
+
+ for plugin in self.core.addonManager.activePlugins():
+ try:
+ plugin.newInteractionTask(task)
+ except:
+ self.core.print_exc()
+
+ if task.output != Output.Notification:
+ self.tasks[task.iid] = task
+
+
+if __name__ == "__main__":
+
+ it = InteractionTask() \ No newline at end of file
diff --git a/module/interaction/InteractionTask.py b/module/interaction/InteractionTask.py
new file mode 100644
index 000000000..b372321b0
--- /dev/null
+++ b/module/interaction/InteractionTask.py
@@ -0,0 +1,81 @@
+# -*- 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 module.Api import InteractionTask as BaseInteractionTask
+from module.Api import Input, Output
+
+#noinspection PyUnresolvedReferences
+class InteractionTask(BaseInteractionTask):
+ """
+ General Interaction Task extends ITask defined by thrift with additional fields and methods.
+ """
+ #: Plugins can put needed data here
+ storage = None
+ #: Timestamp when task expires
+ wait_until = 0
+ #: The received result
+ result = None
+ #: List of registered handles
+ handler = None
+ #: Error Message
+ error = None
+ #: Timeout locked
+ locked = False
+
+ def __init__(self, *args, **kwargs):
+ BaseInteractionTask.__init__(self, *args, **kwargs)
+
+ # additional internal attributes
+ self.storage = {}
+ self.handler = []
+ self.wait_until = 0
+
+ def convertResult(self, value):
+ #TODO: convert based on input/output
+ return value
+
+ def getResult(self):
+ return self.result
+
+ def setResult(self, value):
+ self.result = self.convertResult(value)
+
+ def setWaiting(self, sec, lock=False):
+ if not self.locked:
+ self.wait_until = max(time() + sec, self.wait_until)
+ if lock: self.locked = True
+
+ def isWaiting(self):
+ if self.result or self.error or time() > self.waitUntil:
+ return False
+
+ return True
+
+ def timedOut(self):
+ return time() > self.wait_until > 0
+
+ def correct(self):
+ [x.taskCorrect(self) for x in self.handler]
+
+ def invalid(self):
+ [x.taskInvalid(self) for x in self.handler]
+
+ def __str__(self):
+ return "<InteractionTask '%s'>" % self.id \ No newline at end of file
diff --git a/module/interaction/__init__.py b/module/interaction/__init__.py
new file mode 100644
index 000000000..de6d13128
--- /dev/null
+++ b/module/interaction/__init__.py
@@ -0,0 +1,2 @@
+__author__ = 'christian'
+ \ No newline at end of file
diff --git a/module/forwarder.py b/module/lib/forwarder.py
index eacb33c2b..eacb33c2b 100644
--- a/module/forwarder.py
+++ b/module/lib/forwarder.py
diff --git a/module/lib/hg_tool.py b/module/lib/hg_tool.py
new file mode 100644
index 000000000..cd97833df
--- /dev/null
+++ b/module/lib/hg_tool.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import re
+from subprocess import Popen, PIPE
+from time import time, gmtime, strftime
+
+aliases = {"zoidber": "zoidberg", "zoidberg10": "zoidberg", "webmaster": "dhmh", "mast3rranan": "ranan",
+ "ranan2": "ranan"}
+exclude = ["locale/*", "module/lib/*"]
+date_format = "%Y-%m-%d"
+line_re = re.compile(r" (\d+) \**", re.I)
+
+def add_exclude_flags(args):
+ for dir in exclude:
+ args.extend(["-X", dir])
+
+# remove small percentages
+def wipe(data, perc=1):
+ s = (sum(data.values()) * perc) / 100
+ for k, v in data.items():
+ if v < s: del data[k]
+
+ return data
+
+# remove aliases
+def de_alias(data):
+ for k, v in aliases.iteritems():
+ if k not in data: continue
+ alias = aliases[k]
+
+ if alias in data: data[alias] += data[k]
+ else: data[alias] = data[k]
+
+ del data[k]
+
+ return data
+
+
+def output(data):
+ s = float(sum(data.values()))
+ print "Total Lines: %d" % s
+ for k, v in data.iteritems():
+ print "%15s: %.1f%% | %d" % (k, (v * 100) / s, v)
+ print
+
+
+def file_list():
+ args = ["hg", "status", "-A"]
+ add_exclude_flags(args)
+ p = Popen(args, stdout=PIPE)
+ out, err = p.communicate()
+ return [x.split()[1] for x in out.splitlines() if x.split()[0] in "CMA"]
+
+
+def hg_annotate(path):
+ args = ["hg", "annotate", "-u", path]
+ p = Popen(args, stdout=PIPE)
+ out, err = p.communicate()
+
+ data = {}
+
+ for line in out.splitlines():
+ author, non, line = line.partition(":")
+
+ # probably binary file
+ if author == path: return {}
+
+ author = author.strip().lower()
+ if not line.strip(): continue # don't count blank lines
+
+ if author in data: data[author] += 1
+ else: data[author] = 1
+
+ return de_alias(data)
+
+
+def hg_churn(days=None):
+ args = ["hg", "churn"]
+ if days:
+ args.append("-d")
+ t = time() - 60 * 60 * 24 * days
+ args.append("%s to %s" % (strftime(date_format, gmtime(t)), strftime(date_format)))
+
+ add_exclude_flags(args)
+ p = Popen(args, stdout=PIPE)
+ out, err = p.communicate()
+
+ data = {}
+
+ for line in out.splitlines():
+ m = line_re.search(line)
+ author = line.split()[0]
+ lines = int(m.group(1))
+
+ if "@" in author:
+ author, n, email = author.partition("@")
+
+ author = author.strip().lower()
+
+ if author in data: data[author] += lines
+ else: data[author] = lines
+
+ return de_alias(data)
+
+
+def complete_annotate():
+ files = file_list()
+ data = {}
+ for f in files:
+ tmp = hg_annotate(f)
+ for k, v in tmp.iteritems():
+ if k in data: data[k] += v
+ else: data[k] = v
+
+ return data
+
+
+if __name__ == "__main__":
+ for d in (30, 90, 180):
+ c = wipe(hg_churn(d))
+ print "Changes in %d days:" % d
+ output(c)
+
+ c = wipe(hg_churn())
+ print "Total changes:"
+ output(c)
+
+ print "Current source code version:"
+ data = wipe(complete_annotate())
+ output(data)
+
+
diff --git a/module/lib/new_collections.py b/module/lib/new_collections.py
new file mode 100644
index 000000000..12d05b4b9
--- /dev/null
+++ b/module/lib/new_collections.py
@@ -0,0 +1,375 @@
+## {{{ http://code.activestate.com/recipes/576693/ (r9)
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+
+try:
+ from thread import get_ident as _get_ident
+except ImportError:
+ from dummy_thread import get_ident as _get_ident
+
+try:
+ from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+ pass
+
+
+class OrderedDict(dict):
+ 'Dictionary that remembers insertion order'
+ # An inherited dict maps keys to values.
+ # The inherited dict provides __getitem__, __len__, __contains__, and get.
+ # The remaining methods are order-aware.
+ # Big-O running times for all methods are the same as for regular dictionaries.
+
+ # The internal self.__map dictionary maps keys to links in a doubly linked list.
+ # The circular doubly linked list starts and ends with a sentinel element.
+ # The sentinel element never gets deleted (this simplifies the algorithm).
+ # Each link is stored as a list of length three: [PREV, NEXT, KEY].
+
+ def __init__(self, *args, **kwds):
+ '''Initialize an ordered dictionary. Signature is the same as for
+ regular dictionaries, but keyword arguments are not recommended
+ because their insertion order is arbitrary.
+
+ '''
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__root
+ except AttributeError:
+ self.__root = root = [] # sentinel node
+ root[:] = [root, root, None]
+ self.__map = {}
+ self.__update(*args, **kwds)
+
+ def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+ 'od.__setitem__(i, y) <==> od[i]=y'
+ # Setting a new item creates a new link which goes at the end of the linked
+ # list, and the inherited dictionary is updated with the new key/value pair.
+ if key not in self:
+ root = self.__root
+ last = root[0]
+ last[1] = root[0] = self.__map[key] = [last, root, key]
+ dict_setitem(self, key, value)
+
+ def __delitem__(self, key, dict_delitem=dict.__delitem__):
+ 'od.__delitem__(y) <==> del od[y]'
+ # Deleting an existing item uses self.__map to find the link which is
+ # then removed by updating the links in the predecessor and successor nodes.
+ dict_delitem(self, key)
+ link_prev, link_next, key = self.__map.pop(key)
+ link_prev[1] = link_next
+ link_next[0] = link_prev
+
+ def __iter__(self):
+ 'od.__iter__() <==> iter(od)'
+ root = self.__root
+ curr = root[1]
+ while curr is not root:
+ yield curr[2]
+ curr = curr[1]
+
+ def __reversed__(self):
+ 'od.__reversed__() <==> reversed(od)'
+ root = self.__root
+ curr = root[0]
+ while curr is not root:
+ yield curr[2]
+ curr = curr[0]
+
+ def clear(self):
+ 'od.clear() -> None. Remove all items from od.'
+ try:
+ for node in self.__map.itervalues():
+ del node[:]
+ root = self.__root
+ root[:] = [root, root, None]
+ self.__map.clear()
+ except AttributeError:
+ pass
+ dict.clear(self)
+
+ def popitem(self, last=True):
+ '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+ '''
+ if not self:
+ raise KeyError('dictionary is empty')
+ root = self.__root
+ if last:
+ link = root[0]
+ link_prev = link[0]
+ link_prev[1] = root
+ root[0] = link_prev
+ else:
+ link = root[1]
+ link_next = link[1]
+ root[1] = link_next
+ link_next[0] = root
+ key = link[2]
+ del self.__map[key]
+ value = dict.pop(self, key)
+ return key, value
+
+ # -- the following methods do not depend on the internal structure --
+
+ def keys(self):
+ 'od.keys() -> list of keys in od'
+ return list(self)
+
+ def values(self):
+ 'od.values() -> list of values in od'
+ return [self[key] for key in self]
+
+ def items(self):
+ 'od.items() -> list of (key, value) pairs in od'
+ return [(key, self[key]) for key in self]
+
+ def iterkeys(self):
+ 'od.iterkeys() -> an iterator over the keys in od'
+ return iter(self)
+
+ def itervalues(self):
+ 'od.itervalues -> an iterator over the values in od'
+ for k in self:
+ yield self[k]
+
+ def iteritems(self):
+ 'od.iteritems -> an iterator over the (key, value) items in od'
+ for k in self:
+ yield (k, self[k])
+
+ def update(*args, **kwds):
+ '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
+
+ If E is a dict instance, does: for k in E: od[k] = E[k]
+ If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
+ Or if E is an iterable of items, does: for k, v in E: od[k] = v
+ In either case, this is followed by: for k, v in F.items(): od[k] = v
+
+ '''
+ if len(args) > 2:
+ raise TypeError('update() takes at most 2 positional '
+ 'arguments (%d given)' % (len(args),))
+ elif not args:
+ raise TypeError('update() takes at least 1 argument (0 given)')
+ self = args[0]
+ # Make progressively weaker assumptions about "other"
+ other = ()
+ if len(args) == 2:
+ other = args[1]
+ if isinstance(other, dict):
+ for key in other:
+ self[key] = other[key]
+ elif hasattr(other, 'keys'):
+ for key in other.keys():
+ self[key] = other[key]
+ else:
+ for key, value in other:
+ self[key] = value
+ for key, value in kwds.items():
+ self[key] = value
+
+ __update = update # let subclasses override update without breaking __init__
+
+ __marker = object()
+
+ def pop(self, key, default=__marker):
+ '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+ If key is not found, d is returned if given, otherwise KeyError is raised.
+
+ '''
+ if key in self:
+ result = self[key]
+ del self[key]
+ return result
+ if default is self.__marker:
+ raise KeyError(key)
+ return default
+
+ def setdefault(self, key, default=None):
+ 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+ if key in self:
+ return self[key]
+ self[key] = default
+ return default
+
+ def __repr__(self, _repr_running={}):
+ 'od.__repr__() <==> repr(od)'
+ call_key = id(self), _get_ident()
+ if call_key in _repr_running:
+ return '...'
+ _repr_running[call_key] = 1
+ try:
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, self.items())
+ finally:
+ del _repr_running[call_key]
+
+ def __reduce__(self):
+ 'Return state information for pickling'
+ items = [[k, self[k]] for k in self]
+ inst_dict = vars(self).copy()
+ for k in vars(OrderedDict()):
+ inst_dict.pop(k, None)
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def copy(self):
+ 'od.copy() -> a shallow copy of od'
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+ and values equal to v (which defaults to None).
+
+ '''
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
+
+ def __eq__(self, other):
+ '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
+ while comparison to a regular mapping is order-insensitive.
+
+ '''
+ if isinstance(other, OrderedDict):
+ return len(self)==len(other) and self.items() == other.items()
+ return dict.__eq__(self, other)
+
+ def __ne__(self, other):
+ return not self == other
+
+ # -- the following methods are only used in Python 2.7 --
+
+ def viewkeys(self):
+ "od.viewkeys() -> a set-like object providing a view on od's keys"
+ return KeysView(self)
+
+ def viewvalues(self):
+ "od.viewvalues() -> an object providing a view on od's values"
+ return ValuesView(self)
+
+ def viewitems(self):
+ "od.viewitems() -> a set-like object providing a view on od's items"
+ return ItemsView(self)
+## end of http://code.activestate.com/recipes/576693/ }}}
+
+## {{{ http://code.activestate.com/recipes/500261/ (r15)
+from operator import itemgetter as _itemgetter
+from keyword import iskeyword as _iskeyword
+import sys as _sys
+
+def namedtuple(typename, field_names, verbose=False, rename=False):
+ """Returns a new subclass of tuple with named fields.
+
+ >>> Point = namedtuple('Point', 'x y')
+ >>> Point.__doc__ # docstring for the new class
+ 'Point(x, y)'
+ >>> p = Point(11, y=22) # instantiate with positional args or keywords
+ >>> p[0] + p[1] # indexable like a plain tuple
+ 33
+ >>> x, y = p # unpack like a regular tuple
+ >>> x, y
+ (11, 22)
+ >>> p.x + p.y # fields also accessable by name
+ 33
+ >>> d = p._asdict() # convert to a dictionary
+ >>> d['x']
+ 11
+ >>> Point(**d) # convert from a dictionary
+ Point(x=11, y=22)
+ >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
+ Point(x=100, y=22)
+
+ """
+
+ # Parse and validate the field names. Validation serves two purposes,
+ # generating informative error messages and preventing template injection attacks.
+ if isinstance(field_names, basestring):
+ field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
+ field_names = tuple(map(str, field_names))
+ if rename:
+ names = list(field_names)
+ seen = set()
+ for i, name in enumerate(names):
+ if (not min(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
+ or not name or name[0].isdigit() or name.startswith('_')
+ or name in seen):
+ names[i] = '_%d' % i
+ seen.add(name)
+ field_names = tuple(names)
+ for name in (typename,) + field_names:
+ if not min(c.isalnum() or c=='_' for c in name):
+ raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
+ if _iskeyword(name):
+ raise ValueError('Type names and field names cannot be a keyword: %r' % name)
+ if name[0].isdigit():
+ raise ValueError('Type names and field names cannot start with a number: %r' % name)
+ seen_names = set()
+ for name in field_names:
+ if name.startswith('_') and not rename:
+ raise ValueError('Field names cannot start with an underscore: %r' % name)
+ if name in seen_names:
+ raise ValueError('Encountered duplicate field name: %r' % name)
+ seen_names.add(name)
+
+ # Create and fill-in the class template
+ numfields = len(field_names)
+ argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
+ reprtxt = ', '.join('%s=%%r' % name for name in field_names)
+ template = '''class %(typename)s(tuple):
+ '%(typename)s(%(argtxt)s)' \n
+ __slots__ = () \n
+ _fields = %(field_names)r \n
+ def __new__(_cls, %(argtxt)s):
+ return _tuple.__new__(_cls, (%(argtxt)s)) \n
+ @classmethod
+ def _make(cls, iterable, new=tuple.__new__, len=len):
+ 'Make a new %(typename)s object from a sequence or iterable'
+ result = new(cls, iterable)
+ if len(result) != %(numfields)d:
+ raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
+ return result \n
+ def __repr__(self):
+ return '%(typename)s(%(reprtxt)s)' %% self \n
+ def _asdict(self):
+ 'Return a new dict which maps field names to their values'
+ return dict(zip(self._fields, self)) \n
+ def _replace(_self, **kwds):
+ 'Return a new %(typename)s object replacing specified fields with new values'
+ result = _self._make(map(kwds.pop, %(field_names)r, _self))
+ if kwds:
+ raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
+ return result \n
+ def __getnewargs__(self):
+ return tuple(self) \n\n''' % locals()
+ for i, name in enumerate(field_names):
+ template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
+ if verbose:
+ print template
+
+ # Execute the template string in a temporary namespace
+ namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
+ _property=property, _tuple=tuple)
+ try:
+ exec template in namespace
+ except SyntaxError, e:
+ raise SyntaxError(e.message + ':\n' + template)
+ result = namespace[typename]
+
+ # For pickling to work, the __module__ variable needs to be set to the frame
+ # where the named tuple is created. Bypass this step in enviroments where
+ # sys._getframe is not defined (Jython for example) or sys._getframe is not
+ # defined for arguments greater than 0 (IronPython).
+ try:
+ result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+
+ return result
+## end of http://code.activestate.com/recipes/500261/ }}}
diff --git a/module/network/Browser.py b/module/network/Browser.py
index d68a23687..9cf6c2f30 100644
--- a/module/network/Browser.py
+++ b/module/network/Browser.py
@@ -16,7 +16,7 @@ class Browser(object):
self.options = options #holds pycurl options
self.bucket = bucket
- self.cj = None # needs to be setted later
+ self.cj = None # needs to be set later
self._size = 0
self.renewHTTPRequest()
@@ -55,6 +55,13 @@ class Browser(object):
return 0
@property
+ def name(self):
+ if self.dl:
+ return self.dl.name
+ else:
+ return ""
+
+ @property
def arrived(self):
if self.dl:
return self.dl.arrived
diff --git a/module/network/Bucket.py b/module/network/Bucket.py
index 69da277ae..db67faa4a 100644
--- a/module/network/Bucket.py
+++ b/module/network/Bucket.py
@@ -20,15 +20,18 @@
from time import time
from threading import Lock
+# 10kb minimum rate
+MIN_RATE = 10240
+
class Bucket:
def __init__(self):
- self.rate = 0
+ self.rate = 0 # bytes per second, maximum targeted throughput
self.tokens = 0
self.timestamp = time()
self.lock = Lock()
def __nonzero__(self):
- return False if self.rate < 10240 else True
+ return False if self.rate < MIN_RATE else True
def setRate(self, rate):
self.lock.acquire()
@@ -36,8 +39,8 @@ class Bucket:
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
+ """ 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()
@@ -47,7 +50,6 @@ class Bucket:
time = -self.tokens/float(self.rate)
else:
time = 0
-
self.lock.release()
return time
diff --git a/module/network/CookieJar.py b/module/network/CookieJar.py
index c05812334..a020d6f9e 100644
--- a/module/network/CookieJar.py
+++ b/module/network/CookieJar.py
@@ -20,10 +20,12 @@
from time import time
class CookieJar():
- def __init__(self, pluginname, account=None):
+ def __init__(self, pluginname):
self.cookies = {}
- self.plugin = pluginname
- self.account = account
+ self.pluginname = pluginname
+
+ def __repr__(self):
+ return ("<CookieJar plugin=%s>\n\t" % self.pluginname) + "\n\t".join(self.cookies.values())
def addCookies(self, clist):
for c in clist:
@@ -33,18 +35,18 @@ class CookieJar():
def getCookies(self):
return self.cookies.values()
- def parseCookie(self, name):
+ def getCookie(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=None):
+ if not exp: exp = time() + 3600 * 24 * 180
- 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)
+ # dot makes it valid on all subdomains
+ s = ".%s TRUE %s FALSE %s %s %s" % (domain.strip("."), path, exp, name, value)
self.cookies[name] = s
def clear(self):
- self.cookies = {}
+ self.cookies = {} \ No newline at end of file
diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py
index b637aef32..d17177ee7 100644
--- a/module/network/HTTPChunk.py
+++ b/module/network/HTTPChunk.py
@@ -20,10 +20,13 @@ 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 module.utils import remove_chars
+from module.utils.fs import fs_encode
+
from HTTPRequest import HTTPRequest
class WrongFormat(Exception):
@@ -204,7 +207,7 @@ class HTTPChunk(HTTPRequest):
def writeHeader(self, buf):
self.header += buf
- #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
+ #@TODO forward headers?, this is possibly unneeded, 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()
@@ -233,8 +236,8 @@ class HTTPChunk(HTTPRequest):
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
+ # otherwise reduce sleep time percentile (values are based on tests)
+ # So in general cpu time is saved without reducing bandwidth too much
if size < self.lastSize:
self.sleep += 0.002
@@ -250,17 +253,19 @@ class HTTPChunk(HTTPRequest):
def parseHeader(self):
- """parse data from recieved header"""
+ """parse data from received 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 "content-disposition" in line:
+
+ m = search("filename(?P<type>=|\*=(?P<enc>.+)'')(?P<name>.*)", line)
+ if m:
+ name = remove_chars(m.groupdict()['name'], "\"';").strip()
+ self.p._name = name
+ self.log.debug("Content-Disposition: %s" % name)
if not self.resume and line.startswith("content-length"):
self.p.size = int(line.split(":")[1])
diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py
index fe8075539..c6d2e1547 100644
--- a/module/network/HTTPDownload.py
+++ b/module/network/HTTPDownload.py
@@ -17,9 +17,9 @@
@author: RaNaN
"""
-from os import remove, fsync
+from os import remove
from os.path import dirname
-from time import sleep, time
+from time import time
from shutil import move
from logging import getLogger
@@ -28,11 +28,13 @@ 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
+from module.plugins.Base import Abort
+from module.utils.fs import save_join, fs_encode
+
+# TODO: save content-disposition for resuming
class HTTPDownload():
- """ loads a url http + ftp """
+ """ loads an url, http + ftp supported """
def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
options={}, progressNotify=None, disposition=False):
@@ -49,7 +51,7 @@ class HTTPDownload():
self.abort = False
self.size = 0
- self.nameDisposition = None #will be parsed from content disposition
+ self._name = ""# will be parsed from content disposition
self.chunks = []
@@ -87,6 +89,10 @@ class HTTPDownload():
if not self.size: return 0
return (self.arrived * 100) / self.size
+ @property
+ def name(self):
+ return self._name if self.disposition else ""
+
def _copyChunks(self):
init = fs_encode(self.info.getChunkName(0)) #initial chunk name
@@ -113,8 +119,8 @@ class HTTPDownload():
remove(fname) #remove chunk
fo.close()
- if self.nameDisposition and self.disposition:
- self.filename = save_join(dirname(self.filename), self.nameDisposition)
+ if self.name:
+ self.filename = save_join(dirname(self.filename), self.name)
move(init, fs_encode(self.filename))
self.info.remove() #remove info file
@@ -144,8 +150,7 @@ class HTTPDownload():
finally:
self.close()
- if self.nameDisposition and self.disposition: return self.nameDisposition
- return None
+ return self.name
def _download(self, chunks, resume):
if not resume:
@@ -169,7 +174,7 @@ class HTTPDownload():
while 1:
#need to create chunks
- if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk
+ if not chunksCreated and self.chunkSupport and self.size: #will be set later by first chunk
if not resume:
self.info.setSize(self.size)
@@ -188,7 +193,7 @@ class HTTPDownload():
self.chunks.append(c)
self.m.add_handle(handle)
else:
- #close immediatly
+ #close immediately
self.log.debug("Invalid curl handle -> closed")
c.close()
@@ -286,7 +291,7 @@ class HTTPDownload():
if self.abort:
raise Abort()
- #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize
+ #sleep(0.003) #suppress busy waiting - limits dl speed to (1 / x) * buffersize
self.m.select(1)
for chunk in self.chunks:
diff --git a/module/network/HTTPRequest.py b/module/network/HTTPRequest.py
index 4747d937f..4b45e10a2 100644
--- a/module/network/HTTPRequest.py
+++ b/module/network/HTTPRequest.py
@@ -25,21 +25,21 @@ from httplib import responses
from logging import getLogger
from cStringIO import StringIO
-from module.plugins.Plugin import Abort
+from module.plugins.Base import Abort
def myquote(url):
- return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
+ return quote(url.encode('utf8') 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()))
+ return urlencode(dict((x.encode('utf8') if isinstance(x, unicode) else x, \
+ y.encode('utf8') 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)]))
+ Exception.__init__(self, "Bad server response: %s %s" % (code, responses.get(int(code), "Unknown Header")))
self.code = code
self.content = content
@@ -62,6 +62,7 @@ class HTTPRequest():
self.initHandle()
self.setInterface(options)
+ self.setOptions(options)
self.c.setopt(pycurl.WRITEFUNCTION, self.write)
self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
@@ -79,7 +80,8 @@ class HTTPRequest():
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)
+ # Interval for low speed, detects connection loss, but can abort dl if hoster stalls the download
+ self.c.setopt(pycurl.LOW_SPEED_TIME, 45)
self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
#self.c.setopt(pycurl.VERBOSE, 1)
@@ -127,6 +129,11 @@ class HTTPRequest():
if "timeout" in options:
self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"])
+ def setOptions(self, options):
+ """ Sets same options as available in pycurl """
+ for k, v in options.iteritems():
+ if hasattr(pycurl, k):
+ self.c.setopt(getattr(pycurl, k), v)
def addCookies(self):
""" put cookies from curl handle to cj """
@@ -193,11 +200,20 @@ class HTTPRequest():
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)
+ # overwrite HEAD request, we want a common request type
+ if post:
+ self.c.setopt(pycurl.CUSTOMREQUEST, "POST")
+ else:
+ self.c.setopt(pycurl.CUSTOMREQUEST, "GET")
+
+ try:
+ self.c.perform()
+ rep = self.header
+ finally:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.NOBODY, 0)
+ self.c.unsetopt(pycurl.CUSTOMREQUEST)
else:
self.c.perform()
@@ -262,7 +278,7 @@ class HTTPRequest():
#TODO: html_unescape as default
except LookupError:
- self.log.debug("No Decoder foung for %s" % encoding)
+ self.log.debug("No Decoder found for %s" % encoding)
except Exception:
self.log.debug("Error when decoding string from %s." % encoding)
diff --git a/module/network/RequestFactory.py b/module/network/RequestFactory.py
index 5b1528281..932184678 100644
--- a/module/network/RequestFactory.py
+++ b/module/network/RequestFactory.py
@@ -24,38 +24,29 @@ 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 = {}
+ @property
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())
-
+ def getRequest(self, pluginName, cj=None):
req = Browser(self.bucket, self.getOptions())
- if account:
- cj = self.getCookieJar(pluginName, account)
+ if cj:
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 ! """
+ """ returns a http request, don't forget to close it ! """
options = self.getOptions()
options.update(kwargs) # submit kwargs as additional options
return HTTPRequest(CookieJar(None), options)
@@ -67,16 +58,12 @@ class RequestFactory():
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)]
+ return rep
- cj = CookieJar(pluginName, account)
- self.cookiejars[(pluginName, account)] = cj
- return cj
+ def openCookieJar(self, pluginname):
+ """Create new CookieJar"""
+ return CookieJar(pluginname)
def getProxies(self):
""" returns a proxy list for the request classes """
@@ -106,7 +93,7 @@ class RequestFactory():
def getOptions(self):
"""returns options needed for pycurl"""
- return {"interface": self.iface(),
+ return {"interface": self.iface,
"proxies": self.getProxies(),
"ipv6": self.core.config["download"]["ipv6"]}
diff --git a/module/network/XDCCRequest.py b/module/network/XDCCRequest.py
index f03798c17..7a1a98cb5 100644
--- a/module/network/XDCCRequest.py
+++ b/module/network/XDCCRequest.py
@@ -119,7 +119,7 @@ class XDCCRequest():
fh.write(data)
- # acknowledge data by sending number of recceived bytes
+ # acknowledge data by sending number of received bytes
dccsock.send(struct.pack('!I', self.recv))
dccsock.close()
diff --git a/module/plugins/Account.py b/module/plugins/Account.py
index c147404e0..7c24298e7 100644
--- a/module/plugins/Account.py
+++ b/module/plugins/Account.py
@@ -1,292 +1,288 @@
# -*- 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
+from module.utils import compare_time, format_size, parseFileSize, lock, from_string
+from module.Api import AccountInfo
+from module.network.CookieJar import CookieJar
+
+from Base import Base
class WrongPassword(Exception):
pass
-
-class Account(Base):
+#noinspection PyUnresolvedReferences
+class Account(Base, AccountInfo):
"""
- 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`
+ Base class for every account plugin.
+ Just overwrite `login` and cookies will be stored and the account becomes accessible in\
+ associated hoster plugin. Plugin should also provide `loadAccountInfo`. \
+ An instance of this class is created for every entered account, it holds all \
+ fields of AccountInfo ttype, and can be set easily at runtime.
"""
- __name__ = "Account"
- __version__ = "0.2"
- __type__ = "account"
- __description__ = """Account Plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
+
+ # constants for special values
+ UNKNOWN = -1
+ UNLIMITED = -2
+
+ # Default values
+ valid = True
+ validuntil = -1
+ trafficleft = -1
+ maxtraffic = -1
+ premium = True
+ activated = True
#: after that time [in minutes] pyload will relogin the account
login_timeout = 600
#: account data will be reloaded after this time
info_threshold = 600
+ # known options
+ known_opt = ("time", "limitDL")
- def __init__(self, manager, accounts):
+ def __init__(self, manager, loginname, password, options):
Base.__init__(self, manager.core)
+ if "activated" in options:
+ activated = from_string(options["activated"], "bool")
+ else:
+ activated = Account.activated
+
+ for opt in self.known_opt:
+ if opt not in options:
+ options[opt] = ""
+
+ for opt in options.keys():
+ if opt not in self.known_opt:
+ del options[opt]
+
+ # default account attributes
+ AccountInfo.__init__(self, self.__name__, loginname, Account.valid, Account.validuntil, Account.trafficleft,
+ Account.maxtraffic, Account.premium, activated, options)
+
self.manager = manager
- self.accounts = {}
- self.infos = {} # cache for account information
+
self.lock = RLock()
+ self.timestamp = 0
+ self.login_ts = 0 # timestamp for login
+ self.cj = CookieJar(self.__name__)
+ self.password = password
+ self.error = None
- 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
+ def login(self, req):
+ """login into account, the cookies will be saved so the user can be recognized
- :param user: loginname
- :param data: data dictionary
:param req: `Request` instance
"""
- pass
+ raise NotImplemented
+
+ def relogin(self):
+ """ Force a login. """
+ req = self.getAccountRequest()
+ try:
+ return self._login(req)
+ finally:
+ req.close()
@lock
- def _login(self, user, data):
+ def _login(self, req):
# set timestamp for login
- self.timestamps[user] = time()
-
- req = self.getAccountRequest(user)
+ self.login_ts = time()
+
try:
- self.login(user, data, req)
+ try:
+ self.login(req)
+ except TypeError: #TODO: temporary
+ self.logDebug("Deprecated .login(...) signature omit user, data")
+ self.login(self.loginname, {"password": self.password}, req)
+
+
+ self.valid = True
except WrongPassword:
self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": _("Wrong Password")})
- data["valid"] = False
+ _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname
+ , "msg": _("Wrong Password")})
+ self.valid = False
except Exception, e:
self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": e})
- data["valid"] = False
+ _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname
+ , "msg": e})
+ self.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 self.valid
+
+ def restoreDefaults(self):
+ self.validuntil = Account.validuntil
+ self.trafficleft = Account.trafficleft
+ self.maxtraffic = Account.maxtraffic
+ self.premium = Account.premium
+
+ def update(self, password=None, options=None):
+ """ updates the account and returns true if anything changed """
+
+ self.login_ts = 0
+ self.valid = True #set valid, so the login will be retried
+
+ if "activated" in options:
+ self.activated = from_string(options["avtivated"], "bool")
+
+ if password:
+ self.password = password
+ self.relogin()
return True
+ if options:
+ # remove unknown options
+ for opt in options.keys():
+ if opt not in self.known_opt:
+ del options[opt]
+
+ before = self.options
+ self.options.update(options)
+ return self.options != before
+
+ def getAccountRequest(self):
+ return self.core.requestFactory.getRequest(self.__name__, self.cj)
- 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]
+ def getDownloadSettings(self):
+ """ Can be overwritten to change download settings. Default is no chunkLimit, max dl limit, resumeDownload
+
+ :return: (chunkLimit, limitDL, resumeDownload) / (int, int ,bool)
+ """
+ return -1, 0, True
@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`
+ def getAccountInfo(self, force=False):
+ """retrieve account info's for an user, do **not** overwrite this method!\\
+ just use it to retrieve info's 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)
+ if force or self.timestamp + self.info_threshold * 60 < time():
+ # make sure to login
+ req = self.getAccountRequest()
+ self.checkLogin(req)
+ self.logDebug("Get Account Info for %s" % self.loginname)
try:
- infos = self.loadAccountInfo(name, req)
- if not type(infos) == dict:
- raise Exception("Wrong return format")
+ try:
+ infos = self.loadAccountInfo(req)
+ except TypeError: #TODO: temporary
+ self.logDebug("Deprecated .loadAccountInfo(...) signature, omit user argument.")
+ infos = self.loadAccountInfo(self.loginname, req)
except Exception, e:
infos = {"error": str(e)}
-
- if req: req.close()
+ finally:
+ 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
+ self.timestamp = time()
+
+ self.restoreDefaults() # reset to initial state
+ if type(infos) == dict: # copy result from dict to class
+ for k, v in infos.iteritems():
+ if hasattr(self, k):
+ setattr(self, k, v)
+ else:
+ self.logDebug("Unknown attribute %s=%s" % (k, v))
+
+ #TODO: remove user
+ def loadAccountInfo(self, req):
+ """ Overwrite this method and set account attributes within this method.
+
+ :param user: Deprecated
+ :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]
+ pass
- def selectAccount(self):
- """ returns an valid account name and data"""
- usable = []
- for user, data in self.accounts.iteritems():
- if not data["valid"]: continue
+ def getAccountCookies(self, user):
+ self.logDebug("Deprecated method .getAccountCookies -> use account.cj")
+ return self.cj
- 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)
+ def getAccountData(self, user):
+ self.logDebug("Deprecated method .getAccountData -> use fields directly")
+ return {"password": self.password}
- 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
+ def isPremium(self, user=None):
+ if user: self.logDebug("Deprecated Argument user for .isPremium()", user)
+ return self.premium
- usable.append((user, data))
+ def isUsable(self):
+ """Check several constraints to determine if account should be used"""
+ if not self.valid or not self.activated: return False
- if not usable: return None, None
- return choice(usable)
+ if self.options["time"]:
+ time_data = ""
+ try:
+ time_data = self.options["time"]
+ start, end = time_data.split("-")
+ if not compare_time(start.split(":"), end.split(":")):
+ return False
+ except:
+ self.logWarning(_("Your Time %s has a wrong format, use: 1:22-3:44") % time_data)
+
+ if 0 <= self.validuntil < time():
+ return False
+ if self.trafficleft is 0: # test explicitly for 0
+ return False
- def canUse(self):
- return False if self.selectAccount() == (None, None) else True
+ return True
def parseTraffic(self, string): #returns kbyte
return parseFileSize(string) / 1024
+ def formatTrafficleft(self):
+ if self.trafficleft is None:
+ self.getAccountInfo(force=True)
+ return format_size(self.trafficleft*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)
+ def empty(self, user=None):
+ if user: self.logDebug("Deprecated argument user for .empty()", user)
+
+ self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % self.login)
+
+ self.trafficleft = 0
+ self.scheduleRefresh(30 * 60)
- self.infos[user].update({"trafficleft": 0})
- self.scheduleRefresh(user, 30 * 60)
+ def expired(self, user=None):
+ if user: self.logDebug("Deprecated argument user for .expired()", user)
- def expired(self, user):
- if user in self.infos:
- self.logWarning(_("Account %s is expired, checking again in 1h") % user)
+ self.logWarning(_("Account %s is expired, checking again in 1h") % user)
- self.infos[user].update({"validuntil": time() - 1})
- self.scheduleRefresh(user, 60 * 60)
+ self.validuntil = time() - 1
+ self.scheduleRefresh(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])
+ def scheduleRefresh(self, time=0, force=True):
+ """ add a task for refreshing the account info to the scheduler """
+ self.logDebug("Scheduled Account refresh for %s in %s seconds." % (self.loginname, time))
+ self.core.scheduler.addJob(time, self.getAccountInfo, [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
+ def checkLogin(self, req):
+ """ checks if the user is still logged in """
+ if self.login_ts + self.login_timeout * 60 < time():
+ if self.login_ts: # separate from fresh login to have better debug logs
+ self.logDebug("Reached login timeout for %s" % self.loginname)
+ else:
+ self.logDebug("Login with %s" % self.loginname)
+
+ self._login(req)
+ 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/Addon.py b/module/plugins/Addon.py
new file mode 100644
index 000000000..60223dd28
--- /dev/null
+++ b/module/plugins/Addon.py
@@ -0,0 +1,203 @@
+# -*- 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 traceback import print_exc
+
+#from functools import wraps
+from module.utils import has_method, to_list
+
+from Base import Base
+
+def class_name(p):
+ return p.rpartition(".")[2]
+
+
+def AddEventListener(event):
+ """ Used to register method for events. Arguments needs to match parameter of event
+
+ :param event: Name of event or list of them.
+ """
+ class _klass(object):
+ def __new__(cls, f, *args, **kwargs):
+ for ev in to_list(event):
+ addonManager.addEventListener(class_name(f.__module__), f.func_name, ev)
+ return f
+ return _klass
+
+class ConfigHandler(object):
+ """ Register method as config handler.
+
+ Your method signature has to be:
+ def foo(value=None):
+
+ value will be passed to use your method to set the config.
+ When value is None your method needs to return an interaction task for configuration.
+ """
+
+ def __new__(cls, f, *args, **kwargs):
+ addonManager.addConfigHandler(class_name(f.__module__), f.func_name)
+ return f
+
+def AddonHandler(desc, media=None):
+ """ Register Handler for files, packages, or arbitrary callable methods.
+ To let the method work on packages/files, media must be set and the argument named pid or fid.
+
+ :param desc: verbose description
+ :param media: if True or bits of media type
+ """
+ pass
+
+def AddonInfo(desc):
+ """ Called to retrieve information about the current state.
+ Decorated method must return anything convertable into string.
+
+ :param desc: verbose description
+ """
+ pass
+
+def threaded(f):
+ """ Decorator to run method in a thread. """
+
+ #@wraps(f)
+ def run(*args,**kwargs):
+ addonManager.startThread(f, *args, **kwargs)
+ return run
+
+class Addon(Base):
+ """
+ Base class for addon plugins. Use @threaded decorator for all longer running tasks.
+
+ Decorate methods with @Expose, @AddEventListener, @ConfigHandler
+
+ """
+
+ #: automatically register event listeners for functions, attribute will be deleted don't 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 seconds
+ 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 addonmanager
+ self.cb = None
+
+ #: `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.evm.addEvent(event, getattr(self,f))
+ else:
+ self.evm.addEvent(event, getattr(self,funcs))
+
+ #delete for various reasons
+ self.event_map = None
+
+ if self.event_list:
+ for f in self.event_list:
+ self.evm.addEvent(f, getattr(self,f))
+
+ self.event_list = None
+
+ self.initPeriodical()
+ self.init()
+ 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 addons: %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 "<Addon %s>" % self.__name__
+
+ def isActivated(self):
+ """ checks if addon is activated"""
+ return True if self.__internal__ else self.getConfig("activated")
+
+ def init(self):
+ pass
+
+ def setup(self):
+ """ more init stuff if needed """
+ pass
+
+ def activate(self):
+ """ Used to activate the addon """
+ if has_method(self.__class__, "coreReady"):
+ self.logDebug("Deprecated method .coreReady() use activate() instead")
+ self.coreReady()
+
+ def deactivate(self):
+ """ Used to deactivate the addon. """
+ pass
+
+ def periodical(self):
+ pass
+
+ def newInteractionTask(self, task):
+ """ new interaction task for the plugin, it MUST set the handler and timeout or will be ignored """
+ pass
+
+ def taskCorrect(self, task):
+ pass
+
+ def taskInvalid(self, task):
+ pass
+
+ # public events starts from here
+ 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 \ No newline at end of file
diff --git a/module/plugins/Base.py b/module/plugins/Base.py
new file mode 100644
index 000000000..2b9e12653
--- /dev/null
+++ b/module/plugins/Base.py
@@ -0,0 +1,338 @@
+# -*- 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
+from time import time, sleep
+from random import randint
+
+from module.utils import decode
+from module.utils.fs import exists, makedirs, join, remove
+
+# TODO
+# more attributes if needed
+# get rid of catpcha & container plugins ?! (move to crypter & internals)
+# adapt old plugins as needed
+
+class Fail(Exception):
+ """ raised when failed """
+
+class Retry(Exception):
+ """ raised when start again from beginning """
+
+class Abort(Exception):
+ """ raised when aborted """
+
+class Base(object):
+ """
+ The Base plugin class with all shared methods and every possible attribute for plugin definition.
+ """
+ __version__ = "0.1"
+ #: Regexp pattern which will be matched for download/crypter plugins
+ __pattern__ = r""
+ #: Internal addon plugin which is always loaded
+ __internal__ = False
+ #: Config definition: list of (name, type, label, default_value) or
+ #: (name, type, label, short_description, default_value)
+ __config__ = list()
+ #: Short description, one liner
+ __label__ = ""
+ #: More detailed text
+ __description__ = """"""
+ #: List of needed modules
+ __dependencies__ = tuple()
+ #: Used to assign a category to addon plugins
+ __category__ = ""
+ #: Tags to categorize the plugin, see documentation for further info
+ __tags__ = tuple()
+ #: Base64 encoded .png icon, please don't use sizes above ~3KB
+ __icon__ = ""
+ #: Alternative, link to png icon
+ __icon_url__ = ""
+ #: Url with general information/support/discussion
+ __url__ = ""
+ #: Url to term of content, user is accepting these when using the plugin
+ __toc_url__ = ""
+ #: Url to service (to buy premium) for accounts
+ __ref_url__ = ""
+
+ __author_name__ = tuple()
+ __author_mail__ = tuple()
+
+
+ def __init__(self, core, user=None):
+ self.__name__ = self.__class__.__name__
+
+ #: Core instance
+ self.core = core
+ #: logging instance
+ self.log = core.log
+ #: core config
+ self.config = core.config
+ #: :class:`EventManager`
+ self.evm = core.eventManager
+ #: :class:`InteractionManager`
+ self.im = core.interactionManager
+ if user:
+ #: :class:`Api`, user api when user is set
+ self.api = self.core.api.withUserContext(user)
+ if self.api:
+ #: :class:`User`, user related to this plugin
+ self.user = self.api.user
+ else:
+ self.api = self.core.api
+ self.user = None
+ else:
+ self.api = self.core.api
+ self.user = None
+
+ #: last interaction task
+ self.task = None
+
+ def logInfo(self, *args, **kwargs):
+ """ Print args to log at specific level
+
+ :param args: Arbitrary object which should be logged
+ :param kwargs: sep=(how to separate arguments), default = " | "
+ """
+ self._log("info", *args, **kwargs)
+
+ def logWarning(self, *args, **kwargs):
+ self._log("warning", *args, **kwargs)
+
+ def logError(self, *args, **kwargs):
+ self._log("error", *args, **kwargs)
+
+ def logDebug(self, *args, **kwargs):
+ self._log("debug", *args, **kwargs)
+
+ def _log(self, level, *args, **kwargs):
+ if "sep" in kwargs:
+ sep = "%s" % kwargs["sep"]
+ else:
+ sep = " | "
+
+ strings = []
+ for obj in args:
+ if type(obj) == unicode:
+ strings.append(obj)
+ elif type(obj) == str:
+ strings.append(decode(obj))
+ else:
+ strings.append(str(obj))
+
+ getattr(self.log, level)("%s: %s" % (self.__name__, sep.join(strings)))
+
+ def setConfig(self, option, value):
+ """ Set config value for current plugin """
+ self.core.config.set(self.__name__, option, value)
+
+ def getConf(self, option):
+ """ see `getConfig` """
+ return self.core.config.get(self.__name__, option)
+
+ def getConfig(self, option):
+ """ Returns config value for current plugin """
+ 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)
+
+ def shell(self):
+ """ open ipython shell """
+ if self.core.debug:
+ from IPython import embed
+ #noinspection PyUnresolvedReferences
+ sys.stdout = sys._stdout
+ embed()
+
+ def abort(self):
+ """ Check if plugin is in an abort state, is overwritten by subtypes"""
+ return False
+
+ def checkAbort(self):
+ """ Will be overwritten to determine if control flow should be aborted """
+ if self.abort(): raise Abort()
+
+ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
+ """Load content at url and returns it
+
+ :param url: url as string
+ :param get: GET as dict
+ :param post: POST as dict, list or string
+ :param ref: Set HTTP_REFERER header
+ :param cookies: use saved cookies
+ :param just_header: if True only the header will be retrieved and returned as dict
+ :param decode: Whether to decode the output according to http header, should be True in most cases
+ :return: Loaded content
+ """
+ if not hasattr(self, "req"): raise Exception("Plugin type does not have Request attribute.")
+ self.checkAbort()
+
+ 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 invalidTask(self):
+ if self.task:
+ self.task.invalid()
+
+ def invalidCaptcha(self):
+ self.logDebug("Deprecated method .invalidCaptcha, use .invalidTask")
+ self.invalidTask()
+
+ def correctTask(self):
+ if self.task:
+ self.task.correct()
+
+ def correctCaptcha(self):
+ self.logDebug("Deprecated method .correctCaptcha, use .correctTask")
+ self.correctTask()
+
+ 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()
+
+ name = "%sOCR" % self.__name__
+ has_plugin = name in self.core.pluginManager.getPlugins("internal")
+
+ if self.core.captcha:
+ OCR = self.core.pluginManager.loadClass("internal", name)
+ else:
+ OCR = None
+
+ if OCR and not forceUser:
+ sleep(randint(3000, 5000) / 1000.0)
+ self.checkAbort()
+
+ ocr = OCR()
+ result = ocr.get_captcha(temp_file.name)
+ else:
+ task = self.im.newCaptchaTask(img, imgtype, temp_file.name, result_type)
+ self.task = task
+ self.im.handleTask(task)
+
+ while task.isWaiting():
+ if self.abort():
+ self.im.removeTask(task)
+ raise Abort()
+ sleep(1)
+
+ #TODO task handling
+ self.im.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 appropriate 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 fail(self, reason):
+ """ fail and give reason """
+ raise Fail(reason) \ No newline at end of file
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
index d1549fe80..920009f44 100644
--- a/module/plugins/Crypter.py
+++ b/module/plugins/Crypter.py
@@ -1,72 +1,269 @@
# -*- 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 = []
+from traceback import print_exc
+
+from module.common.packagetools import parseNames
+from module.utils import to_list, has_method, uniqify
+from module.utils.fs import exists, remove, fs_encode
+
+from Base import Base, Retry
+
+class Package:
+ """ Container that indicates that a new package should be created """
+ def __init__(self, name, urls=None):
+ self.name = name
+ self.urls = urls if urls else []
+ # nested packages
+ self.packs = []
+
+ def addURL(self, url):
+ self.urls.append(url)
+
+ def addPackage(self, pack):
+ self.packs.append(pack)
+
+ def getAllURLs(self):
+ urls = self.urls
+ for p in self.packs:
+ urls.extend(p.getAllURLs())
+ return urls
+
+ # same name and urls is enough to be equal for packages
+ def __eq__(self, other):
+ return self.name == other.name and self.urls == other.urls
+
+ def __repr__(self):
+ return u"<CrypterPackage name=%s, links=%s, packs=%s" % (self.name, self.urls, self.packs)
+
+ def __hash__(self):
+ return hash(self.name) ^ hash(frozenset(self.urls))
+
+class PyFileMockup:
+ """ Legacy class needed by old crypter plugins """
+ def __init__(self, url, pack):
+ self.url = url
+ self.name = url
+ self._package = pack
+ self.packageid = pack.id if pack else -1
+
+ def package(self):
+ return self._package
+
+class Crypter(Base):
+ """
+ Base class for (de)crypter plugins. Overwrite decrypt* methods.
+
+ How to use decrypt* methods:
+
+ You have to overwrite at least one method of decryptURL, decryptURLs, decryptFile.
+
+ After decrypting and generating urls/packages you have to return the result.
+ Valid return Data is:
+
+ :class:`Package` instance Crypter.Package
+ A **new** package will be created with the name and the urls of the object.
+
+ List of urls and `Package` instances
+ All urls in the list will be added to the **current** package. For each `Package`\
+ instance a new package will be created.
+
+ """
+
+ #: Prefix to annotate that the submited string for decrypting is indeed file content
+ CONTENT_PREFIX = "filecontent:"
+
+ @classmethod
+ def decrypt(cls, core, url_or_urls):
+ """Static method to decrypt urls or content. Can be used by other plugins.
+ To decrypt file content prefix the string with ``CONTENT_PREFIX `` as seen above.
+
+ :param core: pyLoad `Core`, needed in decrypt context
+ :param url_or_urls: List of urls or single url/ file content
+ :return: List of decrypted urls, all package info removed
+ """
+ urls = to_list(url_or_urls)
+ p = cls(core)
+ try:
+ result = p.processDecrypt(urls)
+ finally:
+ p.clean()
+
+ ret = []
+
+ for url_or_pack in result:
+ if isinstance(url_or_pack, Package): #package
+ ret.extend(url_or_pack.getAllURLs())
+ else: # single url
+ ret.append(url_or_pack)
+ # eliminate duplicates
+ return uniqify(ret)
+
+ def __init__(self, core, package=None, password=None):
+ Base.__init__(self, core)
+ self.req = core.requestFactory.getRequest(self.__name__)
+
+ # Package the plugin was initialized for, don't use this, its not guaranteed to be set
+ self.package = package
+ #: Password supplied by user
+ self.password = password
+ #: Propose a renaming of the owner package
+ self.rename = None
- #: List of urls, pyLoad will generate packagenames
+ # For old style decrypter, do not use these!
+ self.packages = []
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):
+ self.pyfile = None
+
+ self.init()
+
+ def init(self):
+ """More init stuff if needed"""
+
+ def setup(self):
+ """Called everytime before decrypting. A Crypter plugin will be most likely used for several jobs."""
+
+ def decryptURL(self, url):
+ """Decrypt a single url
+
+ :param url: url to decrypt
+ :return: See :class:`Crypter` Documentation
+ """
+ if url.startswith("http"): # basic method to redirect
+ return self.decryptFile(self.load(url))
+ else:
+ self.fail(_("Not existing file or unsupported protocol"))
+
+ def decryptURLs(self, urls):
+ """Decrypt a bunch of urls
+
+ :param urls: list of urls
+ :return: See :class:`Crypter` Documentation
+ """
raise NotImplementedError
- def createPackages(self):
- """ create new packages from self.packages """
- for pack in self.packages:
+ def decryptFile(self, content):
+ """Decrypt file content
- 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)
+ :param content: content to decrypt as string
+ :return: See :class:`Crypter` Documentation
+ """
+ raise NotImplementedError
+
+ def generatePackages(self, urls):
+ """Generates :class:`Package` instances and names from urls. Useful for many different links and no\
+ given package name.
+
+ :param urls: list of urls
+ :return: list of `Package`
+ """
+ return [Package(name, purls) for name, purls in parseNames([(url,url) for url in urls]).iteritems()]
+
+ def _decrypt(self, urls):
+ """ Internal method to select decrypting method
+
+ :param urls: List of urls/content
+ :return:
+ """
+ cls = self.__class__
+
+ # separate local and remote files
+ content, urls = self.getLocalContent(urls)
+
+ if has_method(cls, "decryptURLs"):
+ self.setup()
+ result = to_list(self.decryptURLs(urls))
+ elif has_method(cls, "decryptURL"):
+ result = []
+ for url in urls:
+ self.setup()
+ result.extend(to_list(self.decryptURL(url)))
+ elif has_method(cls, "decrypt"):
+ self.logDebug("Deprecated .decrypt() method in Crypter plugin")
+ result = []
+ for url in urls:
+ self.pyfile = PyFileMockup(url, self.package)
+ self.setup()
+ self.decrypt(self.pyfile)
+ result.extend(self.convertPackages())
+ else:
+ if not has_method(cls, "decryptFile") or urls:
+ self.logDebug("No suited decrypting method was overwritten in plugin")
+ result = []
+
+ if has_method(cls, "decryptFile"):
+ for f, c in content:
+ self.setup()
+ result.extend(to_list(self.decryptFile(c)))
+ try:
+ if f.startswith("tmp_"): remove(f)
+ except :
+ pass
+
+ return result
+
+ def processDecrypt(self, urls):
+ """Catches all exceptions in decrypt methods and return results
+
+ :return: Decrypting results
+ """
+ try:
+ return self._decrypt(urls)
+ except Exception:
+ if self.core.debug:
+ print_exc()
+ return []
+
+ def getLocalContent(self, urls):
+ """Load files from disk and separate to file content and url list
+
+ :param urls:
+ :return: list of (filename, content), remote urls
+ """
+ content = []
+ # do nothing if no decryptFile method
+ if hasattr(self.__class__, "decryptFile"):
+ remote = []
+ for url in urls:
+ path = None
+ if url.startswith("http"): # skip urls directly
+ pass
+ elif url.startswith(self.CONTENT_PREFIX):
+ path = url
+ elif exists(url):
+ path = url
+ elif exists(self.core.path(url)):
+ path = self.core.path(url)
+
+ if path:
+ try:
+ if path.startswith(self.CONTENT_PREFIX):
+ content.append(("", path[len(self.CONTENT_PREFIX)]))
+ else:
+ f = open(fs_encode(path), "rb")
+ content.append((f.name, f.read()))
+ f.close()
+ except IOError, e:
+ self.logError("IOError", e)
+ else:
+ remote.append(url)
+
+ #swap filtered url list
+ urls = remote
+
+ return content, urls
- if self.pyfile.package().password:
- self.core.api.setPackageData(pid, {"password": self.pyfile.package().password})
+ def retry(self):
+ """ Retry decrypting, will only work once. Somewhat deprecated method, should be avoided. """
+ raise Retry()
- if self.urls:
- self.core.api.generateAndAddPackages(self.urls)
+ def convertPackages(self):
+ """ Deprecated """
+ self.logDebug("Deprecated method .convertPackages()")
+ res = [Package(name, urls) for name, urls in self.packages]
+ res.extend(self.urls)
+ return res
+ def clean(self):
+ if hasattr(self, "req"):
+ self.req.close()
+ del self.req \ No newline at end of file
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
index 814a70949..ad4f8f16b 100644
--- a/module/plugins/Hoster.py
+++ b/module/plugins/Hoster.py
@@ -13,21 +13,389 @@
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
+
+ @author: RaNaN, spoob, mkaay
"""
-from module.plugins.Plugin import Plugin
+import os
+from time import time
+
+if os.name != "nt":
+ from module.utils.fs import chown
+ from pwd import getpwnam
+ from grp import getgrnam
+
+from Base import Base, Fail, Retry
+from module.utils import chunks as _chunks
+from module.utils.fs import save_join, save_filename, fs_encode, fs_decode,\
+ remove, makedirs, chmod, stat, exists, join
+
+# Import for Hoster Plugins
+chunks = _chunks
+
+class Reconnect(Exception):
+ """ raised when reconnected """
+
+class SkipDownload(Exception):
+ """ raised when download should be skipped """
+
+class Hoster(Base):
+ """
+ Base plugin for hoster plugin. Overwrite getInfo for online status retrieval, process for downloading.
+ """
+
+ @staticmethod
+ def getInfo(urls):
+ """This method is used to retrieve the online status of files for hoster plugins.
+ It has to *yield* list of tuples with the result in this format (name, size, status, url),
+ where status is one of API pyfile statuses.
+
+ :param urls: List of urls
+ :return: yield list of tuple with results (name, size, status, url)
+ """
+ pass
+
+ def __init__(self, pyfile):
+ Base.__init__(self, pyfile.m.core)
+
+ self.wantReconnect = False
+ #: enables simultaneous processing of multiple downloads
+ self.limitDL = 0
+ #: chunk limit
+ self.chunkLimit = 1
+ #: enables resume (will be ignored if server dont accept chunks)
+ 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 = self.core.accountManager.getAccountForPlugin(self.__name__)
+
+ #: premium status
+ self.premium = False
+ #: username/login
+ self.user = None
+
+ if self.account and not self.account.isUsable(): self.account = None
+ if self.account:
+ self.user = self.account.loginname
+ #: Browser instance, see `network.Browser`
+ self.req = self.account.getAccountRequest()
+ # Default: -1, True, True
+ self.chunkLimit, self.limitDL, self.resumeDownload = self.account.getDownloadSettings()
+ self.premium = self.account.isPremium()
+ else:
+ self.req = self.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.retries = 0 # amount of retries already made
+ self.html = None # some plugins store html code here
+
+ self.init()
+
+ def getMultiDL(self):
+ return self.limitDL <= 0
+
+ def setMultiDL(self, val):
+ self.limitDL = 0 if val else 1
+
+ #: virtual attribute using self.limitDL on behind
+ multiDL = property(getMultiDL, setMultiDL)
+
+ def getChunkCount(self):
+ if self.chunkLimit <= 0:
+ return self.config["download"]["chunks"]
+ return min(self.config["download"]["chunks"], self.chunkLimit)
+
+ def getDownloadLimit(self):
+ if self.account:
+ limit = self.account.options.get("limitDL", 0)
+ if limit == "": limit = 0
+ if self.limitDL > 0: # a limit is already set, we use the minimum
+ return min(int(limit), self.limitDL)
+ else:
+ return int(limit)
+ else:
+ return self.limitDL
+
+
+ def __call__(self):
+ return self.__name__
+
+ def init(self):
+ """initialize the plugin (in addition to `__init__`)"""
+ pass
+
+ def setup(self):
+ """ setup for environment 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:
+ # will force a re-login or reload of account info if necessary
+ self.account.getAccountInfo()
+ 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 abort(self):
+ return self.pyfile.abort
+
+ def resetAccount(self):
+ """ don't 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 addon
+
+ 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)
+
+ self.checkAbort()
+ if self.thread.m.reconnecting.isSet():
+ self.waiting = False
+ self.wantReconnect = False
+ raise Reconnect
+
+ self.waiting = False
+ self.pyfile.setStatus("starting")
+
+ 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 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.checkAbort()
+
+ 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_filename(self.pyfile.name)
+
+ filename = join(location, name)
+
+ self.core.addonManager.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 apparently 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])
-def getInfo(self):
- #result = [ .. (name, size, status, url) .. ]
- return
+ self.log.debug("File %s not skipped, because it does not exists." % self.pyfile.name)
-class Hoster(Plugin):
- __name__ = "Hoster"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "hoster"
- __description__ = """Base hoster plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
+ 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/MultiHoster.py b/module/plugins/MultiHoster.py
new file mode 100644
index 000000000..1936478b4
--- /dev/null
+++ b/module/plugins/MultiHoster.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from module.utils import remove_chars
+
+from Account import Account
+
+def normalize(domain):
+ """ Normalize domain/plugin name, so they are comparable """
+ return remove_chars(domain.strip().lower(), "-.")
+
+#noinspection PyUnresolvedReferences
+class MultiHoster(Account):
+ """
+ Base class for MultiHoster services.
+ This is also an Account instance so you should see :class:`Account` and overwrite necessary methods.
+ Multihoster becomes only active when an Account was entered and the MultiHoster addon was activated.
+ You need to overwrite `loadHosterList` and a corresponding :class:`Hoster` plugin with the same name should
+ be available to make your service working.
+ """
+
+ #: List of hoster names that will be replaced so pyLoad will recognize them: (orig_name, pyload_name)
+ replacements = [("freakshare.net", "freakshare.com")]
+
+ #: Load new hoster list every x seconds
+ hoster_timeout = 300
+
+ def __init__(self, *args, **kwargs):
+
+ # Hoster list
+ self.hoster = []
+ # Timestamp
+ self.ts = 0
+
+ Account.__init__(self, *args, **kwargs)
+
+ def loadHosterList(self, req):
+ """Load list of supported hoster
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+
+ def isHosterUsuable(self, domain):
+ """ Determine before downloading if hoster should be used.
+
+ :param domain: domain name
+ :return: True to let the MultiHoster download, False to fallback to default plugin
+ """
+ return True
+
+ def getHosterList(self, force=False):
+ if self.ts + self.hoster_timeout < time() or force:
+ req = self.getAccountRequest()
+ try:
+ self.hoster = self.loadHosterList(req)
+ except Exception, e:
+ self.logError(e)
+ return []
+ finally:
+ req.close()
+
+ for rep in self.replacements:
+ if rep[0] in self.hoster:
+ self.hoster.remove(rep[0])
+ if rep[1] not in self.hoster:
+ self.hoster.append(rep[1])
+
+ self.ts = time()
+
+ return self.hoster \ No newline at end of file
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/UserAddon.py b/module/plugins/UserAddon.py
new file mode 100644
index 000000000..c49b1ef41
--- /dev/null
+++ b/module/plugins/UserAddon.py
@@ -0,0 +1,25 @@
+# -*- 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 Addon import Addon
+
+class UserAddon(Addon):
+ """
+ Special type of an addon that only works a specific user. Has a configuration for every user who added it .
+ """ \ No newline at end of file
diff --git a/module/plugins/accounts/FilesonicCom.py b/module/plugins/accounts/FilesonicCom.py
index 1207f1b56..1b0104b2a 100644
--- a/module/plugins/accounts/FilesonicCom.py
+++ b/module/plugins/accounts/FilesonicCom.py
@@ -37,10 +37,10 @@ class FilesonicCom(Account):
decode=True)
return json_loads(xml)["FSApi_Utility"]["getFilesonicDomainForCurrentIp"]["response"]
- def loadAccountInfo(self, user, req):
+ def loadAccountInfo(self, req):
xml = req.load(self.API_URL + "/user?method=getInfo&format=json",
- post={"u": user,
- "p": self.accounts[user]["password"]}, decode=True)
+ post={"u": self.loginname,
+ "p": self.password}, decode=True)
self.logDebug("account status retrieved from api %s" % xml)
@@ -56,15 +56,16 @@ class FilesonicCom(Account):
validuntil = -1
return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
- def login(self, user, data, req):
+ def login(self, req):
domain = self.getDomain(req)
post_vars = {
- "email": user,
- "password": data["password"],
+ "email": self.loginname,
+ "password": self.password,
"rememberMe": 1
}
page = req.load("http://www%s/user/login" % domain, cookies=True, post=post_vars, decode=True)
if "Provided password does not match." in page or "You must be logged in to view this page." in page:
self.wrongPassword()
+
diff --git a/module/plugins/accounts/OronCom.py b/module/plugins/accounts/OronCom.py
index 793984121..2c1d33162 100755
--- a/module/plugins/accounts/OronCom.py
+++ b/module/plugins/accounts/OronCom.py
@@ -20,16 +20,17 @@
from module.plugins.Account import Account
import re
from time import strptime, mktime
+from module.utils import formatSize, parseFileSize
class OronCom(Account):
__name__ = "OronCom"
- __version__ = "0.12"
+ __version__ = "0.13"
__type__ = "account"
__description__ = """oron.com account plugin"""
__author_name__ = ("DHMH")
__author_mail__ = ("DHMH@pyload.org")
- def loadAccountInfo(self, user, req):
+ def loadAccountInfo(self, req):
req.load("http://oron.com/?op=change_lang&lang=german")
src = req.load("http://oron.com/?op=my_account").replace("\n", "")
validuntil = re.search(r"<td>Premiumaccount läuft bis:</td>\s*<td>(.*?)</td>", src)
@@ -37,7 +38,7 @@ class OronCom(Account):
validuntil = validuntil.group(1)
validuntil = int(mktime(strptime(validuntil, "%d %B %Y")))
trafficleft = re.search(r'<td>Download Traffic verfügbar:</td>\s*<td>(.*?)</td>', src).group(1)
- self.logDebug("Oron left: " + trafficleft)
+ self.logDebug("Oron left: " + formatSize(parseFileSize(trafficleft)))
trafficleft = int(self.parseTraffic(trafficleft))
premium = True
else:
@@ -47,8 +48,9 @@ class OronCom(Account):
tmp = {"validuntil": validuntil, "trafficleft": trafficleft, "premium" : premium}
return tmp
- def login(self, user, data, req):
+ def login(self, req):
req.load("http://oron.com/?op=change_lang&lang=german")
- page = req.load("http://oron.com/login", post={"login": user, "password": data["password"], "op": "login"})
+ page = req.load("http://oron.com/login", post={"login": self.loginname, "password": self.password, "op": "login"})
if r'<b class="err">Login oder Passwort falsch</b>' in page:
self.wrongPassword()
+
diff --git a/module/plugins/accounts/Premium4Me.py b/module/plugins/accounts/Premium4Me.py
index de4fdc219..6a52cb61a 100644
--- a/module/plugins/accounts/Premium4Me.py
+++ b/module/plugins/accounts/Premium4Me.py
@@ -1,23 +1,27 @@
-from module.plugins.Account import Account
+# -*- coding: utf-8 -*-
+from module.plugins.MultiHoster import MultiHoster
-class Premium4Me(Account):
+class Premium4Me(MultiHoster):
__name__ = "Premium4Me"
- __version__ = "0.02"
+ __version__ = "0.10"
__type__ = "account"
__description__ = """Premium4.me account plugin"""
__author_name__ = ("RaNaN", "zoidberg")
__author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
- def loadAccountInfo(self, user, req):
+ def loadAccountInfo(self, req):
traffic = req.load("http://premium4.me/api/traffic.php?authcode=%s" % self.authcode)
- account_info = {"trafficleft": int(traffic) / 1024,
- "validuntil": -1}
+ account_info = {"trafficleft": int(traffic) / 1024, "validuntil": -1}
return account_info
- def login(self, user, data, req):
- self.authcode = req.load("http://premium4.me/api/getauthcode.php?username=%s&password=%s" % (user, data["password"])).strip()
-
+ def login(self, req):
+ self.authcode = req.load("http://premium4.me/api/getauthcode.php?username=%s&password=%s" % (self.loginname, self.password)).strip()
+
if "wrong username" in self.authcode:
- self.wrongPassword() \ No newline at end of file
+ self.wrongPassword()
+
+ def loadHosterList(self, req):
+ page = req.load("http://premium4.me/api/hosters.php?authcode=%s" % self.authcode)
+ return [x.strip() for x in page.replace("\"", "").split(";")] \ No newline at end of file
diff --git a/module/plugins/accounts/RealdebridCom.py b/module/plugins/accounts/RealdebridCom.py
index adbd090db..9460fc815 100644
--- a/module/plugins/accounts/RealdebridCom.py
+++ b/module/plugins/accounts/RealdebridCom.py
@@ -1,25 +1,35 @@
-from module.plugins.Account import Account
-import xml.dom.minidom as dom
-
-class RealdebridCom(Account):
- __name__ = "RealdebridCom"
- __version__ = "0.41"
- __type__ = "account"
- __description__ = """Real-Debrid.com account plugin"""
- __author_name__ = ("Devirex, Hazzard")
- __author_mail__ = ("naibaf_11@yahoo.de")
-
- def loadAccountInfo(self, user, req):
- page = req.load("http://real-debrid.com/api/account.php")
- xml = dom.parseString(page)
- account_info = {"validuntil": int(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
- "trafficleft": -1}
-
- return account_info
-
- def login(self, user, data, req):
- page = req.load("https://real-debrid.com/ajax/login.php", get = {"user": user, "pass": data["password"]})
- #page = req.load("https://real-debrid.com/login.html", post={"user": user, "pass": data["password"]}, cookies=True)
-
- if "Your login informations are incorrect" in page:
- self.wrongPassword()
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.plugins.MultiHoster import MultiHoster
+import xml.dom.minidom as dom
+
+class RealdebridCom(MultiHoster):
+ __name__ = "RealdebridCom"
+ __version__ = "0.5"
+ __type__ = "account"
+ __description__ = """Real-Debrid.com account plugin"""
+ __author_name__ = ("Devirex, Hazzard")
+ __author_mail__ = ("naibaf_11@yahoo.de")
+
+ def loadAccountInfo(self, req):
+ page = req.load("http://real-debrid.com/api/account.php")
+ xml = dom.parseString(page)
+ account_info = {"validuntil": int(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
+ "trafficleft": -1}
+
+ return account_info
+
+ def login(self, req):
+ page = req.load("https://real-debrid.com/ajax/login.php?user=%s&pass=%s" % (self.loginname, self.password))
+ #page = req.load("https://real-debrid.com/login.html", post={"user": user, "pass": data["password"]}, cookies=True)
+
+ if "Your login informations are incorrect" in page:
+ self.wrongPassword()
+
+
+ def loadHosterList(self, req):
+ https = "https" if self.getConfig("https") else "http"
+ page = req.load(https + "://real-debrid.com/api/hosters.php").replace("\"","").strip()
+
+ return[x.strip() for x in page.split(",") if x.strip()] \ No newline at end of file
diff --git a/module/plugins/accounts/RyushareCom.py b/module/plugins/accounts/RyushareCom.py
index 055680ea0..f734eb11b 100644
--- a/module/plugins/accounts/RyushareCom.py
+++ b/module/plugins/accounts/RyushareCom.py
@@ -3,7 +3,7 @@ from module.plugins.internal.XFSPAccount import XFSPAccount
class RyushareCom(XFSPAccount):
__name__ = "RyushareCom"
- __version__ = "0.02"
+ __version__ = "0.03"
__type__ = "account"
__description__ = """ryushare.com account plugin"""
__author_name__ = ("zoidberg", "trance4us")
@@ -12,6 +12,7 @@ class RyushareCom(XFSPAccount):
MAIN_PAGE = "http://ryushare.com/"
def login(self, user, data, req):
+ req.lastURL = "http://ryushare.com/login.python"
html = req.load("http://ryushare.com/login.python", post={"login": user, "password": data["password"], "op": "login"})
if 'Incorrect Login or Password' in html or '>Error<' in html:
self.wrongPassword() \ No newline at end of file
diff --git a/module/plugins/accounts/ShareonlineBiz.py b/module/plugins/accounts/ShareonlineBiz.py
index cdc4ebb63..4dd398d6d 100644
--- a/module/plugins/accounts/ShareonlineBiz.py
+++ b/module/plugins/accounts/ShareonlineBiz.py
@@ -23,35 +23,53 @@ import re
class ShareonlineBiz(Account):
__name__ = "ShareonlineBiz"
- __version__ = "0.23"
+ __version__ = "0.3"
__type__ = "account"
__description__ = """share-online.biz account plugin"""
- __author_name__ = ("mkaay", "zoidberg")
- __author_mail__ = ("mkaay@mkaay.de", "zoidberg@mujmail.cz")
+ __author_name__ = ("mkaay")
+ __author_mail__ = ("mkaay@mkaay.de")
- def getUserAPI(self, user, req):
- return req.load("http://api.share-online.biz/account.php?username=%s&password=%s&act=userDetails" % (user, self.accounts[user]["password"]))
-
- def loadAccountInfo(self, user, req):
- src = self.getUserAPI(user, req)
-
+ def getUserAPI(self, req):
+ src = req.load("http://api.share-online.biz/account.php?username=%s&password=%s&act=userDetails" % (self.loginname, self.password))
info = {}
for line in src.splitlines():
- if "=" in line:
- key, value = line.split("=")
- info[key] = value
- self.logDebug(info)
+ key, value = line.split("=")
+ info[key] = value
+ return info
+
+ def loadAccountInfo(self, user, req):
+ try:
+ info = self.getUserAPI(req)
+ return {"validuntil": int(info["expire_date"]), "trafficleft": -1, "premium": not info["group"] == "Sammler"}
+ except:
+ pass
+
+ #fallback
+ src = req.load("http://www.share-online.biz/members.php?setlang=en")
+ validuntil = re.search(r'<td align="left"><b>Package Expire Date:</b></td>\s*<td align="left">(\d+/\d+/\d+)</td>', src)
+ if validuntil:
+ validuntil = int(mktime(strptime(validuntil.group(1), "%m/%d/%y")))
+ else:
+ validuntil = -1
- if "dl" in info and info["dl"].lower() != "not_available":
- req.cj.setCookie("share-online.biz", "dl", info["dl"])
- if "a" in info and info["a"].lower() != "not_available":
- req.cj.setCookie("share-online.biz", "a", info["a"])
-
- return {"validuntil": int(info["expire_date"]) if "expire_date" in info else -1,
- "trafficleft": -1,
- "premium": True if ("dl" in info or "a" in info) and (info["group"] != "Sammler") else False}
+ acctype = re.search(r'<td align="left" ><b>Your Package:</b></td>\s*<td align="left">\s*<b>(.*?)</b>\s*</td>', src)
+ if acctype:
+ if acctype.group(1) == "Collector account (free)":
+ premium = False
+ else:
+ premium = True
+
+ tmp = {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
+ return tmp
def login(self, user, data, req):
- src = self.getUserAPI(user, req)
- if "EXCEPTION" in src:
- self.wrongPassword() \ No newline at end of file
+ post_vars = {
+ "act": "login",
+ "location": "index.php",
+ "dieseid": "",
+ "user": user,
+ "pass": data["password"],
+ "login": "Login"
+ }
+ req.lastURL = "http://www.share-online.biz/"
+ req.load("https://www.share-online.biz/login.php", cookies=True, post=post_vars)
diff --git a/module/plugins/hooks/AlldebridCom.py b/module/plugins/addons/AlldebridCom.py
index f9657ed8c..f9657ed8c 100644
--- a/module/plugins/hooks/AlldebridCom.py
+++ b/module/plugins/addons/AlldebridCom.py
diff --git a/module/plugins/hooks/BypassCaptcha.py b/module/plugins/addons/BypassCaptcha.py
index 24ad17dd8..24ad17dd8 100644
--- a/module/plugins/hooks/BypassCaptcha.py
+++ b/module/plugins/addons/BypassCaptcha.py
diff --git a/module/plugins/hooks/CaptchaBrotherhood.py b/module/plugins/addons/CaptchaBrotherhood.py
index a22a5ee1d..a22a5ee1d 100644
--- a/module/plugins/hooks/CaptchaBrotherhood.py
+++ b/module/plugins/addons/CaptchaBrotherhood.py
diff --git a/module/plugins/hooks/CaptchaTrader.py b/module/plugins/addons/CaptchaTrader.py
index 2b8a50a8e..889fa38ef 100644
--- a/module/plugins/hooks/CaptchaTrader.py
+++ b/module/plugins/addons/CaptchaTrader.py
@@ -27,7 +27,7 @@ from pycurl import FORM_FILE, LOW_SPEED_TIME
from module.network.RequestFactory import getURL, getRequest
from module.network.HTTPRequest import BadHeader
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
PYLOAD_KEY = "9f65e7f381c3af2b076ea680ae96b0b7"
@@ -44,7 +44,7 @@ class CaptchaTraderException(Exception):
def __repr__(self):
return "<CaptchaTraderException %s>" % self.err
-class CaptchaTrader(Hook):
+class CaptchaTrader(Addon):
__name__ = "CaptchaTrader"
__version__ = "0.14"
__description__ = """send captchas to captchatrader.com"""
diff --git a/module/plugins/hooks/Checksum.py b/module/plugins/addons/Checksum.py
index cb6f4bfe8..cb6f4bfe8 100644
--- a/module/plugins/hooks/Checksum.py
+++ b/module/plugins/addons/Checksum.py
diff --git a/module/plugins/hooks/ClickAndLoad.py b/module/plugins/addons/ClickAndLoad.py
index 97e5cd57d..6d6928557 100644
--- a/module/plugins/hooks/ClickAndLoad.py
+++ b/module/plugins/addons/ClickAndLoad.py
@@ -21,9 +21,9 @@
import socket
import thread
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
-class ClickAndLoad(Hook):
+class ClickAndLoad(Addon):
__name__ = "ClickAndLoad"
__version__ = "0.2"
__description__ = """Gives abillity to use jd's click and load. depends on webinterface"""
@@ -32,7 +32,7 @@ class ClickAndLoad(Hook):
__author_name__ = ("RaNaN", "mkaay")
__author_mail__ = ("RaNaN@pyload.de", "mkaay@mkaay.de")
- def coreReady(self):
+ def activate(self):
self.port = int(self.core.config['webinterface']['port'])
if self.core.config['webinterface']['activated']:
try:
diff --git a/module/plugins/hooks/DeathByCaptcha.py b/module/plugins/addons/DeathByCaptcha.py
index 59ff40ded..59ff40ded 100644
--- a/module/plugins/hooks/DeathByCaptcha.py
+++ b/module/plugins/addons/DeathByCaptcha.py
diff --git a/module/plugins/hooks/DownloadScheduler.py b/module/plugins/addons/DownloadScheduler.py
index 7cadede38..7cadede38 100644
--- a/module/plugins/hooks/DownloadScheduler.py
+++ b/module/plugins/addons/DownloadScheduler.py
diff --git a/module/plugins/hooks/EasybytezCom.py b/module/plugins/addons/EasybytezCom.py
index 21a988555..21a988555 100644
--- a/module/plugins/hooks/EasybytezCom.py
+++ b/module/plugins/addons/EasybytezCom.py
diff --git a/module/plugins/hooks/Ev0InFetcher.py b/module/plugins/addons/Ev0InFetcher.py
index 5941cf38c..608baf217 100644
--- a/module/plugins/hooks/Ev0InFetcher.py
+++ b/module/plugins/addons/Ev0InFetcher.py
@@ -18,18 +18,18 @@
from module.lib import feedparser
from time import mktime, time
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
-class Ev0InFetcher(Hook):
+class Ev0InFetcher(Addon):
__name__ = "Ev0InFetcher"
__version__ = "0.2"
__description__ = """checks rss feeds for ev0.in"""
__config__ = [("activated", "bool", "Activated", "False"),
("interval", "int", "Check interval in minutes", "10"),
("queue", "bool", "Move new shows directly to Queue", False),
- ("shows", "str", "Shows to check for (comma seperated)", ""),
+ ("shows", "str", "Shows to check for (comma separated)", ""),
("quality", "xvid;x264;rmvb", "Video Format", "xvid"),
- ("hoster", "str", "Hoster to use (comma seperated)", "NetloadIn,RapidshareCom,MegauploadCom,HotfileCom")]
+ ("hoster", "str", "Hoster to use (comma separated)", "NetloadIn,RapidshareCom,MegauploadCom,HotfileCom")]
__author_name__ = ("mkaay")
__author_mail__ = ("mkaay@mkaay.de")
@@ -40,7 +40,7 @@ class Ev0InFetcher(Hook):
results = self.core.pluginManager.parseUrls(links)
sortedLinks = {}
- for url, hoster in results:
+ for url, hoster in results[0]:
if hoster not in sortedLinks:
sortedLinks[hoster] = []
sortedLinks[hoster].append(url)
diff --git a/module/plugins/hooks/ExpertDecoders.py b/module/plugins/addons/ExpertDecoders.py
index 2e66e49ca..2e66e49ca 100644
--- a/module/plugins/hooks/ExpertDecoders.py
+++ b/module/plugins/addons/ExpertDecoders.py
diff --git a/module/plugins/hooks/ExternalScripts.py b/module/plugins/addons/ExternalScripts.py
index 2e77f1dae..00fc7c114 100644
--- a/module/plugins/hooks/ExternalScripts.py
+++ b/module/plugins/addons/ExternalScripts.py
@@ -14,18 +14,17 @@
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.1
+ @author: RaNaN
"""
import subprocess
-from os import listdir, access, X_OK, makedirs
-from os.path import join, exists, basename
+from os import access, X_OK, makedirs
+from os.path import basename
-from module.plugins.Hook import Hook
-from module.utils import save_join
+from module.plugins.Addon import Addon
+from module.utils.fs import save_join, exists, join, listdir
-class ExternalScripts(Hook):
+class ExternalScripts(Addon):
__name__ = "ExternalScripts"
__version__ = "0.21"
__description__ = """Run external scripts"""
diff --git a/module/plugins/hooks/ExtractArchive.py b/module/plugins/addons/ExtractArchive.py
index c789495ca..5f749ed0d 100644
--- a/module/plugins/hooks/ExtractArchive.py
+++ b/module/plugins/addons/ExtractArchive.py
@@ -3,8 +3,7 @@
import sys
import os
-from os import remove, chmod, makedirs
-from os.path import exists, basename, isfile, isdir, join
+from os.path import basename, isfile, isdir, join
from traceback import print_exc
from copy import copy
@@ -49,11 +48,11 @@ if os.name != "nt":
from pwd import getpwnam
from grp import getgrnam
-from module.utils import save_join, fs_encode
-from module.plugins.Hook import Hook, threaded, Expose
+from module.utils.fs import save_join, fs_encode, exists, remove, chmod, makedirs
+from module.plugins.Addon import Addon, threaded, Expose
from module.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword
-class ExtractArchive(Hook):
+class ExtractArchive(Addon):
"""
Provides: unrarFinished (folder, filename)
"""
diff --git a/module/plugins/hooks/HotFolder.py b/module/plugins/addons/HotFolder.py
index ee1031ad5..d05026448 100644
--- a/module/plugins/hooks/HotFolder.py
+++ b/module/plugins/addons/HotFolder.py
@@ -26,9 +26,9 @@ from os.path import isfile
from shutil import move
import time
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
-class HotFolder(Hook):
+class HotFolder(Addon):
__name__ = "HotFolder"
__version__ = "0.1"
__description__ = """observe folder and file for changes and add container and links"""
diff --git a/module/plugins/hooks/IRCInterface.py b/module/plugins/addons/IRCInterface.py
index e2737dc3a..ddaa40613 100644
--- a/module/plugins/hooks/IRCInterface.py
+++ b/module/plugins/addons/IRCInterface.py
@@ -27,13 +27,13 @@ from time import sleep
from traceback import print_exc
import re
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
from module.network.RequestFactory import getURL
from module.utils import formatSize
from pycurl import FORM_FILE
-class IRCInterface(Thread, Hook):
+class IRCInterface(Thread, Addon):
__name__ = "IRCInterface"
__version__ = "0.1"
__description__ = """connect to irc and let owner perform different tasks"""
@@ -52,7 +52,7 @@ class IRCInterface(Thread, Hook):
def __init__(self, core, manager):
Thread.__init__(self)
- Hook.__init__(self, core, manager)
+ Addon.__init__(self, core, manager)
self.setDaemon(True)
# self.sm = core.server_methods
self.api = core.api #todo, only use api
diff --git a/module/plugins/hooks/ImageTyperz.py b/module/plugins/addons/ImageTyperz.py
index 59b6334a7..59b6334a7 100644
--- a/module/plugins/hooks/ImageTyperz.py
+++ b/module/plugins/addons/ImageTyperz.py
diff --git a/module/plugins/hooks/LinkdecrypterCom.py b/module/plugins/addons/LinkdecrypterCom.py
index ac939afd9..ac939afd9 100644
--- a/module/plugins/hooks/LinkdecrypterCom.py
+++ b/module/plugins/addons/LinkdecrypterCom.py
diff --git a/module/plugins/hooks/MergeFiles.py b/module/plugins/addons/MergeFiles.py
index 02d343096..48f997681 100644
--- a/module/plugins/hooks/MergeFiles.py
+++ b/module/plugins/addons/MergeFiles.py
@@ -24,11 +24,11 @@ import traceback
from os.path import join
from module.utils import save_join, fs_encode
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
BUFFER_SIZE = 4096
-class MergeFiles(Hook):
+class MergeFiles(Addon):
__name__ = "MergeFiles"
__version__ = "0.1"
__description__ = "Merges parts splitted with hjsplit"
diff --git a/module/plugins/hooks/MultiHome.py b/module/plugins/addons/MultiHome.py
index f15148538..af3f55416 100644
--- a/module/plugins/hooks/MultiHome.py
+++ b/module/plugins/addons/MultiHome.py
@@ -17,10 +17,10 @@
@author: mkaay
"""
-from module.plugins.Hook import Hook
+from module.plugins.Addon import Addon
from time import time
-class MultiHome(Hook):
+class MultiHome(Addon):
__name__ = "MultiHome"
__version__ = "0.1"
__description__ = """ip address changer"""
diff --git a/module/plugins/addons/MultiHoster.py b/module/plugins/addons/MultiHoster.py
new file mode 100644
index 000000000..05d25b958
--- /dev/null
+++ b/module/plugins/addons/MultiHoster.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import re
+from types import MethodType
+
+from module.plugins.MultiHoster import MultiHoster as MultiHosterAccount, normalize
+from module.plugins.Addon import Addon, AddEventListener
+from module.plugins.PluginManager import PluginTuple
+
+class MultiHoster(Addon):
+ __version__ = "0.1"
+ __internal__ = True
+ __description__ = "Gives ability to use MultiHoster services. You need to add your account first."
+ __config__ = []
+ __author_mail__ = ("pyLoad Team",)
+ __author_mail__ = ("support@pyload.org",)
+
+ #TODO: multiple accounts - multihoster / config options
+
+ def init(self):
+
+ # overwritten plugins
+ self.plugins = {}
+
+ def addHoster(self, account):
+
+ self.logDebug("New MultiHoster %s" % account.__name__)
+
+ pluginMap = {}
+ for name in self.core.pluginManager.getPlugins("hoster").keys():
+ pluginMap[name.lower()] = name
+
+ supported = []
+ new_supported = []
+
+ for hoster in account.getHosterList():
+ name = normalize(hoster)
+
+ if name in pluginMap:
+ supported.append(pluginMap[name])
+ else:
+ new_supported.append(hoster)
+
+ if not supported and not new_supported:
+ account.logError(_("No Hoster loaded"))
+ return
+
+ klass = self.core.pluginManager.getPluginClass(account.__name__)
+
+ # inject plugin plugin
+ account.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(supported)))
+ for hoster in supported:
+ self.plugins[hoster] = klass
+
+ account.logDebug("New Hosters: %s" % ", ".join(sorted(new_supported)))
+
+ # create new regexp
+ regexp = r".*(%s).*" % "|".join([klass.__pattern__] + [x.replace(".", "\\.") for x in new_supported])
+
+ # recreate plugin tuple for new regexp
+ hoster = self.core.pluginManager.getPlugins("hoster")
+ p = hoster[account.__name__]
+ new = PluginTuple(p.version, re.compile(regexp), p.deps, p.user, p.path)
+ hoster[account.__name__] = new
+
+
+
+ @AddEventListener("accountDeleted")
+ def refreshAccounts(self, plugin=None, user=None):
+
+ self.plugins = {}
+
+ for name, account in self.core.accountManager.iterAccounts():
+ if isinstance(account, MultiHosterAccount) and account.isUsable():
+ self.addHoster(account)
+
+ @AddEventListener("accountUpdated")
+ def refreshAccount(self, plugin, user):
+
+ account = self.core.accountManager.getAccount(plugin, user)
+ if isinstance(account, MultiHosterAccount) and account.isUsable():
+ self.addHoster(account)
+
+ def activate(self):
+ self.refreshAccounts()
+
+ # new method for plugin manager
+ def getPlugin(self2, name):
+ if name in self.plugins:
+ return self.plugins[name]
+ return self2.getPluginClass(name)
+
+ pm = self.core.pluginManager
+ pm.getPlugin = MethodType(getPlugin, pm, object)
+
+
+ def deactivate(self):
+ #restore state
+ pm = self.core.pluginManager
+ pm.getPlugin = pm.getPluginClass
+
diff --git a/module/plugins/hooks/MultishareCz.py b/module/plugins/addons/MultishareCz.py
index a00c6cb2b..a00c6cb2b 100644
--- a/module/plugins/hooks/MultishareCz.py
+++ b/module/plugins/addons/MultishareCz.py
diff --git a/module/plugins/hooks/Premium4Me.py b/module/plugins/addons/Premium4Me.py
index fc3ce2343..fc3ce2343 100644
--- a/module/plugins/hooks/Premium4Me.py
+++ b/module/plugins/addons/Premium4Me.py
diff --git a/module/plugins/hooks/PremiumizeMe.py b/module/plugins/addons/PremiumizeMe.py
index 3825e9219..3825e9219 100644
--- a/module/plugins/hooks/PremiumizeMe.py
+++ b/module/plugins/addons/PremiumizeMe.py
diff --git a/module/plugins/hooks/RealdebridCom.py b/module/plugins/addons/RealdebridCom.py
index bd3179673..bd3179673 100644
--- a/module/plugins/hooks/RealdebridCom.py
+++ b/module/plugins/addons/RealdebridCom.py
diff --git a/module/plugins/hooks/RehostTo.py b/module/plugins/addons/RehostTo.py
index b16987f5c..b16987f5c 100644
--- a/module/plugins/hooks/RehostTo.py
+++ b/module/plugins/addons/RehostTo.py
diff --git a/module/plugins/hooks/UpdateManager.py b/module/plugins/addons/UpdateManager.py
index ce75399c5..5bc6ac447 100644
--- a/module/plugins/hooks/UpdateManager.py
+++ b/module/plugins/addons/UpdateManager.py
@@ -23,11 +23,11 @@ from os import stat
from os.path import join, exists
from time import time
-from module.ConfigParser import IGNORE
+from module.plugins.PluginManager import IGNORE
from module.network.RequestFactory import getURL
-from module.plugins.Hook import threaded, Expose, Hook
+from module.plugins.Addon import threaded, Expose, Addon
-class UpdateManager(Hook):
+class UpdateManager(Addon):
__name__ = "UpdateManager"
__version__ = "0.12"
__description__ = """checks for updates"""
@@ -60,6 +60,11 @@ class UpdateManager(Hook):
@threaded
def periodical(self):
+
+ if self.core.version.endswith("-dev"):
+ self.logDebug("No update check performed on dev version.")
+ return
+
update = self.checkForUpdate()
if update:
self.info["pyload"] = True
@@ -129,10 +134,10 @@ class UpdateManager(Hook):
else:
type = prefix
- plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
+ plugins = self.core.pluginManager.getPlugins(type)
if name in plugins:
- if float(plugins[name]["v"]) >= float(version):
+ if float(plugins[name].version) >= float(version):
continue
if name in IGNORE or (type, name) in IGNORE:
diff --git a/module/plugins/hooks/XFileSharingPro.py b/module/plugins/addons/XFileSharingPro.py
index 3981db2d0..3981db2d0 100644
--- a/module/plugins/hooks/XFileSharingPro.py
+++ b/module/plugins/addons/XFileSharingPro.py
diff --git a/module/plugins/hooks/XMPPInterface.py b/module/plugins/addons/XMPPInterface.py
index a96adf524..e8ef1d2ca 100644
--- a/module/plugins/hooks/XMPPInterface.py
+++ b/module/plugins/addons/XMPPInterface.py
@@ -19,12 +19,12 @@
"""
from pyxmpp import streamtls
-from pyxmpp.all import JID, Message
+from pyxmpp.all import JID, Message, Presence
from pyxmpp.jabber.client import JabberClient
from pyxmpp.interface import implements
from pyxmpp.interfaces import *
-from module.plugins.hooks.IRCInterface import IRCInterface
+from module.plugins.addons.IRCInterface import IRCInterface
class XMPPInterface(IRCInterface, JabberClient):
__name__ = "XMPPInterface"
@@ -121,7 +121,26 @@ class XMPPInterface(IRCInterface, JabberClient):
in a client session."""
return [
("normal", self.message),
- ]
+ ]
+
+ def presence_control(self, stanza):
+ from_jid = unicode(stanza.get_from_jid())
+ stanza_type = stanza.get_type()
+ self.log.debug("pyLoad XMPP: %s stanza from %s" % (stanza_type,
+ from_jid))
+
+ if from_jid in self.getConfig("owners"):
+ return stanza.make_accept_response()
+
+ return stanza.make_deny_response()
+
+ def session_started(self):
+ self.stream.send(Presence())
+
+ self.stream.set_presence_handler("subscribe", self.presence_control)
+ self.stream.set_presence_handler("subscribed", self.presence_control)
+ self.stream.set_presence_handler("unsubscribe", self.presence_control)
+ self.stream.set_presence_handler("unsubscribed", self.presence_control)
def message(self, stanza):
"""Message handler for the component."""
@@ -248,4 +267,10 @@ class VersionHandler(object):
q.newTextChild(q.ns(), "name", "Echo component")
q.newTextChild(q.ns(), "version", "1.0")
return iq
- \ No newline at end of file
+
+ def unload(self):
+ self.log.debug("pyLoad XMPP: unloading")
+ self.disconnect()
+
+ def deactivate(self):
+ self.unload()
diff --git a/module/plugins/hooks/ZeveraCom.py b/module/plugins/addons/ZeveraCom.py
index 46c752c21..46c752c21 100644
--- a/module/plugins/hooks/ZeveraCom.py
+++ b/module/plugins/addons/ZeveraCom.py
diff --git a/module/plugins/hooks/__init__.py b/module/plugins/addons/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/hooks/__init__.py
+++ b/module/plugins/addons/__init__.py
diff --git a/module/plugins/captcha/GigasizeCom.py b/module/plugins/captcha/GigasizeCom.py
deleted file mode 100644
index d31742eb5..000000000
--- a/module/plugins/captcha/GigasizeCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-from captcha import OCR
-
-class GigasizeCom(OCR):
- 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
-
-if __name__ == '__main__':
- ocr = GigasizeCom()
- import urllib
- urllib.urlretrieve('http://www.gigasize.com/randomImage.php', "gigasize_tmp.jpg")
-
- print ocr.get_captcha('gigasize_tmp.jpg')
diff --git a/module/plugins/captcha/LinksaveIn.py b/module/plugins/captcha/LinksaveIn.py
deleted file mode 100644
index 3ad7b265a..000000000
--- a/module/plugins/captcha/LinksaveIn.py
+++ /dev/null
@@ -1,147 +0,0 @@
-from captcha import OCR
-import Image
-from os import sep
-from os.path import dirname
-from os.path import abspath
-from glob import glob
-
-
-class LinksaveIn(OCR):
- __name__ = "LinksaveIn"
- def __init__(self):
- OCR.__init__(self)
- self.data_dir = dirname(abspath(__file__)) + sep + "LinksaveIn" + sep
-
- def load_image(self, image):
- im = Image.open(image)
- frame_nr = 0
-
- lut = im.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- new = Image.new("RGB", im.size)
- npix = new.load()
- while True:
- try:
- im.seek(frame_nr)
- except EOFError:
- break
- frame = im.copy()
- pix = frame.load()
- for x in range(frame.size[0]):
- for y in range(frame.size[1]):
- if lut[pix[x, y]] != (0,0,0):
- npix[x, y] = lut[pix[x, y]]
- frame_nr += 1
- new.save(self.data_dir+"unblacked.png")
- self.image = new.copy()
- self.pixels = self.image.load()
- self.result_captcha = ''
-
- def get_bg(self):
- stat = {}
- cstat = {}
- img = self.image.convert("P")
- for bgpath in glob(self.data_dir+"bg/*.gif"):
- stat[bgpath] = 0
- bg = Image.open(bgpath)
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- for x in range(bg.size[0]):
- for y in range(bg.size[1]):
- rgb_bg = bglut[bgpix[x, y]]
- rgb_c = lut[pix[x, y]]
- try:
- cstat[rgb_c] += 1
- except:
- cstat[rgb_c] = 1
- if rgb_bg == rgb_c:
- stat[bgpath] += 1
- max_p = 0
- bg = ""
- for bgpath, value in stat.items():
- if max_p < value:
- bg = bgpath
- max_p = value
- return bg
-
- def substract_bg(self, bgpath):
- bg = Image.open(bgpath)
- img = self.image.convert("P")
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- orgpix = self.image.load()
- for x in range(bg.size[0]):
- for y in range(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 range(new.size[0]):
- for y in range(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
-
-if __name__ == '__main__':
- import urllib
- ocr = LinksaveIn()
- testurl = "http://linksave.in/captcha/cap.php?hsh=2229185&code=ZzHdhl3UffV3lXTH5U4b7nShXj%2Bwma1vyoNBcbc6lcc%3D"
- urllib.urlretrieve(testurl, ocr.data_dir+"captcha.gif")
-
- print ocr.get_captcha(ocr.data_dir+'captcha.gif')
diff --git a/module/plugins/captcha/MegauploadCom.py b/module/plugins/captcha/MegauploadCom.py
deleted file mode 100644
index 469ee4094..000000000
--- a/module/plugins/captcha/MegauploadCom.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from captcha import OCR
-
-class MegauploadCom(OCR):
- __name__ = "MegauploadCom"
- def __init__(self):
- OCR.__init__(self)
-
- def get_captcha(self, image):
- self.load_image(image)
- self.run_tesser(True, True, False, True)
- return self.result_captcha
-
-if __name__ == '__main__':
- ocr = MegauploadCom()
diff --git a/module/plugins/container/CCF.py b/module/plugins/container/CCF.py
index 301b033d4..ab7ff1099 100644
--- a/module/plugins/container/CCF.py
+++ b/module/plugins/container/CCF.py
@@ -4,13 +4,13 @@
import re
from urllib2 import build_opener
-from module.plugins.Container import Container
+from module.plugins.Crypter import Crypter
from module.lib.MultipartPostHandler import MultipartPostHandler
from os import makedirs
from os.path import exists, join
-class CCF(Container):
+class CCF(Crypter):
__name__ = "CCF"
__version__ = "0.2"
__pattern__ = r"(?!http://).*\.ccf$"
diff --git a/module/plugins/container/LinkList.py b/module/plugins/container/LinkList.py
deleted file mode 100644
index fefeaf486..000000000
--- a/module/plugins/container/LinkList.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import codecs
-from module.utils import fs_encode
-from module.plugins.Container import Container
-
-class LinkList(Container):
- __name__ = "LinkList"
- __version__ = "0.12"
- __pattern__ = r".+\.txt$"
- __description__ = """Read Link Lists in txt format"""
- __config__ = [("clear", "bool", "Clear Linklist after adding", False),
- ("encoding", "string", "File encoding (default utf-8)", "")]
- __author_name__ = ("spoob", "jeix")
- __author_mail__ = ("spoob@pyload.org", "jeix@hasnomail.com")
-
- def decrypt(self, pyfile):
- try:
- file_enc = codecs.lookup(self.getConfig("encoding")).name
- except:
- file_enc = "utf-8"
-
- print repr(pyfile.url)
- print pyfile.url
-
- file_name = fs_encode(pyfile.url)
-
- txt = codecs.open(file_name, 'r', file_enc)
- links = txt.readlines()
- curPack = "Parsed links from %s" % pyfile.name
-
- packages = {curPack:[],}
-
- for link in links:
- link = link.strip()
- if not link: continue
-
- if link.startswith(";"):
- continue
- if link.startswith("[") and link.endswith("]"):
- # new package
- curPack = link[1:-1]
- packages[curPack] = []
- continue
- packages[curPack].append(link)
- txt.close()
-
- # empty packages fix
-
- delete = []
-
- for key,value in packages.iteritems():
- if not value:
- delete.append(key)
-
- for key in delete:
- del packages[key]
-
- if self.getConfig("clear"):
- try:
- txt = open(file_name, 'wb')
- txt.close()
- except:
- self.log.warning(_("LinkList could not be cleared."))
-
- for name, links in packages.iteritems():
- self.packages.append((name, links, name))
diff --git a/module/plugins/container/RSDF.py b/module/plugins/container/RSDF.py
index ea5cd67f2..cbc9864b1 100644
--- a/module/plugins/container/RSDF.py
+++ b/module/plugins/container/RSDF.py
@@ -5,9 +5,9 @@ import base64
import binascii
import re
-from module.plugins.Container import Container
+from module.plugins.Crypter import Crypter
-class RSDF(Container):
+class RSDF(Crypter):
__name__ = "RSDF"
__version__ = "0.21"
__pattern__ = r".*\.rsdf"
diff --git a/module/plugins/crypter/FilesonicComFolder.py b/module/plugins/crypter/FilesonicComFolder.py
index b967a74a1..02ae66295 100644
--- a/module/plugins/crypter/FilesonicComFolder.py
+++ b/module/plugins/crypter/FilesonicComFolder.py
@@ -4,8 +4,6 @@ import re
from module.plugins.Crypter import Crypter
class FilesonicComFolder(Crypter):
- __name__ = "FilesonicComFolder"
- __type__ = "crypter"
__pattern__ = r"http://(\w*\.)?(sharingmatrix|filesonic|wupload)\.[^/]*/folder/\w+/?"
__version__ = "0.11"
__description__ = """Filesonic.com/Wupload.com Folder Plugin"""
@@ -15,9 +13,8 @@ class FilesonicComFolder(Crypter):
FOLDER_PATTERN = r'<table>\s*<caption>Files Folder</caption>(.*?)</table>'
LINK_PATTERN = r'<a href="([^"]+)">'
- def decrypt(self, pyfile):
- html = self.load(self.pyfile.url)
-
+ def decryptURL(self, url):
+ html = self.load(url)
new_links = []
folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
@@ -26,6 +23,7 @@ class FilesonicComFolder(Crypter):
new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1)))
if new_links:
- self.core.files.addLinks(new_links, self.pyfile.package().id)
+ return new_links
else:
- self.fail('Could not extract any links') \ No newline at end of file
+ self.fail('Could not extract any links')
+
diff --git a/module/plugins/crypter/HotfileFolderCom.py b/module/plugins/crypter/HotfileFolderCom.py
index 00771e2a3..ea7311e3c 100644
--- a/module/plugins/crypter/HotfileFolderCom.py
+++ b/module/plugins/crypter/HotfileFolderCom.py
@@ -9,17 +9,21 @@ class HotfileFolderCom(Crypter):
__name__ = "HotfileFolderCom"
__type__ = "crypter"
__pattern__ = r"http://(?:www\.)?hotfile.com/list/\w+/\w+"
- __version__ = "0.1"
+ __version__ = "0.2"
__description__ = """HotfileFolder Download Plugin"""
__author_name__ = ("RaNaN")
__author_mail__ = ("RaNaN@pyload.org")
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
+ def decryptURL(self, url):
+ html = self.load(url)
- name = re.findall(r'<img src="/i/folder.gif" width="23" height="14" style="margin-bottom: -2px;" />([^<]+)', html, re.MULTILINE)[0].replace("/", "")
- new_links = re.findall(r'href="(http://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+[^"]+)', html)
+ new_links = []
+ for link in re.findall(r'href="(http://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+[^"]+)', html):
+ new_links.append(link[0])
- new_links = [x[0] for x in new_links]
+ if new_links:
+ self.logDebug("Found %d new links" % len(new_links))
+ return new_links
+ else:
+ self.fail('Could not extract any links')
- self.packages.append((name, new_links, name)) \ No newline at end of file
diff --git a/module/plugins/crypter/LinkList.py b/module/plugins/crypter/LinkList.py
new file mode 100644
index 000000000..ebfa373eb
--- /dev/null
+++ b/module/plugins/crypter/LinkList.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.plugins.Crypter import Crypter, Package
+
+class LinkList(Crypter):
+ __name__ = "LinkList"
+ __version__ = "0.11"
+ __pattern__ = r".+\.txt$"
+ __description__ = """Read Link Lists in txt format"""
+ __author_name__ = ("spoob", "jeix")
+ __author_mail__ = ("spoob@pyload.org", "jeix@hasnomail.com")
+
+ # method declaration is needed here
+ def decryptURL(self, url):
+ return Crypter.decryptURL(self, url)
+
+ def decryptFile(self, content):
+ links = content.splitlines()
+
+ curPack = "default"
+ packages = {curPack:[]}
+
+ for link in links:
+ link = link.strip()
+ if not link: continue
+
+ if link.startswith(";"):
+ continue
+ if link.startswith("[") and link.endswith("]"):
+ # new package
+ curPack = link[1:-1]
+ packages[curPack] = []
+ continue
+ packages[curPack].append(link)
+
+ # empty packages fix
+ delete = []
+
+ for key,value in packages.iteritems():
+ if not value:
+ delete.append(key)
+
+ for key in delete:
+ del packages[key]
+
+ urls = []
+
+ for name, links in packages.iteritems():
+ if name == "default":
+ urls.extend(links)
+ else:
+ urls.append(Package(name, links))
+
+ return urls \ No newline at end of file
diff --git a/module/plugins/crypter/OronComFolder.py b/module/plugins/crypter/OronComFolder.py
index 57b273163..726371966 100755
--- a/module/plugins/crypter/OronComFolder.py
+++ b/module/plugins/crypter/OronComFolder.py
@@ -8,25 +8,39 @@ class OronComFolder(Crypter):
__name__ = "OronComFolder"
__type__ = "crypter"
__pattern__ = r"http://(?:www\.)?oron.com/folder/\w+"
- __version__ = "0.1"
+ __version__ = "0.2"
__description__ = """Oron.com Folder Plugin"""
__author_name__ = ("DHMH")
__author_mail__ = ("webmaster@pcProfil.de")
- FOLDER_PATTERN = r'<table width="100%" cellpadding="7" cellspacing="1" class="tbl2">(.*)</table>\n </div>'
+ FOLDER_PATTERN = r'<table(?:.*)class="tbl"(?:.*)>(?:.*)<table(?:.*)class="tbl2"(?:.*)>(?P<body>.*)</table>(?:.*)</table>'
LINK_PATTERN = r'<a href="([^"]+)" target="_blank">'
- def decrypt(self, pyfile):
- html = self.load(self.pyfile.url)
+ def decryptURL(self, url):
+ html = self.load(url)
new_links = []
+ if 'No such folder exist' in html:
+ # Don't fail because if there's more than a folder for this package
+ # and only one of them fails, no urls at all will be added.
+ self.logWarning("Folder does not exist")
+ return new_links
+
folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if folder is None: self.fail("Parse error (FOLDER)")
+ if folder is None:
+ # Don't fail because if there's more than a folder for this package
+ # and only one of them fails, no urls at all will be added.
+ self.logWarning("Parse error (FOLDER)")
+ return new_links
new_links.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
if new_links:
- self.core.files.addLinks(new_links, self.pyfile.package().id)
+ self.logDebug("Found %d new links" % len(new_links))
+ return new_links
else:
- self.fail('Could not extract any links') \ No newline at end of file
+ # Don't fail because if there's more than a folder for this package
+ # and only one of them fails, no urls at all will be added.
+ self.logWarning('Could not extract any links')
+ return new_links
diff --git a/module/plugins/crypter/XfilesharingProFolder.py b/module/plugins/crypter/XfilesharingProFolder.py
new file mode 100644
index 000000000..8e58c207d
--- /dev/null
+++ b/module/plugins/crypter/XfilesharingProFolder.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from module.plugins.Crypter import Crypter, Package
+import re
+
+class XfilesharingProFolder(Crypter):
+ __name__ = "XfilesharingProFolder"
+ __type__ = "crypter"
+ __pattern__ = r"http://(?:www\.)?((easybytez|turboupload|uploadville|file4safe|fileband|filebeep|grupload|247upload)\.com|(muchshare|annonhost).net|bzlink.us)/users/.*"
+ __version__ = "0.01"
+ __description__ = """Generic XfilesharingPro Folder Plugin"""
+ __author_name__ = ("zoidberg")
+ __author_mail__ = ("zoidberg@mujmail.cz")
+
+ LINK_PATTERN = r'<div class="link"><a href="([^"]+)" target="_blank">[^<]*</a></div>'
+ SUBFOLDER_PATTERN = r'<TD width="1%"><img src="[^"]*/images/folder2.gif"></TD><TD><a href="([^"]+)"><b>(?!\. \.<)([^<]+)</b></a></TD>'
+
+ def decryptURL(self, url):
+ return self.decryptFile(self.load(url, decode = True))
+
+ def decryptFile(self, html):
+ new_links = []
+
+ new_links.extend(re.findall(self.LINK_PATTERN, html))
+
+ subfolders = re.findall(self.SUBFOLDER_PATTERN, html)
+ #self.logDebug(subfolders)
+ for (url, name) in subfolders:
+ if self.package: name = "%s/%s" % (self.package.name, name)
+ new_links.append(Package(name, [url]))
+
+ if not new_links: self.fail('Could not extract any links')
+
+ return new_links \ No newline at end of file
diff --git a/module/plugins/hoster/BezvadataCz.py b/module/plugins/hoster/BezvadataCz.py
index 680bbc173..a0717ad64 100644
--- a/module/plugins/hoster/BezvadataCz.py
+++ b/module/plugins/hoster/BezvadataCz.py
@@ -23,7 +23,7 @@ class BezvadataCz(SimpleHoster):
__name__ = "BezvadataCz"
__type__ = "hoster"
__pattern__ = r"http://(\w*\.)*bezvadata.cz/stahnout/.*"
- __version__ = "0.22"
+ __version__ = "0.23"
__description__ = """BezvaData.cz"""
__author_name__ = ("zoidberg")
__author_mail__ = ("zoidberg@mujmail.cz")
diff --git a/module/plugins/hoster/DdlstorageCom.py b/module/plugins/hoster/DdlstorageCom.py
index 0177d9648..1ad5fa6d8 100644
--- a/module/plugins/hoster/DdlstorageCom.py
+++ b/module/plugins/hoster/DdlstorageCom.py
@@ -5,12 +5,12 @@ class DdlstorageCom(XFileSharingPro):
__name__ = "DdlstorageCom"
__type__ = "hoster"
__pattern__ = r"http://(?:\w*\.)*?ddlstorage.com/\w{12}"
- __version__ = "0.05"
+ __version__ = "0.06"
__description__ = """DDLStorage.com hoster plugin"""
__author_name__ = ("zoidberg")
__author_mail__ = ("zoidberg@mujmail.cz")
- FILE_INFO_PATTERN = r'<h2>Download File\s*<span[^>]*>(?P<N>[^>]+)</span></h2>\s*[^\(]*\((?P<S>[^\)]+)\)</h2>'
+ FILE_INFO_PATTERN = r'<h2>\s*Download File\s*<span[^>]*>(?P<N>[^>]+)</span></h2>\s*[^\(]*\((?P<S>[^\)]+)\)</h2>'
HOSTER_NAME = "ddlstorage.com"
def setup(self):
diff --git a/module/plugins/hoster/DlFreeFr.py b/module/plugins/hoster/DlFreeFr.py
index 8b32e5eb4..5b318fd54 100644
--- a/module/plugins/hoster/DlFreeFr.py
+++ b/module/plugins/hoster/DlFreeFr.py
@@ -103,7 +103,6 @@ class DlFreeFr(SimpleHoster):
#FILE_URL_PATTERN = r'href="(?P<url>http://.*?)">T&eacute;l&eacute;charger ce fichier'
def setup(self):
- self.multiDL = True
self.limitDL = 5
self.resumeDownload = True
self.chunkLimit = 1
diff --git a/module/plugins/hoster/EuroshareEu.py b/module/plugins/hoster/EuroshareEu.py
index a0bfe0ab2..1e1cc0b4b 100644
--- a/module/plugins/hoster/EuroshareEu.py
+++ b/module/plugins/hoster/EuroshareEu.py
@@ -37,7 +37,7 @@ class EuroshareEu(Hoster):
__name__ = "EuroshareEu"
__type__ = "hoster"
__pattern__ = r"http://(\w*\.)?euroshare.eu/file/.*"
- __version__ = "0.2b"
+ __version__ = "0.30"
__description__ = """Euroshare.eu"""
__author_name__ = ("zoidberg")
diff --git a/module/plugins/hoster/FilesMailRu.py b/module/plugins/hoster/FilesMailRu.py
index 6002ab3dc..1284329b5 100644
--- a/module/plugins/hoster/FilesMailRu.py
+++ b/module/plugins/hoster/FilesMailRu.py
@@ -2,9 +2,8 @@
# -*- coding: utf-8 -*-
import re
-from module.plugins.Hoster import Hoster
+from module.plugins.Hoster import Hoster, chunks
from module.network.RequestFactory import getURL
-from module.plugins.Plugin import chunks
def getInfo(urls):
result = []
diff --git a/module/plugins/hoster/FilesonicCom.py b/module/plugins/hoster/FilesonicCom.py
index 525a99e7a..b35ce1b1f 100644
--- a/module/plugins/hoster/FilesonicCom.py
+++ b/module/plugins/hoster/FilesonicCom.py
@@ -7,7 +7,7 @@ from urllib import unquote
from module.plugins.Hoster import Hoster
from module.plugins.ReCaptcha import ReCaptcha
-from module.plugins.Plugin import chunks
+from module.utils import chunks
from module.network.RequestFactory import getURL
from module.common.json_layer import json_loads
diff --git a/module/plugins/hoster/HotfileCom.py b/module/plugins/hoster/HotfileCom.py
index bf4250767..df652edcc 100644
--- a/module/plugins/hoster/HotfileCom.py
+++ b/module/plugins/hoster/HotfileCom.py
@@ -6,7 +6,7 @@ from module.plugins.Hoster import Hoster
from module.plugins.ReCaptcha import ReCaptcha
from module.network.RequestFactory import getURL
-from module.plugins.Plugin import chunks
+from module.utils import chunks
def getInfo(urls):
api_url_base = "http://api.hotfile.com/"
diff --git a/module/plugins/hoster/MegauploadCom.py b/module/plugins/hoster/MegauploadCom.py
index 336cbfb58..8693e4303 100644
--- a/module/plugins/hoster/MegauploadCom.py
+++ b/module/plugins/hoster/MegauploadCom.py
@@ -2,17 +2,13 @@
# -*- coding: utf-8 -*-
import re
-from time import sleep
from module.plugins.Hoster import Hoster
from module.network.RequestFactory import getURL
-from module.network.HTTPRequest import BadHeader
from module.utils import html_unescape
-from module.PyFile import statusMap
-
-from pycurl import error
+from datatypes.PyFile import statusMap
def getInfo(urls):
yield [(url, 0, 1, url) for url in urls]
diff --git a/module/plugins/hoster/MultishareCz.py b/module/plugins/hoster/MultishareCz.py
index a0dda30b8..af7aa94cf 100644
--- a/module/plugins/hoster/MultishareCz.py
+++ b/module/plugins/hoster/MultishareCz.py
@@ -24,7 +24,7 @@ class MultishareCz(SimpleHoster):
__name__ = "MultishareCz"
__type__ = "hoster"
__pattern__ = r"http://(?:\w*\.)?multishare.cz/stahnout/(?P<ID>\d+).*"
- __version__ = "0.34"
+ __version__ = "0.40"
__description__ = """MultiShare.cz"""
__author_name__ = ("zoidberg")
@@ -50,11 +50,12 @@ class MultishareCz(SimpleHoster):
self.download("http://www.multishare.cz/html/download_free.php?ID=%s" % self.fileID)
def handlePremium(self):
- if not self.checkCredit():
+ if not self.checkTrafficLeft():
self.logWarning("Not enough credit left to download file")
self.resetAccount()
self.download("http://www.multishare.cz/html/download_premium.php?ID=%s" % self.fileID)
+ self.checkTrafficLeft()
def handleOverriden(self):
if not self.premium:
@@ -63,18 +64,13 @@ class MultishareCz(SimpleHoster):
self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post = {"link": self.pyfile.url}, decode = True)
self.getFileInfo()
- if not self.checkCredit():
+ if not self.checkTrafficLeft():
self.fail("Not enough credit left to download file")
url = "http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random()*10000*random())
params = {"u_ID" : self.acc_info["u_ID"], "u_hash" : self.acc_info["u_hash"], "link" : self.pyfile.url}
self.logDebug(url, params)
self.download(url, get = params)
-
- def checkCredit(self):
- self.acc_info = self.account.getAccountInfo(self.user, True)
- self.logInfo("User %s has %i MB left" % (self.user, self.acc_info["trafficleft"]/1024))
-
- return self.pyfile.size / 1024 <= self.acc_info["trafficleft"]
+ self.checkTrafficLeft()
getInfo = create_getInfo(MultishareCz) \ No newline at end of file
diff --git a/module/plugins/hoster/NetloadIn.py b/module/plugins/hoster/NetloadIn.py
index fa2f3ddef..9310b5c34 100644
--- a/module/plugins/hoster/NetloadIn.py
+++ b/module/plugins/hoster/NetloadIn.py
@@ -5,14 +5,12 @@ import re
from time import sleep, time
+from module.utils import chunks
from module.plugins.Hoster import Hoster
from module.network.RequestFactory import getURL
-from module.plugins.Plugin import chunks
-
-
def getInfo(urls):
- ## returns list of tupels (name, size (in bytes), status (see FileDatabase), url)
+ ## returns list of tuples (name, size (in bytes), status (see FileDatabase), url)
apiurl = "http://api.netload.in/info.php?auth=Zf9SnQh9WiReEsb18akjvQGqT0I830e8&bz=1&md5=1&file_id="
@@ -202,7 +200,7 @@ class NetloadIn(Hoster):
file_id = re.search('<input name="file_id" type="hidden" value="(.*)" />', page).group(1)
if not captchawaited:
wait = self.get_wait_time(page)
- if i == 0: self.pyfile.waitUntil = time() # dont wait contrary to time on website
+ if i == 0: self.pyfile.waitUntil = time() # don't wait contrary to time on web site
else: self.pyfile.waitUntil = t
self.log.info(_("Netload: waiting for captcha %d s.") % (self.pyfile.waitUntil - time()))
#self.setWait(wait)
diff --git a/module/plugins/hoster/Premium4Me.py b/module/plugins/hoster/Premium4Me.py
index d029b3df1..cd47a9e91 100644
--- a/module/plugins/hoster/Premium4Me.py
+++ b/module/plugins/hoster/Premium4Me.py
@@ -6,7 +6,7 @@ from module.plugins.Hoster import Hoster
class Premium4Me(Hoster):
__name__ = "Premium4Me"
- __version__ = "0.03"
+ __version__ = "0.10"
__type__ = "hoster"
__pattern__ = r"http://premium4.me/.*"
diff --git a/module/plugins/hoster/PutlockerCom.py b/module/plugins/hoster/PutlockerCom.py
index 4de0ca218..8cfcd4d9e 100644
--- a/module/plugins/hoster/PutlockerCom.py
+++ b/module/plugins/hoster/PutlockerCom.py
@@ -52,7 +52,7 @@ class PutlockerCom(Hoster):
__name__ = "PutlockerCom"
__type__ = "hoster"
__pattern__ = r'http://(www\.)?putlocker\.com/(file|embed)/[A-Z0-9]+'
- __version__ = "0.1"
+ __version__ = "0.2"
__description__ = """Putlocker.Com"""
__author_name__ = ("jeix")
@@ -121,6 +121,6 @@ class PutlockerCom(Hoster):
# if link is None:
# self.fail("%s: Plugin broken." % self.__name__)
- return self.link.group(1)
+ return self.link.group(1).replace("&amp;", "&")
diff --git a/module/plugins/hoster/RapidshareCom.py b/module/plugins/hoster/RapidshareCom.py
index 8b31dd42c..6aacd684e 100644
--- a/module/plugins/hoster/RapidshareCom.py
+++ b/module/plugins/hoster/RapidshareCom.py
@@ -52,7 +52,7 @@ class RapidshareCom(Hoster):
__pattern__ = r"https?://[\w\.]*?rapidshare.com/(?:files/(?P<id>\d*?)/(?P<name>[^?]+)|#!download\|(?:\w+)\|(?P<id_new>\d+)\|(?P<name_new>[^|]+))"
__version__ = "1.38"
__description__ = """Rapidshare.com Download Hoster"""
- __config__ = [["server", "Cogent;Deutsche Telekom;Level(3);Level(3) #2;GlobalCrossing;Level(3) #3;Teleglobe;GlobalCrossing #2;TeliaSonera #2;Teleglobe #2;TeliaSonera #3;TeliaSonera", "Preferred Server", "None"]]
+ __config__ = [("server", "Cogent;Deutsche Telekom;Level(3);Level(3) #2;GlobalCrossing;Level(3) #3;Teleglobe;GlobalCrossing #2;TeliaSonera #2;Teleglobe #2;TeliaSonera #3;TeliaSonera", "Preferred Server", "None")]
__author_name__ = ("spoob", "RaNaN", "mkaay")
__author_mail__ = ("spoob@pyload.org", "ranan@pyload.org", "mkaay@mkaay.de")
@@ -129,7 +129,7 @@ class RapidshareCom(Hoster):
self.handleFree()
def handlePremium(self):
- info = self.account.getAccountInfo(self.user, True)
+ info = self.account.getAccountInfo(True)
self.log.debug("%s: Use Premium Account" % self.__name__)
url = self.api_data["mirror"]
self.download(url, get={"directstart":1})
diff --git a/module/plugins/hoster/RealdebridCom.py b/module/plugins/hoster/RealdebridCom.py
index ff4843afd..3c796232e 100644
--- a/module/plugins/hoster/RealdebridCom.py
+++ b/module/plugins/hoster/RealdebridCom.py
@@ -1,88 +1,88 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import re
-from time import time
-from urllib import quote, unquote
-from random import randrange
-
-from module.utils import parseFileSize, remove_chars
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-class RealdebridCom(Hoster):
- __name__ = "RealdebridCom"
- __version__ = "0.49"
- __type__ = "hoster"
-
- __pattern__ = r"https?://.*real-debrid\..*"
- __description__ = """Real-Debrid.com hoster plugin"""
- __author_name__ = ("Devirex, Hazzard")
- __author_mail__ = ("naibaf_11@yahoo.de")
-
- def getFilename(self, url):
- try:
- name = unquote(url.rsplit("/", 1)[1])
- except IndexError:
- name = "Unknown_Filename..."
- if not name or name.endswith(".."): #incomplete filename, append random stuff
- name += "%s.tmp" % randrange(100,999)
- return name
-
- def init(self):
- self.tries = 0
- self.chunkLimit = 3
- self.resumeDownload = True
-
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your Real-debrid account or deactivate this plugin"))
- self.fail("No Real-debrid account provided")
-
- self.log.debug("Real-Debrid: Old URL: %s" % pyfile.url)
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- else:
- password = self.getPassword().splitlines()
- if not password: password = ""
- else: password = password[0]
-
- url = "http://real-debrid.com/ajax/unrestrict.php?lang=en&link=%s&password=%s&time=%s" % (quote(pyfile.url, ""), password, int(time()*1000))
- page = self.load(url)
- data = json_loads(page)
-
- self.logDebug("Returned Data: %s" % data)
-
- if data["error"] != 0:
- if data["message"] == "Your file is unavailable on the hoster.":
- self.offline()
- else:
- self.logWarning(data["message"])
- self.tempOffline()
- else:
- if self.pyfile.name is not None and self.pyfile.name.endswith('.tmp') and data["file_name"]:
- self.pyfile.name = data["file_name"]
- self.pyfile.size = parseFileSize(data["file_size"])
- new_url = data['generated_links'][0][-1]
-
- if self.getConfig("https"):
- new_url = new_url.replace("http://", "https://")
- else:
- new_url = new_url.replace("https://", "http://")
-
- self.log.debug("Real-Debrid: New URL: %s" % new_url)
-
- if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
- #only use when name wasnt already set
- pyfile.name = self.getFilename(new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload(
- {"error": "<title>An error occured while processing your request</title>"})
-
- if check == "error":
- #usual this download can safely be retried
- self.retry(reason="An error occured while generating link.", wait_time=60)
-
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+from urllib import quote, unquote
+from random import randrange
+
+from module.utils import parseFileSize, remove_chars
+from module.common.json_layer import json_loads
+from module.plugins.Hoster import Hoster
+
+class RealdebridCom(Hoster):
+ __name__ = "RealdebridCom"
+ __version__ = "0.49"
+ __type__ = "hoster"
+
+ __pattern__ = r"https?://.*real-debrid\..*"
+ __description__ = """Real-Debrid.com hoster plugin"""
+ __author_name__ = ("Devirex, Hazzard")
+ __author_mail__ = ("naibaf_11@yahoo.de")
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if not name or name.endswith(".."): #incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100,999)
+ return name
+
+ def init(self):
+ self.tries = 0
+ self.chunkLimit = 3
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your Real-debrid account or deactivate this plugin"))
+ self.fail("No Real-debrid account provided")
+
+ self.log.debug("Real-Debrid: Old URL: %s" % pyfile.url)
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ else:
+ password = self.getPassword().splitlines()
+ if not password: password = ""
+ else: password = password[0]
+
+ url = "http://real-debrid.com/ajax/unrestrict.php?lang=en&link=%s&password=%s&time=%s" % (quote(pyfile.url, ""), password, int(time()*1000))
+ page = self.load(url)
+ data = json_loads(page)
+
+ self.logDebug("Returned Data: %s" % data)
+
+ if data["error"] != 0:
+ if data["message"] == "Your file is unavailable on the hoster.":
+ self.offline()
+ else:
+ self.logWarning(data["message"])
+ self.tempOffline()
+ else:
+ if self.pyfile.name is not None and self.pyfile.name.endswith('.tmp') and data["file_name"]:
+ self.pyfile.name = data["file_name"]
+ self.pyfile.size = parseFileSize(data["file_size"])
+ new_url = data['generated_links'][0][-1]
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ self.log.debug("Real-Debrid: New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload(
+ {"error": "<title>An error occured while processing your request</title>"})
+
+ if check == "error":
+ #usual this download can safely be retried
+ self.retry(reason="An error occured while generating link.", wait_time=60)
+
diff --git a/module/plugins/hoster/TurbobitNet.py b/module/plugins/hoster/TurbobitNet.py
index 9de7f9bd0..b3b01c92b 100644
--- a/module/plugins/hoster/TurbobitNet.py
+++ b/module/plugins/hoster/TurbobitNet.py
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
"""
+ Copyright (C) 2012 pyLoad team
+ Copyright (C) 2012 JD-Team support@jdownloader.org
+
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,
@@ -17,8 +20,13 @@
"""
import re
+import random
+from urllib import quote
+from binascii import hexlify, unhexlify
+from Crypto.Cipher import ARC4
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from module.network.RequestFactory import getURL
+from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
from module.plugins.ReCaptcha import ReCaptcha
from pycurl import HTTPHEADER
@@ -27,29 +35,38 @@ class TurbobitNet(SimpleHoster):
__name__ = "TurbobitNet"
__type__ = "hoster"
__pattern__ = r"http://(?:\w*\.)?(turbobit.net|unextfiles.com)/(?:download/free/)?(?P<ID>\w+).*"
- __version__ = "0.05"
+ __version__ = "0.07"
__description__ = """Turbobit.net plugin"""
__author_name__ = ("zoidberg")
__author_mail__ = ("zoidberg@mujmail.cz")
-
+
FILE_INFO_PATTERN = r"<span class='file-icon1[^>]*>(?P<N>[^<]+)</span>\s*\((?P<S>[^\)]+)\)\s*</h1>" #long filenames are shortened
- FILE_NAME_PATTERN = r'<meta name="keywords" content="\s*(?P<N>[^,]+)' #full name but missing on page2
- FILE_OFFLINE_PATTERN = r'<h2>File Not Found</h2>'
- FILE_URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "turbobit.net")]
+ FILE_NAME_PATTERN = r'<meta name="keywords" content="\s+(?P<N>[^,]+)' #full name but missing on page2
+ FILE_OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File was not found'
+ FILE_URL_REPLACEMENTS = [(r"http://(?:\w*\.)?(turbobit.net|unextfiles.com)/(?:download/free/)?(?P<ID>\w+).*", "http://turbobit.net/\g<ID>.html")]
SH_COOKIES = [("turbobit.net", "user_lang", "en")]
-
+
CAPTCHA_KEY_PATTERN = r'src="http://api\.recaptcha\.net/challenge\?k=([^"]+)"'
DOWNLOAD_URL_PATTERN = r'(?P<url>/download/redirect/[^"\']+)'
LIMIT_WAIT_PATTERN = r'<div id="time-limit-text">\s*.*?<span id=\'timeout\'>(\d+)</span>'
- CAPTCHA_SRC_PATTERN = r'<img alt="Captcha" src="(.*?)"'
+ CAPTCHA_SRC_PATTERN = r'<img alt="Captcha" src="(.*?)"'
- def handleFree(self):
+ def handleFree(self):
self.url = "http://turbobit.net/download/free/%s" % self.file_info['ID']
- if not '/download/free/' in self.pyfile.url:
- self.html = self.load(self.url)
-
- recaptcha = ReCaptcha(self)
+ self.html = self.load(self.url)
+
+ rtUpdate = self.getRtUpdate()
+
+ self.solveCaptcha()
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.url = self.getDownloadUrl(rtUpdate)
+ self.wait()
+ self.html = self.load(self.url)
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
+ self.downloadFile()
+
+ def solveCaptcha(self):
for i in range(5):
found = re.search(self.LIMIT_WAIT_PATTERN, self.html)
if found:
@@ -57,12 +74,13 @@ class TurbobitNet(SimpleHoster):
self.setWait(wait_time, wait_time > 60)
self.wait()
self.retry()
-
+
action, inputs = self.parseHtmlForm("action='#'")
- if not inputs: self.parseError("inputs")
+ if not inputs: self.parseError("captcha form")
self.logDebug(inputs)
-
+
if inputs['captcha_type'] == 'recaptcha':
+ recaptcha = ReCaptcha(self)
found = re.search(self.CAPTCHA_KEY_PATTERN, self.html)
captcha_key = found.group(1) if found else '6LcTGLoSAAAAAHCWY9TTIrQfjUlxu6kZlTYP50_c'
inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(captcha_key)
@@ -70,36 +88,82 @@ class TurbobitNet(SimpleHoster):
found = re.search(self.CAPTCHA_SRC_PATTERN, self.html)
if not found: self.parseError('captcha')
captcha_url = found.group(1)
- inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
+ inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
self.logDebug(inputs)
self.html = self.load(self.url, post = inputs)
-
+
if not "<div class='download-timer-header'>" in self.html:
self.invalidCaptcha()
else:
self.correctCaptcha()
break
else: self.fail("Invalid captcha")
-
+
+ def getRtUpdate(self):
+ rtUpdate = self.getStorage("rtUpdate")
+ if not rtUpdate:
+ if self.getStorage("version") != self.__version__ or int(self.getStorage("timestamp", 0)) + 86400000 < timestamp():
+ # that's right, we are even using jdownloader updates
+ rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js")
+ rtUpdate = self.decrypt(rtUpdate.splitlines()[1])
+ # but we still need to fix the syntax to work with other engines than rhino
+ rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{',r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];',rtUpdate)
+ rtUpdate = re.sub(r"for\((\w+)=",r"for(var \1=", rtUpdate)
+
+ self.logDebug("rtUpdate")
+ self.setStorage("rtUpdate", rtUpdate)
+ self.setStorage("timestamp", timestamp())
+ self.setStorage("version", self.__version__)
+ else:
+ self.logError("Unable to download, wait for update...")
+ self.tempOffline()
+
+ return rtUpdate
+
+ def getDownloadUrl(self, rtUpdate):
self.req.http.lastURL = self.url
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- self.setWait(60, False)
- self.wait()
-
- self.html = self.load("http://turbobit.net/download/getLinkTimeout/" + self.file_info['ID'])
- self.downloadFile()
+
+ found = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html)
+ url = "http://turbobit.net%s%s" % (found.groups() if found else ('/files/timeout.js?ver=', ''.join(random.choice('0123456789ABCDEF') for x in range(32))))
+ fun = self.load(url)
+
+ self.setWait(65, False)
+
+ for b in [1,3]:
+ self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % (self.file_info['ID'], b, quote(fun), rtUpdate)
+
+ try:
+ out = self.js.eval(self.jscode)
+ self.logDebug("URL", self.js.engine, out)
+ if out.startswith('/download/'):
+ return "http://turbobit.net%s" % out.strip()
+ except Exception, e:
+ self.logError(e)
+ else:
+ if self.retries >= 2:
+ # retry with updated js
+ self.delStorage("rtUpdate")
+ self.retry()
+
+ def decrypt(self, data):
+ cipher = ARC4.new(hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
+ return unhexlify(cipher.encrypt(unhexlify(data)))
+ def getLocalTimeString():
+ lt = time.localtime()
+ tz = time.altzone if lt.tm_isdst else time.timezone
+ return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600)
+
def handlePremium(self):
self.logDebug("Premium download as user %s" % self.user)
self.downloadFile()
-
+
def downloadFile(self):
found = re.search(self.DOWNLOAD_URL_PATTERN, self.html)
- if not found: self.parseError("download link")
+ if not found: self.parseError("download link")
self.url = "http://turbobit.net" + found.group('url')
self.logDebug(self.url)
- self.download(self.url)
+ self.download(self.url)
getInfo = create_getInfo(TurbobitNet) \ No newline at end of file
diff --git a/module/plugins/hoster/UploadedTo.py b/module/plugins/hoster/UploadedTo.py
index 19ca4ba9d..3cb1e71ff 100644
--- a/module/plugins/hoster/UploadedTo.py
+++ b/module/plugins/hoster/UploadedTo.py
@@ -2,11 +2,10 @@
import re
-from module.utils import html_unescape, parseFileSize
+from module.utils import html_unescape, parseFileSize, chunks
from module.plugins.Hoster import Hoster
from module.network.RequestFactory import getURL
-from module.plugins.Plugin import chunks
from module.plugins.ReCaptcha import ReCaptcha
key = "bGhGMkllZXByd2VEZnU5Y2NXbHhYVlZ5cEE1bkEzRUw=".decode('base64')
diff --git a/module/plugins/hoster/XFileSharingPro.py b/module/plugins/hoster/XFileSharingPro.py
index 7ddbeb280..8e213e9bf 100644
--- a/module/plugins/hoster/XFileSharingPro.py
+++ b/module/plugins/hoster/XFileSharingPro.py
@@ -34,7 +34,7 @@ class XFileSharingPro(SimpleHoster):
__name__ = "XFileSharingPro"
__type__ = "hoster"
__pattern__ = r"^unmatchable$"
- __version__ = "0.09"
+ __version__ = "0.11"
__description__ = """XFileSharingPro common hoster base"""
__author_name__ = ("zoidberg")
__author_mail__ = ("zoidberg@mujmail.cz")
@@ -52,7 +52,6 @@ class XFileSharingPro(SimpleHoster):
RECAPTCHA_URL_PATTERN = r'http://[^"\']+?recaptcha[^"\']+?\?k=([^"\']+)"'
CAPTCHA_DIV_PATTERN = r'<b>Enter code.*?<div.*?>(.*?)</div>'
ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.*?)</'
- #DIRECT_LINK_PATTERN = r'This direct link.*?href=["\'](.*?)["\']'
def setup(self):
self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
@@ -74,25 +73,29 @@ class XFileSharingPro(SimpleHoster):
else:
self.fail("Only premium users can download from other hosters with %s" % self.HOSTER_NAME)
else:
- location = None
+ try:
+ self.html = self.load(pyfile.url, cookies = False, decode = True)
+ self.file_info = self.getFileInfo()
+ except PluginParseError:
+ self.file_info = None
+
+ self.req.http.lastURL = self.pyfile.url
self.req.http.c.setopt(FOLLOWLOCATION, 0)
self.html = self.load(self.pyfile.url, cookies = True, decode = True)
self.header = self.req.http.header
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
-
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ self.location = None
found = re.search("Location\s*:\s*(.*)", self.header, re.I)
if found and re.match(self.DIRECT_LINK_PATTERN, found.group(1)):
- location = found.group(1)
- self.html = self.load(pyfile.url, cookies = False, decode = True)
-
- try:
- self.file_info = self.getFileInfo()
- except PluginParseError:
- pyfile.name = html_unescape(unquote(urlparse(location if location else pyfile.url).path.split("/")[-1]))
-
- if location:
- self.startDownload(location)
+ self.location = found.group(1).strip()
+
+ if not self.file_info:
+ pyfile.name = html_unescape(unquote(urlparse(self.location if self.location else pyfile.url).path.split("/")[-1]))
+
+ if self.location:
+ self.startDownload(self.location)
elif self.premium:
self.handlePremium()
else:
diff --git a/module/plugins/hoster/YoutubeCom.py b/module/plugins/hoster/YoutubeCom.py
index 222e9bf84..3ba40e937 100644
--- a/module/plugins/hoster/YoutubeCom.py
+++ b/module/plugins/hoster/YoutubeCom.py
@@ -77,8 +77,8 @@ class YoutubeCom(Hoster):
self.logDebug("Found links: %s" % fmt_dict)
for fmt in fmt_dict.keys():
if fmt not in self.formats:
- self.logDebug("FMT not supported: %s" % fmt)
- del fmt_dict[fmt]
+ self.logDebug("FMT not supported: %s" % fmt)
+ del fmt_dict[fmt]
allowed = lambda x: self.getConfig(self.formats[x][0])
sel = lambda x: self.formats[x][3] #select quality index
diff --git a/module/plugins/hoster/ZeveraCom.py b/module/plugins/hoster/ZeveraCom.py
index cbedfcb68..8be725d2f 100644
--- a/module/plugins/hoster/ZeveraCom.py
+++ b/module/plugins/hoster/ZeveraCom.py
@@ -1,108 +1,108 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hoster import Hoster
-from module.utils import html_unescape
-from urllib import quote, unquote
-from time import sleep
-
-class ZeveraCom(Hoster):
- __name__ = "ZeveraCom"
- __version__ = "0.20"
- __type__ = "hoster"
- __pattern__ = r"http://zevera.com/.*"
- __description__ = """zevera.com hoster plugin"""
- __author_name__ = ("zoidberg")
- __author_mail__ = ("zoidberg@mujmail.cz")
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
- self.fail("No zevera.com account provided")
-
- self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
-
- if self.account.getAPIData(self.req, cmd = "checklink", olink = pyfile.url) != "Alive":
- self.fail("Offline or not downloadable - contact Zevera support")
-
- header = self.account.getAPIData(self.req, just_header = True, cmd="generatedownloaddirect", olink = pyfile.url)
- if not "location" in header:
- self.fail("Unable to initialize download - contact Zevera support")
-
- self.download(header['location'], disposition = True)
-
- check = self.checkDownload({"error" : 'action="ErrorDownload.aspx'})
- if check == "error":
- self.fail("Error response received - contact Zevera support")
-
- """
- # BitAPI not used - defunct, probably abandoned by Zevera
-
- api_url = "http://zevera.com/API.ashx"
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
- self.fail("No zevera.com account provided")
-
- self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
-
- last_size = retries = 0
- olink = self.pyfile.url #quote(self.pyfile.url.encode('utf_8'))
-
- for i in range(100):
- self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_request', olink = olink)
- self.checkAPIErrors(self.retData)
-
- if self.retData['FileInfo']['StatusID'] == 100:
- break
- elif self.retData['FileInfo']['StatusID'] == 99:
- self.fail('Failed to initialize download (99)')
- else:
- if self.retData['FileInfo']['Progress']['BytesReceived'] <= last_size:
- if retries >= 6:
- self.fail('Failed to initialize download (%d)' % self.retData['FileInfo']['StatusID'] )
- retries += 1
- else:
- retries = 0
-
- last_size = self.retData['FileInfo']['Progress']['BytesReceived']
-
- self.setWait(self.retData['Update_Wait'])
- self.wait()
-
- pyfile.name = self.retData['FileInfo']['RealFileName']
- pyfile.size = self.retData['FileInfo']['FileSizeInBytes']
-
- self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_start', FileID = self.retData['FileInfo']['FileID'])
- self.checkAPIErrors(self.retData)
-
- self.download(self.api_url, get = {
- 'cmd': "open_stream",
- 'login': self.account.loginname,
- 'pass': self.account.password,
- 'FileID': self.retData['FileInfo']['FileID'],
- 'startBytes': 0
- }
- )
-
- def checkAPIErrors(self, retData):
- if not retData:
- self.fail('Unknown API response')
-
- if retData['ErrorCode']:
- self.logError(retData['ErrorCode'], retData['ErrorMessage'])
- #self.fail('ERROR: ' + retData['ErrorMessage'])
-
- if self.pyfile.size / 1024000 > retData['AccountInfo']['AvailableTODAYTrafficForUseInMBytes']:
- self.logWarning("Not enough data left to download the file")
-
- def crazyDecode(self, ustring):
- # accepts decoded ie. unicode string - API response is double-quoted, double-utf8-encoded
- # no idea what the proper order of calling these functions would be :-/
- return html_unescape(unquote(unquote(ustring.replace('@DELIMITER@','#'))).encode('raw_unicode_escape').decode('utf-8'))
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from module.plugins.Hoster import Hoster
+from module.utils import html_unescape
+from urllib import quote, unquote
+from time import sleep
+
+class ZeveraCom(Hoster):
+ __name__ = "ZeveraCom"
+ __version__ = "0.20"
+ __type__ = "hoster"
+ __pattern__ = r"http://zevera.com/.*"
+ __description__ = """zevera.com hoster plugin"""
+ __author_name__ = ("zoidberg")
+ __author_mail__ = ("zoidberg@mujmail.cz")
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
+ self.fail("No zevera.com account provided")
+
+ self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
+
+ if self.account.getAPIData(self.req, cmd = "checklink", olink = pyfile.url) != "Alive":
+ self.fail("Offline or not downloadable - contact Zevera support")
+
+ header = self.account.getAPIData(self.req, just_header = True, cmd="generatedownloaddirect", olink = pyfile.url)
+ if not "location" in header:
+ self.fail("Unable to initialize download - contact Zevera support")
+
+ self.download(header['location'], disposition = True)
+
+ check = self.checkDownload({"error" : 'action="ErrorDownload.aspx'})
+ if check == "error":
+ self.fail("Error response received - contact Zevera support")
+
+ """
+ # BitAPI not used - defunct, probably abandoned by Zevera
+
+ api_url = "http://zevera.com/API.ashx"
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
+ self.fail("No zevera.com account provided")
+
+ self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
+
+ last_size = retries = 0
+ olink = self.pyfile.url #quote(self.pyfile.url.encode('utf_8'))
+
+ for i in range(100):
+ self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_request', olink = olink)
+ self.checkAPIErrors(self.retData)
+
+ if self.retData['FileInfo']['StatusID'] == 100:
+ break
+ elif self.retData['FileInfo']['StatusID'] == 99:
+ self.fail('Failed to initialize download (99)')
+ else:
+ if self.retData['FileInfo']['Progress']['BytesReceived'] <= last_size:
+ if retries >= 6:
+ self.fail('Failed to initialize download (%d)' % self.retData['FileInfo']['StatusID'] )
+ retries += 1
+ else:
+ retries = 0
+
+ last_size = self.retData['FileInfo']['Progress']['BytesReceived']
+
+ self.setWait(self.retData['Update_Wait'])
+ self.wait()
+
+ pyfile.name = self.retData['FileInfo']['RealFileName']
+ pyfile.size = self.retData['FileInfo']['FileSizeInBytes']
+
+ self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_start', FileID = self.retData['FileInfo']['FileID'])
+ self.checkAPIErrors(self.retData)
+
+ self.download(self.api_url, get = {
+ 'cmd': "open_stream",
+ 'login': self.account.loginname,
+ 'pass': self.account.password,
+ 'FileID': self.retData['FileInfo']['FileID'],
+ 'startBytes': 0
+ }
+ )
+
+ def checkAPIErrors(self, retData):
+ if not retData:
+ self.fail('Unknown API response')
+
+ if retData['ErrorCode']:
+ self.logError(retData['ErrorCode'], retData['ErrorMessage'])
+ #self.fail('ERROR: ' + retData['ErrorMessage'])
+
+ if self.pyfile.size / 1024000 > retData['AccountInfo']['AvailableTODAYTrafficForUseInMBytes']:
+ self.logWarning("Not enough data left to download the file")
+
+ def crazyDecode(self, ustring):
+ # accepts decoded ie. unicode string - API response is double-quoted, double-utf8-encoded
+ # no idea what the proper order of calling these functions would be :-/
+ return html_unescape(unquote(unquote(ustring.replace('@DELIMITER@','#'))).encode('raw_unicode_escape').decode('utf-8'))
""" \ No newline at end of file
diff --git a/module/plugins/internal/AbstractExtractor.py b/module/plugins/internal/AbstractExtractor.py
index 2130f910e..3cd635eff 100644
--- a/module/plugins/internal/AbstractExtractor.py
+++ b/module/plugins/internal/AbstractExtractor.py
@@ -13,7 +13,7 @@ class WrongPassword(Exception):
class AbtractExtractor:
@staticmethod
def checkDeps():
- """ Check if system statisfy dependencies
+ """ Check if system satisfies dependencies
:return: boolean
"""
return True
@@ -21,7 +21,7 @@ class AbtractExtractor:
@staticmethod
def getTargets(files_ids):
""" Filter suited targets from list of filename id tuple list
- :param files_ids: List of filepathes
+ :param files_ids: List of file paths
:return: List of targets, id tuple list
"""
raise NotImplementedError
@@ -30,10 +30,10 @@ class AbtractExtractor:
def __init__(self, m, file, out, fullpath, overwrite, renice):
"""Initialize extractor for specific file
- :param m: ExtractArchive Hook plugin
- :param file: Absolute filepath
+ :param m: ExtractArchive addon plugin
+ :param file: Absolute file path
:param out: Absolute path to destination directory
- :param fullpath: extract to fullpath
+ :param fullpath: Extract to fullpath
:param overwrite: Overwrite existing archives
:param renice: Renice value
"""
@@ -52,7 +52,7 @@ class AbtractExtractor:
def checkArchive(self):
- """Check if password if needed. Raise ArchiveError if integrity is
+ """Check if password is needed. Raise ArchiveError if integrity is
questionable.
:return: boolean
diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py
deleted file mode 100644
index e9e321c06..000000000
--- a/module/plugins/internal/MultiHoster.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.utils import remove_chars
-from module.plugins.Hook import Hook
-
-class MultiHoster(Hook):
- """
- Generic MultiHoster plugin
- """
-
- __version__ = "0.12"
-
- interval = 0
- hosters = []
- replacements = []
- supported = []
- ignored = []
-
- def getHosterCached(self):
- if not self.hosters:
-
- try:
- self.hosters = [x.strip() for x in self.getHoster()]
- self.hosters = filter(lambda x: x and x not in self.ignored, self.hosters)
- except Exception, e:
- self.logError("%s" % str(e))
- return []
-
- for rep in self.replacements:
- if rep[0] in self.hosters:
- self.hosters.remove(rep[0])
- if rep[1] not in self.hosters:
- self.hosters.append(rep[1])
-
- return self.hosters
-
-
- def getHoster(self):
- """Load list of supported hoster
-
- :return: List of domain names
- """
- raise NotImplementedError
-
- def coreReady(self):
- pluginMap = {}
- for name in self.core.pluginManager.hosterPlugins.keys():
- pluginMap[name.lower()] = name
-
- new_supported = []
-
- for hoster in self.getHosterCached():
- name = remove_chars(hoster.lower(), "-.")
-
- if name in pluginMap:
- self.supported.append(pluginMap[name])
- else:
- new_supported.append(hoster)
-
- if not self.supported and not new_supported:
- self.logError(_("No Hoster loaded"))
- return
-
- module = self.core.pluginManager.getPlugin(self.__name__)
- klass = getattr(module, self.__name__)
-
- # inject plugin plugin
- self.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(self.supported)))
- for hoster in self.supported:
- dict = self.core.pluginManager.hosterPlugins[hoster]
- dict["new_module"] = module
- dict["new_name"] = self.__name__
-
- self.logDebug("New Hosters: %s" % ", ".join(sorted(new_supported)))
-
- # create new regexp
- if not klass.__pattern__:
- regexp = r".*(%s).*" % "|".join([x.replace(".", "\\.") for x in new_supported])
- else:
- regexp = r".*(%s).*" % "|".join([klass.__pattern__] + [x.replace(".", "\\.") for x in new_supported])
-
- dict = self.core.pluginManager.hosterPlugins[self.__name__]
- dict["pattern"] = regexp
- dict["re"] = re.compile(regexp)
-
-
- def unload(self):
- for hoster in self.supported:
- dict = self.core.pluginManager.hosterPlugins[hoster]
- if "module" in dict:
- del dict["module"]
-
- del dict["new_module"]
- del dict["new_name"]
diff --git a/module/plugins/captcha/NetloadIn.py b/module/plugins/internal/NetloadInOCR.py
index 7f2e6a8d1..e50978701 100644
--- a/module/plugins/captcha/NetloadIn.py
+++ b/module/plugins/internal/NetloadInOCR.py
@@ -1,7 +1,10 @@
-from captcha import OCR
+# -*- coding: utf-8 -*-
+
+from OCR import OCR
+
+class NetloadInOCR(OCR):
+ __version__ = 0.1
-class NetloadIn(OCR):
- __name__ = "NetloadIn"
def __init__(self):
OCR.__init__(self)
@@ -18,7 +21,7 @@ class NetloadIn(OCR):
if __name__ == '__main__':
import urllib
- ocr = NetloadIn()
+ ocr = NetloadInOCR()
urllib.urlretrieve("http://netload.in/share/includes/captcha.php", "captcha.png")
print ocr.get_captcha('captcha.png')
diff --git a/module/plugins/captcha/captcha.py b/module/plugins/internal/OCR.py
index 4cbb736c1..9f8b7ef8c 100644
--- a/module/plugins/captcha/captcha.py
+++ b/module/plugins/internal/OCR.py
@@ -33,8 +33,7 @@ import JpegImagePlugin
class OCR(object):
-
- __name__ = "OCR"
+ __version__ = 0.1
def __init__(self):
self.logger = logging.getLogger("log")
diff --git a/module/plugins/captcha/ShareonlineBiz.py b/module/plugins/internal/ShareonlineBizOCR.py
index b07fb9b0f..c5c2e92e8 100644
--- a/module/plugins/captcha/ShareonlineBiz.py
+++ b/module/plugins/internal/ShareonlineBizOCR.py
@@ -17,10 +17,10 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
###
-from captcha import OCR
+from OCR import OCR
-class ShareonlineBiz(OCR):
- __name__ = "ShareonlineBiz"
+class ShareonlineBizOCR(OCR):
+ __version__ = 0.1
def __init__(self):
OCR.__init__(self)
@@ -48,6 +48,6 @@ class ShareonlineBiz(OCR):
if __name__ == '__main__':
import urllib
- ocr = ShareonlineBiz()
+ ocr = ShareonlineBizOCR()
urllib.urlretrieve("http://www.share-online.biz/captcha.php", "captcha.jpeg")
print ocr.get_captcha('captcha.jpeg')
diff --git a/module/plugins/internal/SimpleHoster.py b/module/plugins/internal/SimpleHoster.py
index 566615120..5056b22b2 100644
--- a/module/plugins/internal/SimpleHoster.py
+++ b/module/plugins/internal/SimpleHoster.py
@@ -141,7 +141,7 @@ class SimpleHoster(Hoster):
or FILE_NAME_INFO = r'(?P<N>file_name)'
and FILE_SIZE_INFO = r'(?P<S>file_size) (?P<U>units)'
FILE_OFFLINE_PATTERN = r'File (deleted|not found)'
- TEMP_OFFLINE_PATTERN = r'Server maintainance'
+ TEMP_OFFLINE_PATTERN = r'Server maintenance'
"""
FILE_SIZE_REPLACEMENTS = []
diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py
index 240dc0233..53995a083 100644
--- a/module/plugins/internal/UnRar.py
+++ b/module/plugins/internal/UnRar.py
@@ -19,11 +19,10 @@
import os
import re
-from os.path import join
from glob import glob
from subprocess import Popen, PIPE
-from module.utils import save_join, decode
+from module.utils.fs import save_join, decode, fs_encode
from module.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError
class UnRar(AbtractExtractor):
@@ -39,7 +38,7 @@ class UnRar(AbtractExtractor):
@staticmethod
def checkDeps():
if os.name == "nt":
- UnRar.CMD = join(pypath, "UnRAR.exe")
+ UnRar.CMD = save_join(pypath, "UnRAR.exe")
p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
p.communicate()
else:
@@ -80,7 +79,7 @@ class UnRar(AbtractExtractor):
self.password = "" #save the correct password
def checkArchive(self):
- p = self.call_unrar("l", "-v", self.file)
+ p = self.call_unrar("l", "-v", fs_encode(self.file))
out, err = p.communicate()
if self.re_wrongpwd.search(err):
self.passwordProtected = True
@@ -102,7 +101,7 @@ class UnRar(AbtractExtractor):
def checkPassword(self, password):
#at this point we can only verify header protected files
if self.headerProtected:
- p = self.call_unrar("l", "-v", self.file, password=password)
+ p = self.call_unrar("l", "-v", fs_encode(self.file), password=password)
out, err = p.communicate()
if self.re_wrongpwd.search(err):
return False
@@ -115,7 +114,7 @@ class UnRar(AbtractExtractor):
# popen thinks process is still alive (just like pexpect) - very strange behavior
# so for now progress can not be determined correctly
- p = self.call_unrar(command, self.file, self.out, password=password)
+ p = self.call_unrar(command, fs_encode(self.file), self.out, password=password)
renice(p.pid, self.renice)
progress(0)
@@ -143,7 +142,7 @@ class UnRar(AbtractExtractor):
def listContent(self):
command = "vb" if self.fullpath else "lb"
- p = self.call_unrar(command, "-v", self.file, password=self.password)
+ p = self.call_unrar(command, "-v", fs_encode(self.file), password=self.password)
out, err = p.communicate()
if "Cannot open" in err:
@@ -178,7 +177,7 @@ class UnRar(AbtractExtractor):
#NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
call = [self.CMD, command] + args + list(xargs)
- self.m.logDebug(" ".join(call))
+ self.m.logDebug(" ".join([decode(arg) for arg in call]))
p = Popen(call, stdout=PIPE, stderr=PIPE)
diff --git a/module/remote/socketbackend/create_ttypes.py b/module/remote/socketbackend/create_ttypes.py
index 05662cb50..8fd59f194 100644
--- a/module/remote/socketbackend/create_ttypes.py
+++ b/module/remote/socketbackend/create_ttypes.py
@@ -74,7 +74,7 @@ class BaseObject(object):
f.write("\n")
- f.write("class Iface:\n")
+ f.write("class Iface(object):\n")
for name in dir(Iface):
if name.startswith("_"): continue
diff --git a/module/remote/socketbackend/ttypes.py b/module/remote/socketbackend/ttypes.py
index f8ea121fa..06f051fc7 100644
--- a/module/remote/socketbackend/ttypes.py
+++ b/module/remote/socketbackend/ttypes.py
@@ -6,133 +6,169 @@
class BaseObject(object):
__slots__ = []
-class Destination:
- Collector = 0
- Queue = 1
-
class DownloadStatus:
- Aborted = 9
- Custom = 11
- Decrypting = 10
- Downloading = 12
- Failed = 8
- Finished = 0
+ Aborted = 12
+ Custom = 15
+ Decrypting = 13
+ Downloading = 10
+ Failed = 7
+ Finished = 5
+ NA = 0
Offline = 1
Online = 2
- Processing = 13
+ Paused = 4
+ Processing = 14
Queued = 3
- Skipped = 4
- Starting = 7
- TempOffline = 6
- Unknown = 14
- Waiting = 5
+ Skipped = 6
+ Starting = 8
+ TempOffline = 11
+ Unknown = 16
+ Waiting = 9
-class ElementType:
- File = 1
- Package = 0
+class FileStatus:
+ Missing = 1
+ Ok = 0
+ Remote = 2
class Input:
- BOOL = 4
- CHOICE = 6
- CLICK = 5
- LIST = 8
- MULTIPLE = 7
- NONE = 0
- PASSWORD = 3
- TABLE = 9
- TEXT = 1
- TEXTBOX = 2
+ Bool = 4
+ Click = 5
+ List = 8
+ Multiple = 7
+ NA = 0
+ Password = 3
+ Select = 6
+ Table = 9
+ Text = 1
+ Textbox = 2
+
+class MediaType:
+ All = 0
+ Archive = 32
+ Audio = 2
+ Document = 16
+ Image = 4
+ Other = 1
+ Video = 8
class Output:
- CAPTCHA = 1
- NOTIFICATION = 4
- QUESTION = 2
+ All = 0
+ Captcha = 2
+ Notification = 1
+ Query = 4
+
+class PackageStatus:
+ Folder = 2
+ Ok = 0
+ Paused = 1
+ Remote = 3
+
+class Permission:
+ Accounts = 16
+ Add = 1
+ All = 0
+ Delete = 2
+ Download = 8
+ Interaction = 32
+ Modify = 4
+ Plugins = 64
+
+class Role:
+ Admin = 0
+ User = 1
class AccountInfo(BaseObject):
- __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type']
+ __slots__ = ['plugin', 'loginname', 'owner', 'valid', 'validuntil', 'trafficleft', 'maxtraffic', 'premium', 'activated', 'shared', 'options']
- 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
+ def __init__(self, plugin=None, loginname=None, owner=None, valid=None, validuntil=None, trafficleft=None, maxtraffic=None, premium=None, activated=None, shared=None, options=None):
+ self.plugin = plugin
+ self.loginname = loginname
+ self.owner = owner
self.valid = valid
+ self.validuntil = validuntil
self.trafficleft = trafficleft
self.maxtraffic = maxtraffic
self.premium = premium
- self.type = type
+ self.activated = activated
+ self.shared = shared
+ self.options = options
-class CaptchaTask(BaseObject):
- __slots__ = ['tid', 'data', 'type', 'resultType']
+class AddonInfo(BaseObject):
+ __slots__ = ['func_name', 'description', 'value']
- def __init__(self, tid=None, data=None, type=None, resultType=None):
- self.tid = tid
- self.data = data
- self.type = type
- self.resultType = resultType
+ def __init__(self, func_name=None, description=None, value=None):
+ self.func_name = func_name
+ self.description = description
+ self.value = value
-class ConfigItem(BaseObject):
- __slots__ = ['name', 'description', 'value', 'type']
+class AddonService(BaseObject):
+ __slots__ = ['func_name', 'description', 'arguments', 'media']
- def __init__(self, name=None, description=None, value=None, type=None):
- self.name = name
+ def __init__(self, func_name=None, description=None, arguments=None, media=None):
+ self.func_name = func_name
self.description = description
- self.value = value
- self.type = type
+ self.arguments = arguments
+ self.media = media
-class ConfigSection(BaseObject):
- __slots__ = ['name', 'description', 'items', 'outline']
+class ConfigHolder(BaseObject):
+ __slots__ = ['name', 'label', 'description', 'long_description', 'items', 'info', 'handler']
- def __init__(self, name=None, description=None, items=None, outline=None):
+ def __init__(self, name=None, label=None, description=None, long_description=None, items=None, info=None, handler=None):
self.name = name
+ self.label = label
self.description = description
+ self.long_description = long_description
self.items = items
- self.outline = outline
+ self.info = info
+ self.handler = handler
-class DownloadInfo(BaseObject):
- __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
+class ConfigInfo(BaseObject):
+ __slots__ = ['name', 'label', 'description', 'saved', 'activated']
- 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
+ def __init__(self, name=None, label=None, description=None, saved=None, activated=None):
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
+ self.label = label
+ self.description = description
+ self.saved = saved
+ self.activated = activated
-class EventInfo(BaseObject):
- __slots__ = ['eventname', 'id', 'type', 'destination']
+class ConfigItem(BaseObject):
+ __slots__ = ['name', 'label', 'description', 'type', 'default_value', 'value']
- def __init__(self, eventname=None, id=None, type=None, destination=None):
- self.eventname = eventname
- self.id = id
+ def __init__(self, name=None, label=None, description=None, type=None, default_value=None, value=None):
+ self.name = name
+ self.label = label
+ self.description = description
self.type = type
- self.destination = destination
+ self.default_value = default_value
+ self.value = value
-class FileData(BaseObject):
- __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order']
+class DownloadInfo(BaseObject):
+ __slots__ = ['url', 'plugin', 'hash', 'status', 'statusmsg', 'error']
- 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
+ def __init__(self, url=None, plugin=None, hash=None, status=None, statusmsg=None, error=None):
self.url = url
- self.name = name
self.plugin = plugin
- self.size = size
- self.format_size = format_size
+ self.hash = hash
self.status = status
self.statusmsg = statusmsg
- self.packageID = packageID
self.error = error
- self.order = order
+
+class DownloadProgress(BaseObject):
+ __slots__ = ['fid', 'pid', 'speed', 'status']
+
+ def __init__(self, fid=None, pid=None, speed=None, status=None):
+ self.fid = fid
+ self.pid = pid
+ self.speed = speed
+ self.status = status
+
+class EventInfo(BaseObject):
+ __slots__ = ['eventname', 'event_args']
+
+ def __init__(self, eventname=None, event_args=None):
+ self.eventname = eventname
+ self.event_args = event_args
class FileDoesNotExists(Exception):
__slots__ = ['fid']
@@ -140,20 +176,45 @@ class FileDoesNotExists(Exception):
def __init__(self, fid=None):
self.fid = fid
+class FileInfo(BaseObject):
+ __slots__ = ['fid', 'name', 'package', 'owner', 'size', 'status', 'media', 'added', 'fileorder', 'download']
+
+ def __init__(self, fid=None, name=None, package=None, owner=None, size=None, status=None, media=None, added=None, fileorder=None, download=None):
+ self.fid = fid
+ self.name = name
+ self.package = package
+ self.owner = owner
+ self.size = size
+ self.status = status
+ self.media = media
+ self.added = added
+ self.fileorder = fileorder
+ self.download = download
+
class InteractionTask(BaseObject):
- __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin']
+ __slots__ = ['iid', 'input', 'data', 'output', 'default_value', 'title', 'description', 'plugin']
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None):
+ def __init__(self, iid=None, input=None, data=None, output=None, default_value=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.output = output
+ self.default_value = default_value
self.title = title
self.description = description
self.plugin = plugin
+class LinkStatus(BaseObject):
+ __slots__ = ['url', 'name', 'plugin', 'size', 'status', 'packagename']
+
+ def __init__(self, url=None, name=None, plugin=None, size=None, status=None, packagename=None):
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.status = status
+ self.packagename = packagename
+
class OnlineCheck(BaseObject):
__slots__ = ['rid', 'data']
@@ -161,39 +222,52 @@ class OnlineCheck(BaseObject):
self.rid = rid
self.data = data
-class OnlineStatus(BaseObject):
- __slots__ = ['name', 'plugin', 'packagename', 'status', 'size']
+class PackageDoesNotExists(Exception):
+ __slots__ = ['pid']
- 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
+ def __init__(self, pid=None):
+ self.pid = pid
-class PackageData(BaseObject):
- __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids']
+class PackageInfo(BaseObject):
+ __slots__ = ['pid', 'name', 'folder', 'root', 'owner', 'site', 'comment', 'password', 'added', 'status', 'packageorder', 'stats', 'fids', 'pids']
- 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):
+ def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, status=None, packageorder=None, stats=None, fids=None, pids=None):
self.pid = pid
self.name = name
self.folder = folder
+ self.root = root
+ self.owner = owner
self.site = site
+ self.comment = comment
self.password = password
- self.dest = dest
- self.order = order
+ self.added = added
+ self.status = status
+ self.packageorder = packageorder
+ self.stats = stats
+ self.fids = fids
+ self.pids = pids
+
+class PackageStats(BaseObject):
+ __slots__ = ['linkstotal', 'linksdone', 'sizetotal', 'sizedone']
+
+ def __init__(self, linkstotal=None, linksdone=None, sizetotal=None, sizedone=None):
+ self.linkstotal = linkstotal
self.linksdone = linksdone
- self.sizedone = sizedone
self.sizetotal = sizetotal
- self.linkstotal = linkstotal
- self.links = links
- self.fids = fids
+ self.sizedone = sizedone
-class PackageDoesNotExists(Exception):
- __slots__ = ['pid']
+class ProgressInfo(BaseObject):
+ __slots__ = ['plugin', 'name', 'statusmsg', 'eta', 'format_eta', 'done', 'total', 'download']
- def __init__(self, pid=None):
- self.pid = pid
+ def __init__(self, plugin=None, name=None, statusmsg=None, eta=None, format_eta=None, done=None, total=None, download=None):
+ self.plugin = plugin
+ self.name = name
+ self.statusmsg = statusmsg
+ self.eta = eta
+ self.format_eta = format_eta
+ self.done = done
+ self.total = total
+ self.download = download
class ServerStatus(BaseObject):
__slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect']
@@ -207,15 +281,6 @@ class ServerStatus(BaseObject):
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']
@@ -229,22 +294,57 @@ class ServiceException(Exception):
def __init__(self, msg=None):
self.msg = msg
+class TreeCollection(BaseObject):
+ __slots__ = ['root', 'files', 'packages']
+
+ def __init__(self, root=None, files=None, packages=None):
+ self.root = root
+ self.files = files
+ self.packages = packages
+
class UserData(BaseObject):
- __slots__ = ['name', 'email', 'role', 'permission', 'templateName']
+ __slots__ = ['uid', 'name', 'email', 'role', 'permission', 'folder', 'traffic', 'dllimit', 'dlquota', 'hddquota', 'user', 'templateName']
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None):
+ def __init__(self, uid=None, name=None, email=None, role=None, permission=None, folder=None, traffic=None, dllimit=None, dlquota=None, hddquota=None, user=None, templateName=None):
+ self.uid = uid
self.name = name
self.email = email
self.role = role
self.permission = permission
+ self.folder = folder
+ self.traffic = traffic
+ self.dllimit = dllimit
+ self.dlquota = dlquota
+ self.hddquota = hddquota
+ self.user = user
self.templateName = templateName
-class Iface:
- def addFiles(self, pid, links):
+class UserDoesNotExists(Exception):
+ __slots__ = ['user']
+
+ def __init__(self, user=None):
+ self.user = user
+
+class Iface(object):
+ def addFromCollector(self, name, paused):
+ pass
+ def addLinks(self, pid, links):
+ pass
+ def addLocalFile(self, pid, name, path):
+ pass
+ def addPackage(self, name, links, password):
pass
- def addPackage(self, name, links, dest):
+ def addPackageChild(self, name, links, password, root, paused):
pass
- def call(self, info):
+ def addPackageP(self, name, links, password, paused):
+ pass
+ def addToCollector(self, links):
+ pass
+ def addUser(self, username, password):
+ pass
+ def callAddon(self, plugin, func, arguments):
+ pass
+ def callAddonHandler(self, plugin, func, pid_or_fid):
pass
def checkOnlineStatus(self, urls):
pass
@@ -252,15 +352,27 @@ class Iface:
pass
def checkURLs(self, urls):
pass
- def deleteFiles(self, fids):
+ def configurePlugin(self, plugin):
pass
- def deleteFinished(self):
+ def createPackage(self, name, folder, root, password, site, comment, paused):
+ pass
+ def deleteCollLink(self, url):
+ pass
+ def deleteCollPack(self, name):
+ pass
+ def deleteConfig(self, plugin):
+ pass
+ def deleteFiles(self, fids):
pass
def deletePackages(self, pids):
pass
+ def findFiles(self, pattern):
+ pass
def freeSpace(self):
pass
- def generateAndAddPackages(self, links, dest):
+ def generateAndAddPackages(self, links, paused):
+ pass
+ def generateDownloadLink(self, fid, timeout):
pass
def generatePackages(self, links):
pass
@@ -268,53 +380,53 @@ class Iface:
pass
def getAccounts(self, refresh):
pass
- def getAllInfo(self):
+ def getAddonHandler(self):
pass
- def getAllUserData(self):
+ def getAllFiles(self):
pass
- def getCaptchaTask(self, exclusive):
+ def getAllInfo(self):
pass
- def getCaptchaTaskStatus(self, tid):
+ def getAllUnfinishedFiles(self):
pass
- def getCollector(self):
+ def getAllUserData(self):
pass
- def getCollectorData(self):
+ def getCollector(self):
pass
def getConfig(self):
pass
- def getConfigValue(self, category, option, section):
- pass
def getEvents(self, uuid):
pass
- def getFileData(self, fid):
+ def getFileInfo(self, fid):
pass
- def getFileOrder(self, pid):
+ def getFileTree(self, pid, full):
pass
- def getInfoByPlugin(self, plugin):
+ def getGlobalPlugins(self):
pass
- def getLog(self, offset):
+ def getInfoByPlugin(self, plugin):
pass
- def getPackageData(self, pid):
+ def getInteractionTask(self, mode):
pass
- def getPackageInfo(self, pid):
+ def getLog(self, offset):
pass
- def getPackageOrder(self, destination):
+ def getNotifications(self):
pass
- def getPluginConfig(self):
+ def getPackageContent(self, pid):
pass
- def getQueue(self):
+ def getPackageInfo(self, pid):
pass
- def getQueueData(self):
+ def getProgressInfo(self):
pass
def getServerVersion(self):
pass
- def getServices(self):
+ def getUnfinishedFileTree(self, pid, full):
+ pass
+ def getUserData(self):
pass
- def getUserData(self, username, password):
+ def getUserPlugins(self):
pass
- def hasService(self, plugin, func):
+ def hasAddonHandler(self, plugin, func):
pass
- def isCaptchaWaiting(self):
+ def isInteractionWaiting(self, mode):
pass
def isTimeDownload(self):
pass
@@ -326,11 +438,11 @@ class Iface:
pass
def moveFiles(self, fids, pid):
pass
- def movePackage(self, destination, pid):
+ def movePackage(self, pid, root):
pass
- def orderFile(self, fid, position):
+ def orderFiles(self, fids, pid, position):
pass
- def orderPackage(self, pid, position):
+ def orderPackage(self, pids, position):
pass
def parseURLs(self, html, url):
pass
@@ -338,14 +450,14 @@ class Iface:
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 removeUser(self, uid):
+ pass
+ def renameCollPack(self, name, new_name):
+ pass
def restart(self):
pass
def restartFailed(self):
@@ -354,15 +466,19 @@ class Iface:
pass
def restartPackage(self, pid):
pass
- def setCaptchaResult(self, tid, result):
+ def saveConfig(self, config):
pass
- def setConfigValue(self, category, option, value, section):
+ def setConfigHandler(self, plugin, iid, value):
+ pass
+ def setInteractionResult(self, iid, result):
pass
def setPackageData(self, pid, data):
pass
- def setPackageName(self, pid, name):
+ def setPackageFolder(self, pid, path):
+ pass
+ def setPackagePaused(self, pid, paused):
pass
- def statusDownloads(self):
+ def setPassword(self, username, old_password, new_password):
pass
def statusServer(self):
pass
@@ -376,7 +492,11 @@ class Iface:
pass
def unpauseServer(self):
pass
- def updateAccount(self, plugin, account, password, options):
+ def updateAccount(self, plugin, account, password):
+ pass
+ def updateAccountInfo(self, account):
+ pass
+ def updateUserData(self, data):
pass
def uploadContainer(self, filename, data):
pass
diff --git a/module/remote/thriftbackend/Processor.py b/module/remote/thriftbackend/Processor.py
index a8b87c82c..6f822e98f 100644
--- a/module/remote/thriftbackend/Processor.py
+++ b/module/remote/thriftbackend/Processor.py
@@ -2,6 +2,7 @@
from thriftgen.pyload import Pyload
+#TODO: new login
class Processor(Pyload.Processor):
def __init__(self, *args, **kwargs):
Pyload.Processor.__init__(self, *args, **kwargs)
diff --git a/module/remote/thriftbackend/Socket.py b/module/remote/thriftbackend/Socket.py
index 2243f9df2..2a84004ea 100644
--- a/module/remote/thriftbackend/Socket.py
+++ b/module/remote/thriftbackend/Socket.py
@@ -8,7 +8,9 @@ from time import sleep
from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException
-WantReadError = Exception #overwritten when ssl is used
+#overwritten when ssl is used
+WantReadError = None
+WantWriteError = None
class SecureSocketConnection:
def __init__(self, connection):
@@ -30,14 +32,14 @@ class SecureSocketConnection:
def send(self, buff):
try:
return self.__dict__["connection"].send(buff)
- except WantReadError:
+ except (WantReadError, WantWriteError):
sleep(0.1)
return self.send(buff)
def recv(self, buff):
try:
return self.__dict__["connection"].recv(buff)
- except WantReadError:
+ except (WantReadError, WantWriteError):
sleep(0.1)
return self.recv(buff)
@@ -47,9 +49,13 @@ class Socket(TSocket):
self.ssl = ssl
def open(self):
+ global WantReadError, WantWriteError
+
if self.ssl:
SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
WantReadError = SSL.WantReadError
+ WantWriteError = SSL.WantWriteError
+
ctx = SSL.Context(SSL.SSLv23_METHOD)
c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
c.set_connect_state()
@@ -68,7 +74,7 @@ class Socket(TSocket):
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
+ # freebsd and Mach don't follow POSIX semantics 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.
diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift
index 1542e651a..8257107b4 100644
--- a/module/remote/thriftbackend/pyload.thrift
+++ b/module/remote/thriftbackend/pyload.thrift
@@ -2,85 +2,118 @@ namespace java org.pyload.thrift
typedef i32 FileID
typedef i32 PackageID
-typedef i32 TaskID
typedef i32 ResultID
typedef i32 InteractionID
+typedef i32 UserID
+typedef i64 UTCDate
+typedef i64 ByteCount
typedef list<string> LinkList
typedef string PluginName
-typedef byte Progress
-typedef byte Priority
-
+typedef string JSONString
+// NA - Not Available
enum DownloadStatus {
- Finished
+ NA,
Offline,
Online,
Queued,
+ Paused,
+ Finished,
Skipped,
+ Failed,
+ Starting,
Waiting,
+ Downloading,
TempOffline,
- Starting,
- Failed,
Aborted,
Decrypting,
- Custom,
- Downloading,
Processing,
+ Custom,
Unknown
}
-enum Destination {
- Collector,
- Queue
+enum MediaType {
+ All = 0
+ Other = 1,
+ Audio = 2,
+ Image = 4,
+ Video = 8,
+ Document = 16,
+ Archive = 32,
+}
+
+enum FileStatus {
+ Ok,
+ Missing,
+ Remote, // file is available at remote location
}
-enum ElementType {
- Package,
- File
+enum PackageStatus {
+ Ok,
+ Paused,
+ Folder,
+ Remote,
}
// 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
+// Todo: how about: time, int, ip, file, s.o.
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
+ NA,
+ Text,
+ Textbox,
+ Password,
+ Bool, // confirm like, yes or no dialog
+ Click, // for positional captchas
+ Select, // select 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,
+ All = 0,
+ Notification = 1,
+ Captcha = 2,
+ Query = 4,
}
-struct DownloadInfo {
- 1: FileID fid,
+enum Permission {
+ All = 0, // requires no permission, but login
+ Add = 1, // can add packages
+ Delete = 2, // can delete packages
+ Modify = 4, // modify some attribute of downloads
+ Download = 8, // can download from webinterface
+ Accounts = 16, // can access accounts
+ Interaction = 32, // can interact with plugins
+ Plugins = 64 // user can configure plugins and activate addons
+}
+
+enum Role {
+ Admin = 0, //admin has all permissions implicit
+ User = 1
+}
+
+struct DownloadProgress {
+ 1: FileID fid,
+ 2: PackageID pid,
+ 3: ByteCount speed,
+ 4: DownloadStatus status,
+}
+
+struct ProgressInfo {
+ 1: PluginName plugin,
2: string name,
- 3: i64 speed,
- 4: i32 eta,
+ 3: string statusmsg,
+ 4: i32 eta, // in seconds
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,
+ 6: ByteCount done,
+ 7: ByteCount total, // arbitary number, size in case of files
+ 8: optional DownloadProgress download
}
struct ServerStatus {
@@ -88,155 +121,200 @@ struct ServerStatus {
2: i16 active,
3: i16 queue,
4: i16 total,
- 5: i64 speed,
+ 5: ByteCount speed,
6: bool download,
7: bool reconnect
}
-struct ConfigItem {
- 1: string name,
- 2: string description,
- 3: string value,
- 4: string type,
+// download info for specific file
+struct DownloadInfo {
+ 1: string url,
+ 2: PluginName plugin,
+ 3: string hash,
+ 4: DownloadStatus status,
+ 5: string statusmsg,
+ 6: string error,
}
-struct ConfigSection {
- 1: string name,
- 2: string description,
- 3: list<ConfigItem> items,
- 4: optional string outline
+struct FileInfo {
+ 1: FileID fid,
+ 2: string name,
+ 3: PackageID package,
+ 4: UserID owner,
+ 5: ByteCount size,
+ 6: FileStatus status,
+ 7: MediaType media,
+ 8: UTCDate added,
+ 9: i16 fileorder,
+ 10: optional DownloadInfo download,
}
-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 {
+struct PackageStats {
+ 1: i16 linkstotal,
+ 2: i16 linksdone,
+ 3: ByteCount sizetotal,
+ 4: ByteCount sizedone,
+}
+
+struct PackageInfo {
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
+ 4: PackageID root,
+ 5: UserID owner,
+ 6: string site,
+ 7: string comment,
+ 8: string password,
+ 9: UTCDate added,
+ 10: PackageStatus status,
+ 11: i16 packageorder,
+ 12: PackageStats stats,
+ 13: list<FileID> fids,
+ 14: list<PackageID> pids,
+}
+
+// thrift does not allow recursive datatypes, so all data is accumulated and mapped with id
+struct TreeCollection {
+ 1: PackageInfo root,
+ 2: map<FileID, FileInfo> files,
+ 3: map<PackageID, PackageInfo> packages
+}
+
+// general info about link, used for collector and online results
+struct LinkStatus {
+ 1: string url,
+ 2: string name,
+ 3: PluginName plugin,
+ 4: ByteCount size, // size <= 0 : unknown
+ 5: DownloadStatus status,
+ 6: string packagename,
}
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,
+ 3: list<string> data,
+ 4: Output output,
+ 5: optional JSONString default_value,
+ 6: string title,
+ 7: string description,
+ 8: PluginName plugin,
}
-struct CaptchaTask {
- 1: i16 tid,
- 2: binary data,
- 3: string type,
- 4: string resultType
+struct AddonService {
+ 1: string func_name,
+ 2: string description,
+ 3: list<string> arguments,
+ 4: optional i16 media,
}
-struct EventInfo {
- 1: string eventname,
- 2: optional i32 id,
- 3: optional ElementType type,
- 4: optional Destination destination
+struct AddonInfo {
+ 1: string func_name,
+ 2: string description,
+ 3: JSONString value,
}
-struct UserData {
+struct ConfigItem {
1: string name,
- 2: string email,
- 3: i32 role,
- 4: i32 permission,
- 5: string templateName
+ 2: string label,
+ 3: string description,
+ 4: string type,
+ 5: JSONString default_value,
+ 6: JSONString value,
}
-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 ConfigHolder {
+ 1: string name,
+ 2: string label,
+ 3: string description,
+ 4: string long_description,
+ 5: list<ConfigItem> items,
+ 6: optional list<AddonInfo> info,
+ 7: optional list<InteractionTask> handler, // if null plugin is not loaded
}
-struct ServiceCall {
- 1: PluginName plugin,
- 2: string func,
- 3: optional list<string> arguments,
- 4: optional bool parseArguments, //default False
+struct ConfigInfo {
+ 1: string name,
+ 2: string label,
+ 3: string description,
+ 4: bool saved,
+ 5: bool activated,
}
-struct OnlineStatus {
- 1: string name,
- 2: PluginName plugin,
- 3: string packagename,
- 4: DownloadStatus status,
- 5: i64 size, // size <= 0 : unknown
+struct EventInfo {
+ 1: string eventname,
+ 2: list<JSONString> event_args,
}
-struct OnlineCheck {
- 1: ResultID rid, // -1 -> nothing more to get
- 2: map<string, OnlineStatus> data, //url to result
+struct UserData {
+ 1: UserID uid,
+ 2: string name,
+ 3: string email,
+ 4: i16 role,
+ 5: i16 permission,
+ 6: string folder,
+ 7: ByteCount traffic
+ 8: i16 dllimit
+ 9: string dlquota,
+ 10: ByteCount hddquota,
+ 11: UserID user,
+ 12: string templateName
}
+struct AccountInfo {
+ 1: PluginName plugin,
+ 2: string loginname,
+ 3: UserID owner,
+ 4: bool valid,
+ 5: UTCDate validuntil,
+ 6: ByteCount trafficleft,
+ 7: ByteCount maxtraffic,
+ 8: bool premium,
+ 9: bool activated,
+ 10: bool shared,
+ 11: map<string, string> options,
+}
+
+struct OnlineCheck {
+ 1: ResultID rid, // -1 -> nothing more to get
+ 2: map<string, LinkStatus> data, // url to result
+}
// exceptions
-exception PackageDoesNotExists{
+exception PackageDoesNotExists {
1: PackageID pid
}
-exception FileDoesNotExists{
+exception FileDoesNotExists {
1: FileID fid
}
-exception ServiceDoesNotExists{
+exception UserDoesNotExists {
+ 1: string user
+}
+
+exception ServiceDoesNotExists {
1: string plugin
2: string func
}
-exception ServiceException{
+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
+ ///////////////////////
- // server status
+ string getServerVersion(),
+ ServerStatus statusServer(),
void pauseServer(),
void unpauseServer(),
bool togglePause(),
- ServerStatus statusServer(),
- i64 freeSpace(),
- string getServerVersion(),
+ ByteCount freeSpace(),
void kill(),
void restart(),
list<string> getLog(1: i32 offset),
@@ -244,10 +322,28 @@ service Pyload {
bool isTimeReconnect(),
bool toggleReconnect(),
- // download preparing
+ // TODO
+ //void scanDownloadFolder(),
+
+ list<ProgressInfo> getProgressInfo(),
+
+ ///////////////////////
+ // Configuration
+ ///////////////////////
+
+ map<string, ConfigHolder> getConfig(),
+ list<ConfigInfo> getGlobalPlugins(),
+ list<ConfigInfo> getUserPlugins(),
+
+ ConfigHolder configurePlugin(1: PluginName plugin),
+ void saveConfig(1: ConfigHolder config),
+ void deleteConfig(1: PluginName plugin),
+ void setConfigHandler(1: PluginName plugin, 2: InteractionID iid, 3: JSONString value),
+
+ ///////////////////////
+ // 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),
@@ -255,83 +351,164 @@ service Pyload {
OnlineCheck checkOnlineStatus(1: LinkList urls),
OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
- // poll results from previosly started online check
+ // poll results from previously 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),
+ // packagename -> urls
+ map<string, LinkList> generatePackages(1: LinkList links),
+
+ ///////////////////////
+ // Adding/Deleting
+ ///////////////////////
+
+ list<PackageID> generateAndAddPackages(1: LinkList links, 2: bool paused),
+
+ PackageID createPackage(1: string name, 2: string folder, 3: PackageID root, 4: string password,
+ 5: string site, 6: string comment, 7: bool paused),
+
+ PackageID addPackage(1: string name, 2: LinkList links, 3: string password),
+ // same as above with paused attribute
+ PackageID addPackageP(1: string name, 2: LinkList links, 3: string password, 4: bool paused),
+
+ // pid -1 is toplevel
+ PackageID addPackageChild(1: string name, 2: LinkList links, 3: string password, 4: PackageID root, 5: bool paused),
+
+ PackageID uploadContainer(1: string filename, 2: binary data),
+
+ void addLinks(1: PackageID pid, 2: LinkList links) throws (1: PackageDoesNotExists e),
+ void addLocalFile(1: PackageID pid, 2: string name, 3: string path) throws (1: PackageDoesNotExists e)
+
+ // these are real file operations and WILL delete files on disk
void deleteFiles(1: list<FileID> fids),
- void deletePackages(1: list<PackageID> pids),
+ void deletePackages(1: list<PackageID> pids), // delete the whole folder recursive
+
+ ///////////////////////
+ // Collector
+ ///////////////////////
+
+ list<LinkStatus> getCollector(),
+
+ void addToCollector(1: LinkList links),
+ PackageID addFromCollector(1: string name, 2: bool paused),
+ void renameCollPack(1: string name, 2: string new_name),
+ void deleteCollPack(1: string name),
+ void deleteCollLink(1: string url),
+
+ ////////////////////////////
+ // File Information retrival
+ ////////////////////////////
+
+ TreeCollection getAllFiles(),
+ TreeCollection getAllUnfinishedFiles(),
+
+ // pid -1 for root, full=False only delivers first level in tree
+ TreeCollection getFileTree(1: PackageID pid, 2: bool full),
+ TreeCollection getUnfinishedFileTree(1: PackageID pid, 2: bool full),
+
+ // same as above with full=False
+ TreeCollection getPackageContent(1: PackageID pid),
+
+ PackageInfo getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ FileInfo getFileInfo(1: FileID fid) throws (1: FileDoesNotExists e),
+ TreeCollection findFiles(1: string pattern),
+
+ ///////////////////////
+ // Modify Downloads
+ ///////////////////////
- // 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 restartFailed(),
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 stopAllDownloads(),
+
+ /////////////////////////
+ // Modify Files/Packages
+ /////////////////////////
+
+ // moving package while downloading is not possible, so they will return bool to indicate success
+ void setPackagePaused(1: PackageID pid, 2: bool paused) throws (1: PackageDoesNotExists e),
+ bool setPackageFolder(1: PackageID pid, 2: string path) throws (1: PackageDoesNotExists e),
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)
+ // as above, this will move files on disk
+ bool movePackage(1: PackageID pid, 2: PackageID root) throws (1: PackageDoesNotExists e),
+ bool moveFiles(1: list<FileID> fids, 2: PackageID pid) throws (1: PackageDoesNotExists e),
+
+ void orderPackage(1: list<PackageID> pids, 2: i16 position),
+ void orderFiles(1: list<FileID> fids, 2: PackageID pid, 3: i16 position),
+
+ ///////////////////////
+ // User Interaction
+ ///////////////////////
+
+ // mode = Output types binary ORed
+ bool isInteractionWaiting(1: i16 mode),
+ InteractionTask getInteractionTask(1: i16 mode),
+ void setInteractionResult(1: InteractionID iid, 2: JSONString result),
+
+ // generate a download link, everybody can download the file until timeout reached
+ string generateDownloadLink(1: FileID fid, 2: i16 timeout),
+
+ list<InteractionTask> getNotifications(),
+
+ ///////////////////////
+ // Event Handling
+ ///////////////////////
+
+ list<EventInfo> getEvents(1: string uuid),
- //accounts
+ ///////////////////////
+ // Account Methods
+ ///////////////////////
+
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),
+ list<string> getAccountTypes(),
+ void updateAccount(1: PluginName plugin, 2: string account, 3: string password),
+ void updateAccountInfo(1: AccountInfo account),
void removeAccount(1: PluginName plugin, 2: string account),
- //auth
+ /////////////////////////
+ // Auth+User Information
+ /////////////////////////
+
bool login(1: string username, 2: string password),
- UserData getUserData(1: string username, 2:string password),
- map<string, UserData> getAllUserData(),
+ // returns own user data
+ UserData getUserData(),
- //services
+ // all user, for admins only
+ map<UserID, UserData> getAllUserData(),
- // 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),
+ UserData addUser(1: string username, 2:string password),
+ // normal user can only update their own userdata and not all attributes
+ void updateUserData(1: UserData data),
+ void removeUser(1: UserID uid),
- //info
- // {plugin: {name: value}}
- map<PluginName, map<string,string>> getAllInfo(),
- map<string, string> getInfoByPlugin(1: PluginName plugin),
+ // works contextual, admin can change every password
+ bool setPassword(1: string username, 2: string old_password, 3: string new_password),
- //scheduler
+ ///////////////////////
+ // Addon Methods
+ ///////////////////////
- // TODO
+ map<PluginName, list<AddonInfo>> getAllInfo(),
+ list<AddonInfo> getInfoByPlugin(1: PluginName plugin),
+
+ map<PluginName, list<AddonService>> getAddonHandler(),
+ bool hasAddonHandler(1: PluginName plugin, 2: string func),
+
+ void callAddon(1: PluginName plugin, 2: string func, 3: list<JSONString> arguments)
+ throws (1: ServiceDoesNotExists e, 2: ServiceException ex),
+
+ // special variant of callAddon that works on the media types, acccepting integer
+ void callAddonHandler(1: PluginName plugin, 2: string func, 3: PackageID pid_or_fid)
+ throws (1: ServiceDoesNotExists e, 2: ServiceException ex),
- // User interaction
+ //scheduler
+
+ // TODO
- //captcha
- bool isCaptchaWaiting(),
- CaptchaTask getCaptchaTask(1: bool exclusive),
- string getCaptchaTaskStatus(1: TaskID tid),
- void setCaptchaResult(1: TaskID tid, 2: string result),
}
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
index bfaf5b078..20015ba43 100755
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
+++ b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
+# Autogenerated by Thrift Compiler (0.8.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
@@ -23,76 +23,93 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
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 ' string getServerVersion()'
+ print ' ServerStatus statusServer()'
print ' void pauseServer()'
print ' void unpauseServer()'
print ' bool togglePause()'
- print ' ServerStatus statusServer()'
- print ' i64 freeSpace()'
- print ' string getServerVersion()'
+ print ' ByteCount freeSpace()'
print ' void kill()'
print ' void restart()'
print ' getLog(i32 offset)'
print ' bool isTimeDownload()'
print ' bool isTimeReconnect()'
print ' bool toggleReconnect()'
- print ' generatePackages(LinkList links)'
+ print ' getProgressInfo()'
+ print ' getConfig()'
+ print ' getGlobalPlugins()'
+ print ' getUserPlugins()'
+ print ' ConfigHolder configurePlugin(PluginName plugin)'
+ print ' void saveConfig(ConfigHolder config)'
+ print ' void deleteConfig(PluginName plugin)'
+ print ' void setConfigHandler(PluginName plugin, InteractionID iid, JSONString value)'
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 ' generatePackages(LinkList links)'
+ print ' generateAndAddPackages(LinkList links, bool paused)'
+ print ' PackageID createPackage(string name, string folder, PackageID root, string password, string site, string comment, bool paused)'
+ print ' PackageID addPackage(string name, LinkList links, string password)'
+ print ' PackageID addPackageP(string name, LinkList links, string password, bool paused)'
+ print ' PackageID addPackageChild(string name, LinkList links, string password, PackageID root, bool paused)'
+ print ' PackageID uploadContainer(string filename, string data)'
+ print ' void addLinks(PackageID pid, LinkList links)'
+ print ' void addLocalFile(PackageID pid, string name, string path)'
print ' void deleteFiles( fids)'
print ' void deletePackages( pids)'
- print ' void pushToQueue(PackageID pid)'
- print ' void pullFromQueue(PackageID pid)'
+ print ' getCollector()'
+ print ' void addToCollector(LinkList links)'
+ print ' PackageID addFromCollector(string name, bool paused)'
+ print ' void renameCollPack(string name, string new_name)'
+ print ' void deleteCollPack(string name)'
+ print ' void deleteCollLink(string url)'
+ print ' TreeCollection getAllFiles()'
+ print ' TreeCollection getAllUnfinishedFiles()'
+ print ' TreeCollection getFileTree(PackageID pid, bool full)'
+ print ' TreeCollection getUnfinishedFileTree(PackageID pid, bool full)'
+ print ' TreeCollection getPackageContent(PackageID pid)'
+ print ' PackageInfo getPackageInfo(PackageID pid)'
+ print ' FileInfo getFileInfo(FileID fid)'
+ print ' TreeCollection findFiles(string pattern)'
print ' void restartPackage(PackageID pid)'
print ' void restartFile(FileID fid)'
print ' void recheckPackage(PackageID pid)'
- print ' void stopAllDownloads()'
+ print ' void restartFailed()'
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 stopAllDownloads()'
+ print ' void setPackagePaused(PackageID pid, bool paused)'
+ print ' bool setPackageFolder(PackageID pid, string path)'
print ' void setPackageData(PackageID pid, data)'
- print ' deleteFinished()'
- print ' void restartFailed()'
+ print ' bool movePackage(PackageID pid, PackageID root)'
+ print ' bool moveFiles( fids, PackageID pid)'
+ print ' void orderPackage( pids, i16 position)'
+ print ' void orderFiles( fids, PackageID pid, i16 position)'
+ print ' bool isInteractionWaiting(i16 mode)'
+ print ' InteractionTask getInteractionTask(i16 mode)'
+ print ' void setInteractionResult(InteractionID iid, JSONString result)'
+ print ' string generateDownloadLink(FileID fid, i16 timeout)'
+ print ' getNotifications()'
print ' getEvents(string uuid)'
print ' getAccounts(bool refresh)'
print ' getAccountTypes()'
- print ' void updateAccount(PluginName plugin, string account, string password, options)'
+ print ' void updateAccount(PluginName plugin, string account, string password)'
+ print ' void updateAccountInfo(AccountInfo account)'
print ' void removeAccount(PluginName plugin, string account)'
print ' bool login(string username, string password)'
- print ' UserData getUserData(string username, string password)'
+ print ' UserData getUserData()'
print ' getAllUserData()'
- print ' getServices()'
- print ' bool hasService(PluginName plugin, string func)'
- print ' string call(ServiceCall info)'
+ print ' UserData addUser(string username, string password)'
+ print ' void updateUserData(UserData data)'
+ print ' void removeUser(UserID uid)'
+ print ' bool setPassword(string username, string old_password, string new_password)'
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 ' getAddonHandler()'
+ print ' bool hasAddonHandler(PluginName plugin, string func)'
+ print ' void callAddon(PluginName plugin, string func, arguments)'
+ print ' void callAddonHandler(PluginName plugin, string func, PackageID pid_or_fid)'
print ''
sys.exit(0)
@@ -144,29 +161,17 @@ 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 cmd == 'getServerVersion':
if len(args) != 0:
- print 'getConfig requires 0 args'
+ print 'getServerVersion requires 0 args'
sys.exit(1)
- pp.pprint(client.getConfig())
+ pp.pprint(client.getServerVersion())
-elif cmd == 'getPluginConfig':
+elif cmd == 'statusServer':
if len(args) != 0:
- print 'getPluginConfig requires 0 args'
+ print 'statusServer requires 0 args'
sys.exit(1)
- pp.pprint(client.getPluginConfig())
+ pp.pprint(client.statusServer())
elif cmd == 'pauseServer':
if len(args) != 0:
@@ -186,24 +191,12 @@ elif cmd == 'togglePause':
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'
@@ -240,11 +233,53 @@ elif cmd == 'toggleReconnect':
sys.exit(1)
pp.pprint(client.toggleReconnect())
-elif cmd == 'generatePackages':
+elif cmd == 'getProgressInfo':
+ if len(args) != 0:
+ print 'getProgressInfo requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getProgressInfo())
+
+elif cmd == 'getConfig':
+ if len(args) != 0:
+ print 'getConfig requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getConfig())
+
+elif cmd == 'getGlobalPlugins':
+ if len(args) != 0:
+ print 'getGlobalPlugins requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getGlobalPlugins())
+
+elif cmd == 'getUserPlugins':
+ if len(args) != 0:
+ print 'getUserPlugins requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getUserPlugins())
+
+elif cmd == 'configurePlugin':
if len(args) != 1:
- print 'generatePackages requires 1 args'
+ print 'configurePlugin requires 1 args'
sys.exit(1)
- pp.pprint(client.generatePackages(eval(args[0]),))
+ pp.pprint(client.configurePlugin(eval(args[0]),))
+
+elif cmd == 'saveConfig':
+ if len(args) != 1:
+ print 'saveConfig requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.saveConfig(eval(args[0]),))
+
+elif cmd == 'deleteConfig':
+ if len(args) != 1:
+ print 'deleteConfig requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.deleteConfig(eval(args[0]),))
+
+elif cmd == 'setConfigHandler':
+ if len(args) != 3:
+ print 'setConfigHandler requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.setConfigHandler(eval(args[0]),eval(args[1]),eval(args[2]),))
elif cmd == 'checkURLs':
if len(args) != 1:
@@ -276,35 +311,71 @@ elif cmd == 'pollResults':
sys.exit(1)
pp.pprint(client.pollResults(eval(args[0]),))
-elif cmd == 'statusDownloads':
- if len(args) != 0:
- print 'statusDownloads requires 0 args'
+elif cmd == 'generatePackages':
+ if len(args) != 1:
+ print 'generatePackages requires 1 args'
sys.exit(1)
- pp.pprint(client.statusDownloads())
+ pp.pprint(client.generatePackages(eval(args[0]),))
-elif cmd == 'getPackageData':
- if len(args) != 1:
- print 'getPackageData requires 1 args'
+elif cmd == 'generateAndAddPackages':
+ if len(args) != 2:
+ print 'generateAndAddPackages requires 2 args'
sys.exit(1)
- pp.pprint(client.getPackageData(eval(args[0]),))
+ pp.pprint(client.generateAndAddPackages(eval(args[0]),eval(args[1]),))
-elif cmd == 'getPackageInfo':
- if len(args) != 1:
- print 'getPackageInfo requires 1 args'
+elif cmd == 'createPackage':
+ if len(args) != 7:
+ print 'createPackage requires 7 args'
sys.exit(1)
- pp.pprint(client.getPackageInfo(eval(args[0]),))
+ pp.pprint(client.createPackage(args[0],args[1],eval(args[2]),args[3],args[4],args[5],eval(args[6]),))
+
+elif cmd == 'addPackage':
+ if len(args) != 3:
+ print 'addPackage requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.addPackage(args[0],eval(args[1]),args[2],))
+
+elif cmd == 'addPackageP':
+ if len(args) != 4:
+ print 'addPackageP requires 4 args'
+ sys.exit(1)
+ pp.pprint(client.addPackageP(args[0],eval(args[1]),args[2],eval(args[3]),))
+
+elif cmd == 'addPackageChild':
+ if len(args) != 5:
+ print 'addPackageChild requires 5 args'
+ sys.exit(1)
+ pp.pprint(client.addPackageChild(args[0],eval(args[1]),args[2],eval(args[3]),eval(args[4]),))
+
+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 == 'getFileData':
+elif cmd == 'addLinks':
+ if len(args) != 2:
+ print 'addLinks requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.addLinks(eval(args[0]),eval(args[1]),))
+
+elif cmd == 'addLocalFile':
+ if len(args) != 3:
+ print 'addLocalFile requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.addLocalFile(eval(args[0]),args[1],args[2],))
+
+elif cmd == 'deleteFiles':
if len(args) != 1:
- print 'getFileData requires 1 args'
+ print 'deleteFiles requires 1 args'
sys.exit(1)
- pp.pprint(client.getFileData(eval(args[0]),))
+ pp.pprint(client.deleteFiles(eval(args[0]),))
-elif cmd == 'getQueue':
- if len(args) != 0:
- print 'getQueue requires 0 args'
+elif cmd == 'deletePackages':
+ if len(args) != 1:
+ print 'deletePackages requires 1 args'
sys.exit(1)
- pp.pprint(client.getQueue())
+ pp.pprint(client.deletePackages(eval(args[0]),))
elif cmd == 'getCollector':
if len(args) != 0:
@@ -312,77 +383,83 @@ elif cmd == 'getCollector':
sys.exit(1)
pp.pprint(client.getCollector())
-elif cmd == 'getQueueData':
- if len(args) != 0:
- print 'getQueueData requires 0 args'
+elif cmd == 'addToCollector':
+ if len(args) != 1:
+ print 'addToCollector requires 1 args'
sys.exit(1)
- pp.pprint(client.getQueueData())
+ pp.pprint(client.addToCollector(eval(args[0]),))
-elif cmd == 'getCollectorData':
- if len(args) != 0:
- print 'getCollectorData requires 0 args'
+elif cmd == 'addFromCollector':
+ if len(args) != 2:
+ print 'addFromCollector requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.addFromCollector(args[0],eval(args[1]),))
+
+elif cmd == 'renameCollPack':
+ if len(args) != 2:
+ print 'renameCollPack requires 2 args'
sys.exit(1)
- pp.pprint(client.getCollectorData())
+ pp.pprint(client.renameCollPack(args[0],args[1],))
-elif cmd == 'getPackageOrder':
+elif cmd == 'deleteCollPack':
if len(args) != 1:
- print 'getPackageOrder requires 1 args'
+ print 'deleteCollPack requires 1 args'
sys.exit(1)
- pp.pprint(client.getPackageOrder(eval(args[0]),))
+ pp.pprint(client.deleteCollPack(args[0],))
-elif cmd == 'getFileOrder':
+elif cmd == 'deleteCollLink':
if len(args) != 1:
- print 'getFileOrder requires 1 args'
+ print 'deleteCollLink requires 1 args'
sys.exit(1)
- pp.pprint(client.getFileOrder(eval(args[0]),))
+ pp.pprint(client.deleteCollLink(args[0],))
-elif cmd == 'generateAndAddPackages':
- if len(args) != 2:
- print 'generateAndAddPackages requires 2 args'
+elif cmd == 'getAllFiles':
+ if len(args) != 0:
+ print 'getAllFiles requires 0 args'
sys.exit(1)
- pp.pprint(client.generateAndAddPackages(eval(args[0]),eval(args[1]),))
+ pp.pprint(client.getAllFiles())
-elif cmd == 'addPackage':
- if len(args) != 3:
- print 'addPackage requires 3 args'
+elif cmd == 'getAllUnfinishedFiles':
+ if len(args) != 0:
+ print 'getAllUnfinishedFiles requires 0 args'
sys.exit(1)
- pp.pprint(client.addPackage(args[0],eval(args[1]),eval(args[2]),))
+ pp.pprint(client.getAllUnfinishedFiles())
-elif cmd == 'addFiles':
+elif cmd == 'getFileTree':
if len(args) != 2:
- print 'addFiles requires 2 args'
+ print 'getFileTree requires 2 args'
sys.exit(1)
- pp.pprint(client.addFiles(eval(args[0]),eval(args[1]),))
+ pp.pprint(client.getFileTree(eval(args[0]),eval(args[1]),))
-elif cmd == 'uploadContainer':
+elif cmd == 'getUnfinishedFileTree':
if len(args) != 2:
- print 'uploadContainer requires 2 args'
+ print 'getUnfinishedFileTree requires 2 args'
sys.exit(1)
- pp.pprint(client.uploadContainer(args[0],args[1],))
+ pp.pprint(client.getUnfinishedFileTree(eval(args[0]),eval(args[1]),))
-elif cmd == 'deleteFiles':
+elif cmd == 'getPackageContent':
if len(args) != 1:
- print 'deleteFiles requires 1 args'
+ print 'getPackageContent requires 1 args'
sys.exit(1)
- pp.pprint(client.deleteFiles(eval(args[0]),))
+ pp.pprint(client.getPackageContent(eval(args[0]),))
-elif cmd == 'deletePackages':
+elif cmd == 'getPackageInfo':
if len(args) != 1:
- print 'deletePackages requires 1 args'
+ print 'getPackageInfo requires 1 args'
sys.exit(1)
- pp.pprint(client.deletePackages(eval(args[0]),))
+ pp.pprint(client.getPackageInfo(eval(args[0]),))
-elif cmd == 'pushToQueue':
+elif cmd == 'getFileInfo':
if len(args) != 1:
- print 'pushToQueue requires 1 args'
+ print 'getFileInfo requires 1 args'
sys.exit(1)
- pp.pprint(client.pushToQueue(eval(args[0]),))
+ pp.pprint(client.getFileInfo(eval(args[0]),))
-elif cmd == 'pullFromQueue':
+elif cmd == 'findFiles':
if len(args) != 1:
- print 'pullFromQueue requires 1 args'
+ print 'findFiles requires 1 args'
sys.exit(1)
- pp.pprint(client.pullFromQueue(eval(args[0]),))
+ pp.pprint(client.findFiles(args[0],))
elif cmd == 'restartPackage':
if len(args) != 1:
@@ -402,11 +479,11 @@ elif cmd == 'recheckPackage':
sys.exit(1)
pp.pprint(client.recheckPackage(eval(args[0]),))
-elif cmd == 'stopAllDownloads':
+elif cmd == 'restartFailed':
if len(args) != 0:
- print 'stopAllDownloads requires 0 args'
+ print 'restartFailed requires 0 args'
sys.exit(1)
- pp.pprint(client.stopAllDownloads())
+ pp.pprint(client.restartFailed())
elif cmd == 'stopDownloads':
if len(args) != 1:
@@ -414,11 +491,29 @@ elif cmd == 'stopDownloads':
sys.exit(1)
pp.pprint(client.stopDownloads(eval(args[0]),))
-elif cmd == 'setPackageName':
+elif cmd == 'stopAllDownloads':
+ if len(args) != 0:
+ print 'stopAllDownloads requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.stopAllDownloads())
+
+elif cmd == 'setPackagePaused':
+ if len(args) != 2:
+ print 'setPackagePaused requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setPackagePaused(eval(args[0]),eval(args[1]),))
+
+elif cmd == 'setPackageFolder':
if len(args) != 2:
- print 'setPackageName requires 2 args'
+ print 'setPackageFolder requires 2 args'
sys.exit(1)
- pp.pprint(client.setPackageName(eval(args[0]),args[1],))
+ pp.pprint(client.setPackageFolder(eval(args[0]),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 == 'movePackage':
if len(args) != 2:
@@ -438,29 +533,41 @@ elif cmd == 'orderPackage':
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'
+elif cmd == 'orderFiles':
+ if len(args) != 3:
+ print 'orderFiles requires 3 args'
sys.exit(1)
- pp.pprint(client.orderFile(eval(args[0]),eval(args[1]),))
+ pp.pprint(client.orderFiles(eval(args[0]),eval(args[1]),eval(args[2]),))
-elif cmd == 'setPackageData':
+elif cmd == 'isInteractionWaiting':
+ if len(args) != 1:
+ print 'isInteractionWaiting requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.isInteractionWaiting(eval(args[0]),))
+
+elif cmd == 'getInteractionTask':
+ if len(args) != 1:
+ print 'getInteractionTask requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getInteractionTask(eval(args[0]),))
+
+elif cmd == 'setInteractionResult':
if len(args) != 2:
- print 'setPackageData requires 2 args'
+ print 'setInteractionResult requires 2 args'
sys.exit(1)
- pp.pprint(client.setPackageData(eval(args[0]),eval(args[1]),))
+ pp.pprint(client.setInteractionResult(eval(args[0]),eval(args[1]),))
-elif cmd == 'deleteFinished':
- if len(args) != 0:
- print 'deleteFinished requires 0 args'
+elif cmd == 'generateDownloadLink':
+ if len(args) != 2:
+ print 'generateDownloadLink requires 2 args'
sys.exit(1)
- pp.pprint(client.deleteFinished())
+ pp.pprint(client.generateDownloadLink(eval(args[0]),eval(args[1]),))
-elif cmd == 'restartFailed':
+elif cmd == 'getNotifications':
if len(args) != 0:
- print 'restartFailed requires 0 args'
+ print 'getNotifications requires 0 args'
sys.exit(1)
- pp.pprint(client.restartFailed())
+ pp.pprint(client.getNotifications())
elif cmd == 'getEvents':
if len(args) != 1:
@@ -481,10 +588,16 @@ elif cmd == 'getAccountTypes':
pp.pprint(client.getAccountTypes())
elif cmd == 'updateAccount':
- if len(args) != 4:
- print 'updateAccount requires 4 args'
+ if len(args) != 3:
+ print 'updateAccount requires 3 args'
sys.exit(1)
- pp.pprint(client.updateAccount(eval(args[0]),args[1],args[2],eval(args[3]),))
+ pp.pprint(client.updateAccount(eval(args[0]),args[1],args[2],))
+
+elif cmd == 'updateAccountInfo':
+ if len(args) != 1:
+ print 'updateAccountInfo requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.updateAccountInfo(eval(args[0]),))
elif cmd == 'removeAccount':
if len(args) != 2:
@@ -499,10 +612,10 @@ elif cmd == 'login':
pp.pprint(client.login(args[0],args[1],))
elif cmd == 'getUserData':
- if len(args) != 2:
- print 'getUserData requires 2 args'
+ if len(args) != 0:
+ print 'getUserData requires 0 args'
sys.exit(1)
- pp.pprint(client.getUserData(args[0],args[1],))
+ pp.pprint(client.getUserData())
elif cmd == 'getAllUserData':
if len(args) != 0:
@@ -510,23 +623,29 @@ elif cmd == 'getAllUserData':
sys.exit(1)
pp.pprint(client.getAllUserData())
-elif cmd == 'getServices':
- if len(args) != 0:
- print 'getServices requires 0 args'
+elif cmd == 'addUser':
+ if len(args) != 2:
+ print 'addUser requires 2 args'
sys.exit(1)
- pp.pprint(client.getServices())
+ pp.pprint(client.addUser(args[0],args[1],))
-elif cmd == 'hasService':
- if len(args) != 2:
- print 'hasService requires 2 args'
+elif cmd == 'updateUserData':
+ if len(args) != 1:
+ print 'updateUserData requires 1 args'
sys.exit(1)
- pp.pprint(client.hasService(eval(args[0]),args[1],))
+ pp.pprint(client.updateUserData(eval(args[0]),))
-elif cmd == 'call':
+elif cmd == 'removeUser':
if len(args) != 1:
- print 'call requires 1 args'
+ print 'removeUser requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.removeUser(eval(args[0]),))
+
+elif cmd == 'setPassword':
+ if len(args) != 3:
+ print 'setPassword requires 3 args'
sys.exit(1)
- pp.pprint(client.call(eval(args[0]),))
+ pp.pprint(client.setPassword(args[0],args[1],args[2],))
elif cmd == 'getAllInfo':
if len(args) != 0:
@@ -540,29 +659,29 @@ elif cmd == 'getInfoByPlugin':
sys.exit(1)
pp.pprint(client.getInfoByPlugin(eval(args[0]),))
-elif cmd == 'isCaptchaWaiting':
+elif cmd == 'getAddonHandler':
if len(args) != 0:
- print 'isCaptchaWaiting requires 0 args'
+ print 'getAddonHandler requires 0 args'
sys.exit(1)
- pp.pprint(client.isCaptchaWaiting())
+ pp.pprint(client.getAddonHandler())
-elif cmd == 'getCaptchaTask':
- if len(args) != 1:
- print 'getCaptchaTask requires 1 args'
+elif cmd == 'hasAddonHandler':
+ if len(args) != 2:
+ print 'hasAddonHandler requires 2 args'
sys.exit(1)
- pp.pprint(client.getCaptchaTask(eval(args[0]),))
+ pp.pprint(client.hasAddonHandler(eval(args[0]),args[1],))
-elif cmd == 'getCaptchaTaskStatus':
- if len(args) != 1:
- print 'getCaptchaTaskStatus requires 1 args'
+elif cmd == 'callAddon':
+ if len(args) != 3:
+ print 'callAddon requires 3 args'
sys.exit(1)
- pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),))
+ pp.pprint(client.callAddon(eval(args[0]),args[1],eval(args[2]),))
-elif cmd == 'setCaptchaResult':
- if len(args) != 2:
- print 'setCaptchaResult requires 2 args'
+elif cmd == 'callAddonHandler':
+ if len(args) != 3:
+ print 'callAddonHandler requires 3 args'
sys.exit(1)
- pp.pprint(client.setCaptchaResult(eval(args[0]),args[1],))
+ pp.pprint(client.callAddonHandler(eval(args[0]),args[1],eval(args[2]),))
else:
print 'Unrecognized method %s' % cmd
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
index 78a42f16a..157d5b87b 100644
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
+++ b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -1,5 +1,5 @@
#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
+# Autogenerated by Thrift Compiler (0.8.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
@@ -8,34 +8,15 @@
from thrift.Thrift import TType, TMessageType, TException
from ttypes import *
-from thrift.Thrift import TProcessor
+from thrift.Thrift import TProcessor, TApplicationException
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, ):
+ def getServerVersion(self, ):
pass
- def getPluginConfig(self, ):
+ def statusServer(self, ):
pass
def pauseServer(self, ):
@@ -47,15 +28,9 @@ class Iface(object):
def togglePause(self, ):
pass
- def statusServer(self, ):
- pass
-
def freeSpace(self, ):
pass
- def getServerVersion(self, ):
- pass
-
def kill(self, ):
pass
@@ -78,10 +53,45 @@ class Iface(object):
def toggleReconnect(self, ):
pass
- def generatePackages(self, links):
+ def getProgressInfo(self, ):
+ pass
+
+ def getConfig(self, ):
+ pass
+
+ def getGlobalPlugins(self, ):
+ pass
+
+ def getUserPlugins(self, ):
+ pass
+
+ def configurePlugin(self, plugin):
"""
Parameters:
- - links
+ - plugin
+ """
+ pass
+
+ def saveConfig(self, config):
+ """
+ Parameters:
+ - config
+ """
+ pass
+
+ def deleteConfig(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ pass
+
+ def setConfigHandler(self, plugin, iid, value):
+ """
+ Parameters:
+ - plugin
+ - iid
+ - value
"""
pass
@@ -123,117 +133,193 @@ class Iface(object):
"""
pass
- def statusDownloads(self, ):
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
pass
- def getPackageData(self, pid):
+ def generateAndAddPackages(self, links, paused):
"""
Parameters:
- - pid
+ - links
+ - paused
"""
pass
- def getPackageInfo(self, pid):
+ def createPackage(self, name, folder, root, password, site, comment, paused):
"""
Parameters:
- - pid
+ - name
+ - folder
+ - root
+ - password
+ - site
+ - comment
+ - paused
"""
pass
- def getFileData(self, fid):
+ def addPackage(self, name, links, password):
"""
Parameters:
- - fid
+ - name
+ - links
+ - password
"""
pass
- def getQueue(self, ):
+ def addPackageP(self, name, links, password, paused):
+ """
+ Parameters:
+ - name
+ - links
+ - password
+ - paused
+ """
pass
- def getCollector(self, ):
+ def addPackageChild(self, name, links, password, root, paused):
+ """
+ Parameters:
+ - name
+ - links
+ - password
+ - root
+ - paused
+ """
pass
- def getQueueData(self, ):
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ pass
+
+ def addLinks(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
pass
- def getCollectorData(self, ):
+ def addLocalFile(self, pid, name, path):
+ """
+ Parameters:
+ - pid
+ - name
+ - path
+ """
pass
- def getPackageOrder(self, destination):
+ def deleteFiles(self, fids):
"""
Parameters:
- - destination
+ - fids
"""
pass
- def getFileOrder(self, pid):
+ def deletePackages(self, pids):
"""
Parameters:
- - pid
+ - pids
"""
pass
- def generateAndAddPackages(self, links, dest):
+ def getCollector(self, ):
+ pass
+
+ def addToCollector(self, links):
"""
Parameters:
- links
- - dest
"""
pass
- def addPackage(self, name, links, dest):
+ def addFromCollector(self, name, paused):
"""
Parameters:
- name
- - links
- - dest
+ - paused
"""
pass
- def addFiles(self, pid, links):
+ def renameCollPack(self, name, new_name):
"""
Parameters:
- - pid
- - links
+ - name
+ - new_name
"""
pass
- def uploadContainer(self, filename, data):
+ def deleteCollPack(self, name):
"""
Parameters:
- - filename
- - data
+ - name
"""
pass
- def deleteFiles(self, fids):
+ def deleteCollLink(self, url):
"""
Parameters:
- - fids
+ - url
"""
pass
- def deletePackages(self, pids):
+ def getAllFiles(self, ):
+ pass
+
+ def getAllUnfinishedFiles(self, ):
+ pass
+
+ def getFileTree(self, pid, full):
"""
Parameters:
- - pids
+ - pid
+ - full
+ """
+ pass
+
+ def getUnfinishedFileTree(self, pid, full):
+ """
+ Parameters:
+ - pid
+ - full
"""
pass
- def pushToQueue(self, pid):
+ def getPackageContent(self, pid):
"""
Parameters:
- pid
"""
pass
- def pullFromQueue(self, pid):
+ def getPackageInfo(self, pid):
"""
Parameters:
- pid
"""
pass
+ def getFileInfo(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ pass
+
+ def findFiles(self, pattern):
+ """
+ Parameters:
+ - pattern
+ """
+ pass
+
def restartPackage(self, pid):
"""
Parameters:
@@ -255,7 +341,7 @@ class Iface(object):
"""
pass
- def stopAllDownloads(self, ):
+ def restartFailed(self, ):
pass
def stopDownloads(self, fids):
@@ -265,19 +351,38 @@ class Iface(object):
"""
pass
- def setPackageName(self, pid, name):
+ def stopAllDownloads(self, ):
+ pass
+
+ def setPackagePaused(self, pid, paused):
"""
Parameters:
- pid
- - name
+ - paused
+ """
+ pass
+
+ def setPackageFolder(self, pid, path):
+ """
+ Parameters:
+ - pid
+ - path
+ """
+ pass
+
+ def setPackageData(self, pid, data):
+ """
+ Parameters:
+ - pid
+ - data
"""
pass
- def movePackage(self, destination, pid):
+ def movePackage(self, pid, root):
"""
Parameters:
- - destination
- pid
+ - root
"""
pass
@@ -289,34 +394,54 @@ class Iface(object):
"""
pass
- def orderPackage(self, pid, position):
+ def orderPackage(self, pids, position):
"""
Parameters:
- - pid
+ - pids
- position
"""
pass
- def orderFile(self, fid, position):
+ def orderFiles(self, fids, pid, position):
"""
Parameters:
- - fid
+ - fids
+ - pid
- position
"""
pass
- def setPackageData(self, pid, data):
+ def isInteractionWaiting(self, mode):
"""
Parameters:
- - pid
- - data
+ - mode
+ """
+ pass
+
+ def getInteractionTask(self, mode):
+ """
+ Parameters:
+ - mode
"""
pass
- def deleteFinished(self, ):
+ def setInteractionResult(self, iid, result):
+ """
+ Parameters:
+ - iid
+ - result
+ """
pass
- def restartFailed(self, ):
+ def generateDownloadLink(self, fid, timeout):
+ """
+ Parameters:
+ - fid
+ - timeout
+ """
+ pass
+
+ def getNotifications(self, ):
pass
def getEvents(self, uuid):
@@ -336,13 +461,19 @@ class Iface(object):
def getAccountTypes(self, ):
pass
- def updateAccount(self, plugin, account, password, options):
+ def updateAccount(self, plugin, account, password):
"""
Parameters:
- plugin
- account
- password
- - options
+ """
+ pass
+
+ def updateAccountInfo(self, account):
+ """
+ Parameters:
+ - account
"""
pass
@@ -362,7 +493,13 @@ class Iface(object):
"""
pass
- def getUserData(self, username, password):
+ def getUserData(self, ):
+ pass
+
+ def getAllUserData(self, ):
+ pass
+
+ def addUser(self, username, password):
"""
Parameters:
- username
@@ -370,24 +507,26 @@ class Iface(object):
"""
pass
- def getAllUserData(self, ):
- pass
-
- def getServices(self, ):
+ def updateUserData(self, data):
+ """
+ Parameters:
+ - data
+ """
pass
- def hasService(self, plugin, func):
+ def removeUser(self, uid):
"""
Parameters:
- - plugin
- - func
+ - uid
"""
pass
- def call(self, info):
+ def setPassword(self, username, old_password, new_password):
"""
Parameters:
- - info
+ - username
+ - old_password
+ - new_password
"""
pass
@@ -401,28 +540,32 @@ class Iface(object):
"""
pass
- def isCaptchaWaiting(self, ):
+ def getAddonHandler(self, ):
pass
- def getCaptchaTask(self, exclusive):
+ def hasAddonHandler(self, plugin, func):
"""
Parameters:
- - exclusive
+ - plugin
+ - func
"""
pass
- def getCaptchaTaskStatus(self, tid):
+ def callAddon(self, plugin, func, arguments):
"""
Parameters:
- - tid
+ - plugin
+ - func
+ - arguments
"""
pass
- def setCaptchaResult(self, tid, result):
+ def callAddonHandler(self, plugin, func, pid_or_fid):
"""
Parameters:
- - tid
- - result
+ - plugin
+ - func
+ - pid_or_fid
"""
pass
@@ -434,123 +577,55 @@ class Client(Iface):
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 getServerVersion(self, ):
+ self.send_getServerVersion()
+ return self.recv_getServerVersion()
- def send_getConfig(self, ):
- self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid)
- args = getConfig_args()
+ 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_getConfig(self, ):
+ 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 = getConfig_result()
+ result = getServerVersion_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result");
- def getPluginConfig(self, ):
- self.send_getPluginConfig()
- return self.recv_getPluginConfig()
+ def statusServer(self, ):
+ self.send_statusServer()
+ return self.recv_statusServer()
- def send_getPluginConfig(self, ):
- self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid)
- args = getPluginConfig_args()
+ 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_getPluginConfig(self, ):
+ 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 = getPluginConfig_result()
+ result = statusServer_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result");
def pauseServer(self, ):
self.send_pauseServer()
@@ -623,31 +698,6 @@ class Client(Iface):
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()
@@ -673,31 +723,6 @@ class Client(Iface):
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()
@@ -849,35 +874,223 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result");
- def generatePackages(self, links):
+ def getProgressInfo(self, ):
+ self.send_getProgressInfo()
+ return self.recv_getProgressInfo()
+
+ def send_getProgressInfo(self, ):
+ self._oprot.writeMessageBegin('getProgressInfo', TMessageType.CALL, self._seqid)
+ args = getProgressInfo_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getProgressInfo(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getProgressInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getProgressInfo failed: unknown result");
+
+ 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 getGlobalPlugins(self, ):
+ self.send_getGlobalPlugins()
+ return self.recv_getGlobalPlugins()
+
+ def send_getGlobalPlugins(self, ):
+ self._oprot.writeMessageBegin('getGlobalPlugins', TMessageType.CALL, self._seqid)
+ args = getGlobalPlugins_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getGlobalPlugins(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getGlobalPlugins_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getGlobalPlugins failed: unknown result");
+
+ def getUserPlugins(self, ):
+ self.send_getUserPlugins()
+ return self.recv_getUserPlugins()
+
+ def send_getUserPlugins(self, ):
+ self._oprot.writeMessageBegin('getUserPlugins', TMessageType.CALL, self._seqid)
+ args = getUserPlugins_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getUserPlugins(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getUserPlugins_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserPlugins failed: unknown result");
+
+ def configurePlugin(self, plugin):
"""
Parameters:
- - links
+ - plugin
"""
- self.send_generatePackages(links)
- return self.recv_generatePackages()
+ self.send_configurePlugin(plugin)
+ return self.recv_configurePlugin()
- def send_generatePackages(self, links):
- self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid)
- args = generatePackages_args()
- args.links = links
+ def send_configurePlugin(self, plugin):
+ self._oprot.writeMessageBegin('configurePlugin', TMessageType.CALL, self._seqid)
+ args = configurePlugin_args()
+ args.plugin = plugin
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_generatePackages(self, ):
+ def recv_configurePlugin(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 = configurePlugin_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "configurePlugin failed: unknown result");
+
+ def saveConfig(self, config):
+ """
+ Parameters:
+ - config
+ """
+ self.send_saveConfig(config)
+ self.recv_saveConfig()
+
+ def send_saveConfig(self, config):
+ self._oprot.writeMessageBegin('saveConfig', TMessageType.CALL, self._seqid)
+ args = saveConfig_args()
+ args.config = config
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_saveConfig(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = saveConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def deleteConfig(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ self.send_deleteConfig(plugin)
+ self.recv_deleteConfig()
+
+ def send_deleteConfig(self, plugin):
+ self._oprot.writeMessageBegin('deleteConfig', TMessageType.CALL, self._seqid)
+ args = deleteConfig_args()
+ args.plugin = plugin
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_deleteConfig(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deleteConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def setConfigHandler(self, plugin, iid, value):
+ """
+ Parameters:
+ - plugin
+ - iid
+ - value
+ """
+ self.send_setConfigHandler(plugin, iid, value)
+ self.recv_setConfigHandler()
+
+ def send_setConfigHandler(self, plugin, iid, value):
+ self._oprot.writeMessageBegin('setConfigHandler', TMessageType.CALL, self._seqid)
+ args = setConfigHandler_args()
+ args.plugin = plugin
+ args.iid = iid
+ args.value = value
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setConfigHandler(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setConfigHandler_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
def checkURLs(self, urls):
"""
@@ -1035,524 +1248,780 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result");
- def statusDownloads(self, ):
- self.send_statusDownloads()
- return self.recv_statusDownloads()
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
+ self.send_generatePackages(links)
+ return self.recv_generatePackages()
- def send_statusDownloads(self, ):
- self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid)
- args = statusDownloads_args()
+ 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_statusDownloads(self, ):
+ 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 = statusDownloads_result()
+ result = generatePackages_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result");
- def getPackageData(self, pid):
+ def generateAndAddPackages(self, links, paused):
"""
Parameters:
- - pid
+ - links
+ - paused
"""
- self.send_getPackageData(pid)
- return self.recv_getPackageData()
+ self.send_generateAndAddPackages(links, paused)
+ return self.recv_generateAndAddPackages()
- def send_getPackageData(self, pid):
- self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid)
- args = getPackageData_args()
- args.pid = pid
+ def send_generateAndAddPackages(self, links, paused):
+ self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
+ args = generateAndAddPackages_args()
+ args.links = links
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getPackageData(self, ):
+ 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 = getPackageData_result()
+ result = generateAndAddPackages_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result");
- def getPackageInfo(self, pid):
+ def createPackage(self, name, folder, root, password, site, comment, paused):
"""
Parameters:
- - pid
+ - name
+ - folder
+ - root
+ - password
+ - site
+ - comment
+ - paused
"""
- self.send_getPackageInfo(pid)
- return self.recv_getPackageInfo()
+ self.send_createPackage(name, folder, root, password, site, comment, paused)
+ return self.recv_createPackage()
- def send_getPackageInfo(self, pid):
- self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid)
- args = getPackageInfo_args()
- args.pid = pid
+ def send_createPackage(self, name, folder, root, password, site, comment, paused):
+ self._oprot.writeMessageBegin('createPackage', TMessageType.CALL, self._seqid)
+ args = createPackage_args()
+ args.name = name
+ args.folder = folder
+ args.root = root
+ args.password = password
+ args.site = site
+ args.comment = comment
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getPackageInfo(self, ):
+ def recv_createPackage(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 = createPackage_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "createPackage failed: unknown result");
- def getFileData(self, fid):
+ def addPackage(self, name, links, password):
"""
Parameters:
- - fid
+ - name
+ - links
+ - password
"""
- self.send_getFileData(fid)
- return self.recv_getFileData()
+ self.send_addPackage(name, links, password)
+ return self.recv_addPackage()
- def send_getFileData(self, fid):
- self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid)
- args = getFileData_args()
- args.fid = fid
+ def send_addPackage(self, name, links, password):
+ self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
+ args = addPackage_args()
+ args.name = name
+ args.links = links
+ args.password = password
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getFileData(self, ):
+ 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 = getFileData_result()
+ result = addPackage_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result");
- def getQueue(self, ):
- self.send_getQueue()
- return self.recv_getQueue()
+ def addPackageP(self, name, links, password, paused):
+ """
+ Parameters:
+ - name
+ - links
+ - password
+ - paused
+ """
+ self.send_addPackageP(name, links, password, paused)
+ return self.recv_addPackageP()
- def send_getQueue(self, ):
- self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid)
- args = getQueue_args()
+ def send_addPackageP(self, name, links, password, paused):
+ self._oprot.writeMessageBegin('addPackageP', TMessageType.CALL, self._seqid)
+ args = addPackageP_args()
+ args.name = name
+ args.links = links
+ args.password = password
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getQueue(self, ):
+ def recv_addPackageP(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 = addPackageP_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackageP failed: unknown result");
- def getCollector(self, ):
- self.send_getCollector()
- return self.recv_getCollector()
+ def addPackageChild(self, name, links, password, root, paused):
+ """
+ Parameters:
+ - name
+ - links
+ - password
+ - root
+ - paused
+ """
+ self.send_addPackageChild(name, links, password, root, paused)
+ return self.recv_addPackageChild()
- def send_getCollector(self, ):
- self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid)
- args = getCollector_args()
+ def send_addPackageChild(self, name, links, password, root, paused):
+ self._oprot.writeMessageBegin('addPackageChild', TMessageType.CALL, self._seqid)
+ args = addPackageChild_args()
+ args.name = name
+ args.links = links
+ args.password = password
+ args.root = root
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getCollector(self, ):
+ def recv_addPackageChild(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 = addPackageChild_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackageChild failed: unknown result");
- def getQueueData(self, ):
- self.send_getQueueData()
- return self.recv_getQueueData()
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ self.send_uploadContainer(filename, data)
+ return self.recv_uploadContainer()
- def send_getQueueData(self, ):
- self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid)
- args = getQueueData_args()
+ 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_getQueueData(self, ):
+ 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 = getQueueData_result()
+ result = uploadContainer_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "uploadContainer failed: unknown result");
- def getCollectorData(self, ):
- self.send_getCollectorData()
- return self.recv_getCollectorData()
+ def addLinks(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
+ self.send_addLinks(pid, links)
+ self.recv_addLinks()
- def send_getCollectorData(self, ):
- self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid)
- args = getCollectorData_args()
+ def send_addLinks(self, pid, links):
+ self._oprot.writeMessageBegin('addLinks', TMessageType.CALL, self._seqid)
+ args = addLinks_args()
+ args.pid = pid
+ args.links = links
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getCollectorData(self, ):
+ def recv_addLinks(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 = addLinks_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");
+ if result.e is not None:
+ raise result.e
+ return
- def getPackageOrder(self, destination):
+ def addLocalFile(self, pid, name, path):
"""
Parameters:
- - destination
+ - pid
+ - name
+ - path
"""
- self.send_getPackageOrder(destination)
- return self.recv_getPackageOrder()
+ self.send_addLocalFile(pid, name, path)
+ self.recv_addLocalFile()
- def send_getPackageOrder(self, destination):
- self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid)
- args = getPackageOrder_args()
- args.destination = destination
+ def send_addLocalFile(self, pid, name, path):
+ self._oprot.writeMessageBegin('addLocalFile', TMessageType.CALL, self._seqid)
+ args = addLocalFile_args()
+ args.pid = pid
+ args.name = name
+ args.path = path
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getPackageOrder(self, ):
+ def recv_addLocalFile(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 = addLocalFile_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");
+ if result.e is not None:
+ raise result.e
+ return
- def getFileOrder(self, pid):
+ def deleteFiles(self, fids):
"""
Parameters:
- - pid
+ - fids
"""
- self.send_getFileOrder(pid)
- return self.recv_getFileOrder()
+ self.send_deleteFiles(fids)
+ self.recv_deleteFiles()
- def send_getFileOrder(self, pid):
- self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid)
- args = getFileOrder_args()
- args.pid = pid
+ 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_getFileOrder(self, ):
+ 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 = getFileOrder_result()
+ 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 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, "getFileOrder failed: unknown result");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result");
- def generateAndAddPackages(self, links, dest):
+ def addToCollector(self, links):
"""
Parameters:
- links
- - dest
"""
- self.send_generateAndAddPackages(links, dest)
- return self.recv_generateAndAddPackages()
+ self.send_addToCollector(links)
+ self.recv_addToCollector()
- def send_generateAndAddPackages(self, links, dest):
- self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
- args = generateAndAddPackages_args()
+ def send_addToCollector(self, links):
+ self._oprot.writeMessageBegin('addToCollector', TMessageType.CALL, self._seqid)
+ args = addToCollector_args()
args.links = links
- args.dest = dest
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_generateAndAddPackages(self, ):
+ def recv_addToCollector(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 = addToCollector_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");
+ return
- def addPackage(self, name, links, dest):
+ def addFromCollector(self, name, paused):
"""
Parameters:
- name
- - links
- - dest
+ - paused
"""
- self.send_addPackage(name, links, dest)
- return self.recv_addPackage()
+ self.send_addFromCollector(name, paused)
+ return self.recv_addFromCollector()
- def send_addPackage(self, name, links, dest):
- self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
- args = addPackage_args()
+ def send_addFromCollector(self, name, paused):
+ self._oprot.writeMessageBegin('addFromCollector', TMessageType.CALL, self._seqid)
+ args = addFromCollector_args()
args.name = name
- args.links = links
- args.dest = dest
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_addPackage(self, ):
+ def recv_addFromCollector(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 = addFromCollector_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addFromCollector failed: unknown result");
- def addFiles(self, pid, links):
+ def renameCollPack(self, name, new_name):
"""
Parameters:
- - pid
- - links
+ - name
+ - new_name
"""
- self.send_addFiles(pid, links)
- self.recv_addFiles()
+ self.send_renameCollPack(name, new_name)
+ self.recv_renameCollPack()
- def send_addFiles(self, pid, links):
- self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid)
- args = addFiles_args()
- args.pid = pid
- args.links = links
+ def send_renameCollPack(self, name, new_name):
+ self._oprot.writeMessageBegin('renameCollPack', TMessageType.CALL, self._seqid)
+ args = renameCollPack_args()
+ args.name = name
+ args.new_name = new_name
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_addFiles(self, ):
+ def recv_renameCollPack(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 = renameCollPack_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
- def uploadContainer(self, filename, data):
+ def deleteCollPack(self, name):
"""
Parameters:
- - filename
- - data
+ - name
"""
- self.send_uploadContainer(filename, data)
- self.recv_uploadContainer()
+ self.send_deleteCollPack(name)
+ self.recv_deleteCollPack()
- def send_uploadContainer(self, filename, data):
- self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid)
- args = uploadContainer_args()
- args.filename = filename
- args.data = data
+ def send_deleteCollPack(self, name):
+ self._oprot.writeMessageBegin('deleteCollPack', TMessageType.CALL, self._seqid)
+ args = deleteCollPack_args()
+ args.name = name
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_uploadContainer(self, ):
+ def recv_deleteCollPack(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 = deleteCollPack_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
- def deleteFiles(self, fids):
+ def deleteCollLink(self, url):
"""
Parameters:
- - fids
+ - url
"""
- self.send_deleteFiles(fids)
- self.recv_deleteFiles()
+ self.send_deleteCollLink(url)
+ self.recv_deleteCollLink()
- def send_deleteFiles(self, fids):
- self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid)
- args = deleteFiles_args()
- args.fids = fids
+ def send_deleteCollLink(self, url):
+ self._oprot.writeMessageBegin('deleteCollLink', TMessageType.CALL, self._seqid)
+ args = deleteCollLink_args()
+ args.url = url
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_deleteFiles(self, ):
+ def recv_deleteCollLink(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 = deleteCollLink_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
- def deletePackages(self, pids):
+ def getAllFiles(self, ):
+ self.send_getAllFiles()
+ return self.recv_getAllFiles()
+
+ def send_getAllFiles(self, ):
+ self._oprot.writeMessageBegin('getAllFiles', TMessageType.CALL, self._seqid)
+ args = getAllFiles_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAllFiles(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllFiles failed: unknown result");
+
+ def getAllUnfinishedFiles(self, ):
+ self.send_getAllUnfinishedFiles()
+ return self.recv_getAllUnfinishedFiles()
+
+ def send_getAllUnfinishedFiles(self, ):
+ self._oprot.writeMessageBegin('getAllUnfinishedFiles', TMessageType.CALL, self._seqid)
+ args = getAllUnfinishedFiles_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAllUnfinishedFiles(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllUnfinishedFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUnfinishedFiles failed: unknown result");
+
+ def getFileTree(self, pid, full):
"""
Parameters:
- - pids
+ - pid
+ - full
"""
- self.send_deletePackages(pids)
- self.recv_deletePackages()
+ self.send_getFileTree(pid, full)
+ return self.recv_getFileTree()
- def send_deletePackages(self, pids):
- self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid)
- args = deletePackages_args()
- args.pids = pids
+ def send_getFileTree(self, pid, full):
+ self._oprot.writeMessageBegin('getFileTree', TMessageType.CALL, self._seqid)
+ args = getFileTree_args()
+ args.pid = pid
+ args.full = full
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_deletePackages(self, ):
+ def recv_getFileTree(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 = getFileTree_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- return
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileTree failed: unknown result");
- def pushToQueue(self, pid):
+ def getUnfinishedFileTree(self, pid, full):
"""
Parameters:
- pid
+ - full
"""
- self.send_pushToQueue(pid)
- self.recv_pushToQueue()
+ self.send_getUnfinishedFileTree(pid, full)
+ return self.recv_getUnfinishedFileTree()
- def send_pushToQueue(self, pid):
- self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid)
- args = pushToQueue_args()
+ def send_getUnfinishedFileTree(self, pid, full):
+ self._oprot.writeMessageBegin('getUnfinishedFileTree', TMessageType.CALL, self._seqid)
+ args = getUnfinishedFileTree_args()
args.pid = pid
+ args.full = full
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_pushToQueue(self, ):
+ def recv_getUnfinishedFileTree(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 = getUnfinishedFileTree_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- return
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getUnfinishedFileTree failed: unknown result");
- def pullFromQueue(self, pid):
+ def getPackageContent(self, pid):
"""
Parameters:
- pid
"""
- self.send_pullFromQueue(pid)
- self.recv_pullFromQueue()
+ self.send_getPackageContent(pid)
+ return self.recv_getPackageContent()
- def send_pullFromQueue(self, pid):
- self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid)
- args = pullFromQueue_args()
+ def send_getPackageContent(self, pid):
+ self._oprot.writeMessageBegin('getPackageContent', TMessageType.CALL, self._seqid)
+ args = getPackageContent_args()
args.pid = pid
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_pullFromQueue(self, ):
+ def recv_getPackageContent(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 = getPackageContent_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- return
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageContent 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 getFileInfo(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ self.send_getFileInfo(fid)
+ return self.recv_getFileInfo()
+
+ def send_getFileInfo(self, fid):
+ self._oprot.writeMessageBegin('getFileInfo', TMessageType.CALL, self._seqid)
+ args = getFileInfo_args()
+ args.fid = fid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getFileInfo(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getFileInfo_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, "getFileInfo failed: unknown result");
+
+ def findFiles(self, pattern):
+ """
+ Parameters:
+ - pattern
+ """
+ self.send_findFiles(pattern)
+ return self.recv_findFiles()
+
+ def send_findFiles(self, pattern):
+ self._oprot.writeMessageBegin('findFiles', TMessageType.CALL, self._seqid)
+ args = findFiles_args()
+ args.pattern = pattern
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_findFiles(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = findFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "findFiles failed: unknown result");
def restartPackage(self, pid):
"""
@@ -1638,25 +2107,25 @@ class Client(Iface):
self._iprot.readMessageEnd()
return
- def stopAllDownloads(self, ):
- self.send_stopAllDownloads()
- self.recv_stopAllDownloads()
+ def restartFailed(self, ):
+ self.send_restartFailed()
+ self.recv_restartFailed()
- def send_stopAllDownloads(self, ):
- self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid)
- args = stopAllDownloads_args()
+ 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_stopAllDownloads(self, ):
+ 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 = stopAllDownloads_result()
+ result = restartFailed_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
@@ -1689,50 +2158,141 @@ class Client(Iface):
self._iprot.readMessageEnd()
return
- def setPackageName(self, pid, name):
+ 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 setPackagePaused(self, pid, paused):
"""
Parameters:
- pid
- - name
+ - paused
"""
- self.send_setPackageName(pid, name)
- self.recv_setPackageName()
+ self.send_setPackagePaused(pid, paused)
+ self.recv_setPackagePaused()
- def send_setPackageName(self, pid, name):
- self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid)
- args = setPackageName_args()
+ def send_setPackagePaused(self, pid, paused):
+ self._oprot.writeMessageBegin('setPackagePaused', TMessageType.CALL, self._seqid)
+ args = setPackagePaused_args()
args.pid = pid
- args.name = name
+ args.paused = paused
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_setPackageName(self, ):
+ def recv_setPackagePaused(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 = setPackagePaused_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
+ if result.e is not None:
+ raise result.e
return
- def movePackage(self, destination, pid):
+ def setPackageFolder(self, pid, path):
"""
Parameters:
- - destination
- pid
+ - path
"""
- self.send_movePackage(destination, pid)
- self.recv_movePackage()
+ self.send_setPackageFolder(pid, path)
+ return self.recv_setPackageFolder()
- def send_movePackage(self, destination, pid):
+ def send_setPackageFolder(self, pid, path):
+ self._oprot.writeMessageBegin('setPackageFolder', TMessageType.CALL, self._seqid)
+ args = setPackageFolder_args()
+ args.pid = pid
+ args.path = path
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setPackageFolder(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPackageFolder_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, "setPackageFolder failed: unknown result");
+
+ 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 movePackage(self, pid, root):
+ """
+ Parameters:
+ - pid
+ - root
+ """
+ self.send_movePackage(pid, root)
+ return self.recv_movePackage()
+
+ def send_movePackage(self, pid, root):
self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid)
args = movePackage_args()
- args.destination = destination
args.pid = pid
+ args.root = root
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
@@ -1747,7 +2307,11 @@ class Client(Iface):
result = movePackage_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- return
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "movePackage failed: unknown result");
def moveFiles(self, fids, pid):
"""
@@ -1756,7 +2320,7 @@ class Client(Iface):
- pid
"""
self.send_moveFiles(fids, pid)
- self.recv_moveFiles()
+ return self.recv_moveFiles()
def send_moveFiles(self, fids, pid):
self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid)
@@ -1777,21 +2341,25 @@ class Client(Iface):
result = moveFiles_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- return
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "moveFiles failed: unknown result");
- def orderPackage(self, pid, position):
+ def orderPackage(self, pids, position):
"""
Parameters:
- - pid
+ - pids
- position
"""
- self.send_orderPackage(pid, position)
+ self.send_orderPackage(pids, position)
self.recv_orderPackage()
- def send_orderPackage(self, pid, position):
+ def send_orderPackage(self, pids, position):
self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid)
args = orderPackage_args()
- args.pid = pid
+ args.pids = pids
args.position = position
args.write(self._oprot)
self._oprot.writeMessageEnd()
@@ -1809,116 +2377,185 @@ class Client(Iface):
self._iprot.readMessageEnd()
return
- def orderFile(self, fid, position):
+ def orderFiles(self, fids, pid, position):
"""
Parameters:
- - fid
+ - fids
+ - pid
- position
"""
- self.send_orderFile(fid, position)
- self.recv_orderFile()
+ self.send_orderFiles(fids, pid, position)
+ self.recv_orderFiles()
- def send_orderFile(self, fid, position):
- self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid)
- args = orderFile_args()
- args.fid = fid
+ def send_orderFiles(self, fids, pid, position):
+ self._oprot.writeMessageBegin('orderFiles', TMessageType.CALL, self._seqid)
+ args = orderFiles_args()
+ args.fids = fids
+ args.pid = pid
args.position = position
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_orderFile(self, ):
+ def recv_orderFiles(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 = orderFiles_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
- def setPackageData(self, pid, data):
+ def isInteractionWaiting(self, mode):
"""
Parameters:
- - pid
- - data
+ - mode
"""
- self.send_setPackageData(pid, data)
- self.recv_setPackageData()
+ self.send_isInteractionWaiting(mode)
+ return self.recv_isInteractionWaiting()
- def send_setPackageData(self, pid, data):
- self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid)
- args = setPackageData_args()
- args.pid = pid
- args.data = data
+ def send_isInteractionWaiting(self, mode):
+ self._oprot.writeMessageBegin('isInteractionWaiting', TMessageType.CALL, self._seqid)
+ args = isInteractionWaiting_args()
+ args.mode = mode
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_setPackageData(self, ):
+ def recv_isInteractionWaiting(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 = isInteractionWaiting_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
- if result.e is not None:
- raise result.e
- return
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isInteractionWaiting failed: unknown result");
- def deleteFinished(self, ):
- self.send_deleteFinished()
- return self.recv_deleteFinished()
+ def getInteractionTask(self, mode):
+ """
+ Parameters:
+ - mode
+ """
+ self.send_getInteractionTask(mode)
+ return self.recv_getInteractionTask()
- def send_deleteFinished(self, ):
- self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid)
- args = deleteFinished_args()
+ def send_getInteractionTask(self, mode):
+ self._oprot.writeMessageBegin('getInteractionTask', TMessageType.CALL, self._seqid)
+ args = getInteractionTask_args()
+ args.mode = mode
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_deleteFinished(self, ):
+ def recv_getInteractionTask(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 = getInteractionTask_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getInteractionTask failed: unknown result");
- def restartFailed(self, ):
- self.send_restartFailed()
- self.recv_restartFailed()
+ def setInteractionResult(self, iid, result):
+ """
+ Parameters:
+ - iid
+ - result
+ """
+ self.send_setInteractionResult(iid, result)
+ self.recv_setInteractionResult()
- def send_restartFailed(self, ):
- self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid)
- args = restartFailed_args()
+ def send_setInteractionResult(self, iid, result):
+ self._oprot.writeMessageBegin('setInteractionResult', TMessageType.CALL, self._seqid)
+ args = setInteractionResult_args()
+ args.iid = iid
+ args.result = result
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_restartFailed(self, ):
+ def recv_setInteractionResult(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 = setInteractionResult_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
return
+ def generateDownloadLink(self, fid, timeout):
+ """
+ Parameters:
+ - fid
+ - timeout
+ """
+ self.send_generateDownloadLink(fid, timeout)
+ return self.recv_generateDownloadLink()
+
+ def send_generateDownloadLink(self, fid, timeout):
+ self._oprot.writeMessageBegin('generateDownloadLink', TMessageType.CALL, self._seqid)
+ args = generateDownloadLink_args()
+ args.fid = fid
+ args.timeout = timeout
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_generateDownloadLink(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = generateDownloadLink_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generateDownloadLink failed: unknown result");
+
+ def getNotifications(self, ):
+ self.send_getNotifications()
+ return self.recv_getNotifications()
+
+ def send_getNotifications(self, ):
+ self._oprot.writeMessageBegin('getNotifications', TMessageType.CALL, self._seqid)
+ args = getNotifications_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getNotifications(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getNotifications_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getNotifications failed: unknown result");
+
def getEvents(self, uuid):
"""
Parameters:
@@ -2004,24 +2641,22 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result");
- def updateAccount(self, plugin, account, password, options):
+ def updateAccount(self, plugin, account, password):
"""
Parameters:
- plugin
- account
- password
- - options
"""
- self.send_updateAccount(plugin, account, password, options)
+ self.send_updateAccount(plugin, account, password)
self.recv_updateAccount()
- def send_updateAccount(self, plugin, account, password, options):
+ def send_updateAccount(self, plugin, account, password):
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()
@@ -2038,6 +2673,34 @@ class Client(Iface):
self._iprot.readMessageEnd()
return
+ def updateAccountInfo(self, account):
+ """
+ Parameters:
+ - account
+ """
+ self.send_updateAccountInfo(account)
+ self.recv_updateAccountInfo()
+
+ def send_updateAccountInfo(self, account):
+ self._oprot.writeMessageBegin('updateAccountInfo', TMessageType.CALL, self._seqid)
+ args = updateAccountInfo_args()
+ args.account = account
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_updateAccountInfo(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = updateAccountInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
def removeAccount(self, plugin, account):
"""
Parameters:
@@ -2100,20 +2763,13 @@ class Client(Iface):
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)
+ def getUserData(self, ):
+ self.send_getUserData()
return self.recv_getUserData()
- def send_getUserData(self, username, password):
+ def send_getUserData(self, ):
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()
@@ -2157,96 +2813,127 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result");
- def getServices(self, ):
- self.send_getServices()
- return self.recv_getServices()
+ def addUser(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ self.send_addUser(username, password)
+ return self.recv_addUser()
- def send_getServices(self, ):
- self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid)
- args = getServices_args()
+ def send_addUser(self, username, password):
+ self._oprot.writeMessageBegin('addUser', TMessageType.CALL, self._seqid)
+ args = addUser_args()
+ args.username = username
+ args.password = password
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getServices(self, ):
+ def recv_addUser(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 = addUser_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addUser failed: unknown result");
- def hasService(self, plugin, func):
+ def updateUserData(self, data):
"""
Parameters:
- - plugin
- - func
+ - data
"""
- self.send_hasService(plugin, func)
- return self.recv_hasService()
+ self.send_updateUserData(data)
+ self.recv_updateUserData()
- def send_hasService(self, plugin, func):
- self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid)
- args = hasService_args()
- args.plugin = plugin
- args.func = func
+ def send_updateUserData(self, data):
+ self._oprot.writeMessageBegin('updateUserData', TMessageType.CALL, self._seqid)
+ args = updateUserData_args()
+ args.data = data
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_hasService(self, ):
+ def recv_updateUserData(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 = updateUserData_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");
+ return
- def call(self, info):
+ def removeUser(self, uid):
"""
Parameters:
- - info
+ - uid
"""
- self.send_call(info)
- return self.recv_call()
+ self.send_removeUser(uid)
+ self.recv_removeUser()
- def send_call(self, info):
- self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid)
- args = call_args()
- args.info = info
+ def send_removeUser(self, uid):
+ self._oprot.writeMessageBegin('removeUser', TMessageType.CALL, self._seqid)
+ args = removeUser_args()
+ args.uid = uid
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_call(self, ):
+ def recv_removeUser(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 = removeUser_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def setPassword(self, username, old_password, new_password):
+ """
+ Parameters:
+ - username
+ - old_password
+ - new_password
+ """
+ self.send_setPassword(username, old_password, new_password)
+ return self.recv_setPassword()
+
+ def send_setPassword(self, username, old_password, new_password):
+ self._oprot.writeMessageBegin('setPassword', TMessageType.CALL, self._seqid)
+ args = setPassword_args()
+ args.username = username
+ args.old_password = old_password
+ args.new_password = new_password
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setPassword(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPassword_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "setPassword failed: unknown result");
def getAllInfo(self, ):
self.send_getAllInfo()
@@ -2303,119 +2990,133 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result");
- def isCaptchaWaiting(self, ):
- self.send_isCaptchaWaiting()
- return self.recv_isCaptchaWaiting()
+ def getAddonHandler(self, ):
+ self.send_getAddonHandler()
+ return self.recv_getAddonHandler()
- def send_isCaptchaWaiting(self, ):
- self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid)
- args = isCaptchaWaiting_args()
+ def send_getAddonHandler(self, ):
+ self._oprot.writeMessageBegin('getAddonHandler', TMessageType.CALL, self._seqid)
+ args = getAddonHandler_args()
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_isCaptchaWaiting(self, ):
+ def recv_getAddonHandler(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 = getAddonHandler_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAddonHandler failed: unknown result");
- def getCaptchaTask(self, exclusive):
+ def hasAddonHandler(self, plugin, func):
"""
Parameters:
- - exclusive
+ - plugin
+ - func
"""
- self.send_getCaptchaTask(exclusive)
- return self.recv_getCaptchaTask()
+ self.send_hasAddonHandler(plugin, func)
+ return self.recv_hasAddonHandler()
- def send_getCaptchaTask(self, exclusive):
- self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid)
- args = getCaptchaTask_args()
- args.exclusive = exclusive
+ def send_hasAddonHandler(self, plugin, func):
+ self._oprot.writeMessageBegin('hasAddonHandler', TMessageType.CALL, self._seqid)
+ args = hasAddonHandler_args()
+ args.plugin = plugin
+ args.func = func
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getCaptchaTask(self, ):
+ def recv_hasAddonHandler(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 = hasAddonHandler_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");
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "hasAddonHandler failed: unknown result");
- def getCaptchaTaskStatus(self, tid):
+ def callAddon(self, plugin, func, arguments):
"""
Parameters:
- - tid
+ - plugin
+ - func
+ - arguments
"""
- self.send_getCaptchaTaskStatus(tid)
- return self.recv_getCaptchaTaskStatus()
+ self.send_callAddon(plugin, func, arguments)
+ self.recv_callAddon()
- def send_getCaptchaTaskStatus(self, tid):
- self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid)
- args = getCaptchaTaskStatus_args()
- args.tid = tid
+ def send_callAddon(self, plugin, func, arguments):
+ self._oprot.writeMessageBegin('callAddon', TMessageType.CALL, self._seqid)
+ args = callAddon_args()
+ args.plugin = plugin
+ args.func = func
+ args.arguments = arguments
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_getCaptchaTaskStatus(self, ):
+ def recv_callAddon(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 = callAddon_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");
+ if result.e is not None:
+ raise result.e
+ if result.ex is not None:
+ raise result.ex
+ return
- def setCaptchaResult(self, tid, result):
+ def callAddonHandler(self, plugin, func, pid_or_fid):
"""
Parameters:
- - tid
- - result
+ - plugin
+ - func
+ - pid_or_fid
"""
- self.send_setCaptchaResult(tid, result)
- self.recv_setCaptchaResult()
+ self.send_callAddonHandler(plugin, func, pid_or_fid)
+ self.recv_callAddonHandler()
- def send_setCaptchaResult(self, tid, result):
- self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid)
- args = setCaptchaResult_args()
- args.tid = tid
- args.result = result
+ def send_callAddonHandler(self, plugin, func, pid_or_fid):
+ self._oprot.writeMessageBegin('callAddonHandler', TMessageType.CALL, self._seqid)
+ args = callAddonHandler_args()
+ args.plugin = plugin
+ args.func = func
+ args.pid_or_fid = pid_or_fid
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
- def recv_setCaptchaResult(self, ):
+ def recv_callAddonHandler(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 = callAddonHandler_result()
result.read(self._iprot)
self._iprot.readMessageEnd()
+ if result.e is not None:
+ raise result.e
+ if result.ex is not None:
+ raise result.ex
return
@@ -2423,76 +3124,93 @@ 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["getServerVersion"] = Processor.process_getServerVersion
+ self._processMap["statusServer"] = Processor.process_statusServer
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["getProgressInfo"] = Processor.process_getProgressInfo
+ self._processMap["getConfig"] = Processor.process_getConfig
+ self._processMap["getGlobalPlugins"] = Processor.process_getGlobalPlugins
+ self._processMap["getUserPlugins"] = Processor.process_getUserPlugins
+ self._processMap["configurePlugin"] = Processor.process_configurePlugin
+ self._processMap["saveConfig"] = Processor.process_saveConfig
+ self._processMap["deleteConfig"] = Processor.process_deleteConfig
+ self._processMap["setConfigHandler"] = Processor.process_setConfigHandler
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["generatePackages"] = Processor.process_generatePackages
self._processMap["generateAndAddPackages"] = Processor.process_generateAndAddPackages
+ self._processMap["createPackage"] = Processor.process_createPackage
self._processMap["addPackage"] = Processor.process_addPackage
- self._processMap["addFiles"] = Processor.process_addFiles
+ self._processMap["addPackageP"] = Processor.process_addPackageP
+ self._processMap["addPackageChild"] = Processor.process_addPackageChild
self._processMap["uploadContainer"] = Processor.process_uploadContainer
+ self._processMap["addLinks"] = Processor.process_addLinks
+ self._processMap["addLocalFile"] = Processor.process_addLocalFile
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["getCollector"] = Processor.process_getCollector
+ self._processMap["addToCollector"] = Processor.process_addToCollector
+ self._processMap["addFromCollector"] = Processor.process_addFromCollector
+ self._processMap["renameCollPack"] = Processor.process_renameCollPack
+ self._processMap["deleteCollPack"] = Processor.process_deleteCollPack
+ self._processMap["deleteCollLink"] = Processor.process_deleteCollLink
+ self._processMap["getAllFiles"] = Processor.process_getAllFiles
+ self._processMap["getAllUnfinishedFiles"] = Processor.process_getAllUnfinishedFiles
+ self._processMap["getFileTree"] = Processor.process_getFileTree
+ self._processMap["getUnfinishedFileTree"] = Processor.process_getUnfinishedFileTree
+ self._processMap["getPackageContent"] = Processor.process_getPackageContent
+ self._processMap["getPackageInfo"] = Processor.process_getPackageInfo
+ self._processMap["getFileInfo"] = Processor.process_getFileInfo
+ self._processMap["findFiles"] = Processor.process_findFiles
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["restartFailed"] = Processor.process_restartFailed
self._processMap["stopDownloads"] = Processor.process_stopDownloads
- self._processMap["setPackageName"] = Processor.process_setPackageName
+ self._processMap["stopAllDownloads"] = Processor.process_stopAllDownloads
+ self._processMap["setPackagePaused"] = Processor.process_setPackagePaused
+ self._processMap["setPackageFolder"] = Processor.process_setPackageFolder
+ self._processMap["setPackageData"] = Processor.process_setPackageData
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["orderFiles"] = Processor.process_orderFiles
+ self._processMap["isInteractionWaiting"] = Processor.process_isInteractionWaiting
+ self._processMap["getInteractionTask"] = Processor.process_getInteractionTask
+ self._processMap["setInteractionResult"] = Processor.process_setInteractionResult
+ self._processMap["generateDownloadLink"] = Processor.process_generateDownloadLink
+ self._processMap["getNotifications"] = Processor.process_getNotifications
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["updateAccountInfo"] = Processor.process_updateAccountInfo
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["addUser"] = Processor.process_addUser
+ self._processMap["updateUserData"] = Processor.process_updateUserData
+ self._processMap["removeUser"] = Processor.process_removeUser
+ self._processMap["setPassword"] = Processor.process_setPassword
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
+ self._processMap["getAddonHandler"] = Processor.process_getAddonHandler
+ self._processMap["hasAddonHandler"] = Processor.process_hasAddonHandler
+ self._processMap["callAddon"] = Processor.process_callAddon
+ self._processMap["callAddonHandler"] = Processor.process_callAddonHandler
def process(self, iprot, oprot):
(name, type, seqid) = iprot.readMessageBegin()
@@ -2509,46 +3227,24 @@ class Processor(Iface, TProcessor):
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()
+ def process_getServerVersion(self, seqid, iprot, oprot):
+ args = getServerVersion_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getConfig_result()
- result.success = self._handler.getConfig()
- oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid)
+ result = getServerVersion_result()
+ result.success = self._handler.getServerVersion()
+ oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getPluginConfig(self, seqid, iprot, oprot):
- args = getPluginConfig_args()
+ def process_statusServer(self, seqid, iprot, oprot):
+ args = statusServer_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getPluginConfig_result()
- result.success = self._handler.getPluginConfig()
- oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid)
+ result = statusServer_result()
+ result.success = self._handler.statusServer()
+ oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -2586,17 +3282,6 @@ class Processor(Iface, TProcessor):
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)
@@ -2608,17 +3293,6 @@ class Processor(Iface, TProcessor):
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)
@@ -2685,13 +3359,90 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_generatePackages(self, seqid, iprot, oprot):
- args = generatePackages_args()
+ def process_getProgressInfo(self, seqid, iprot, oprot):
+ args = getProgressInfo_args()
args.read(iprot)
iprot.readMessageEnd()
- result = generatePackages_result()
- result.success = self._handler.generatePackages(args.links)
- oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid)
+ result = getProgressInfo_result()
+ result.success = self._handler.getProgressInfo()
+ oprot.writeMessageBegin("getProgressInfo", 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_getGlobalPlugins(self, seqid, iprot, oprot):
+ args = getGlobalPlugins_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getGlobalPlugins_result()
+ result.success = self._handler.getGlobalPlugins()
+ oprot.writeMessageBegin("getGlobalPlugins", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getUserPlugins(self, seqid, iprot, oprot):
+ args = getUserPlugins_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getUserPlugins_result()
+ result.success = self._handler.getUserPlugins()
+ oprot.writeMessageBegin("getUserPlugins", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_configurePlugin(self, seqid, iprot, oprot):
+ args = configurePlugin_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = configurePlugin_result()
+ result.success = self._handler.configurePlugin(args.plugin)
+ oprot.writeMessageBegin("configurePlugin", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_saveConfig(self, seqid, iprot, oprot):
+ args = saveConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = saveConfig_result()
+ self._handler.saveConfig(args.config)
+ oprot.writeMessageBegin("saveConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_deleteConfig(self, seqid, iprot, oprot):
+ args = deleteConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deleteConfig_result()
+ self._handler.deleteConfig(args.plugin)
+ oprot.writeMessageBegin("deleteConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setConfigHandler(self, seqid, iprot, oprot):
+ args = setConfigHandler_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setConfigHandler_result()
+ self._handler.setConfigHandler(args.plugin, args.iid, args.value)
+ oprot.writeMessageBegin("setConfigHandler", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -2751,66 +3502,129 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_statusDownloads(self, seqid, iprot, oprot):
- args = statusDownloads_args()
+ 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_generateAndAddPackages(self, seqid, iprot, oprot):
+ args = generateAndAddPackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generateAndAddPackages_result()
+ result.success = self._handler.generateAndAddPackages(args.links, args.paused)
+ oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_createPackage(self, seqid, iprot, oprot):
+ args = createPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = createPackage_result()
+ result.success = self._handler.createPackage(args.name, args.folder, args.root, args.password, args.site, args.comment, args.paused)
+ oprot.writeMessageBegin("createPackage", 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.password)
+ oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_addPackageP(self, seqid, iprot, oprot):
+ args = addPackageP_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addPackageP_result()
+ result.success = self._handler.addPackageP(args.name, args.links, args.password, args.paused)
+ oprot.writeMessageBegin("addPackageP", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_addPackageChild(self, seqid, iprot, oprot):
+ args = addPackageChild_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addPackageChild_result()
+ result.success = self._handler.addPackageChild(args.name, args.links, args.password, args.root, args.paused)
+ oprot.writeMessageBegin("addPackageChild", 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 = statusDownloads_result()
- result.success = self._handler.statusDownloads()
- oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid)
+ result = uploadContainer_result()
+ result.success = self._handler.uploadContainer(args.filename, args.data)
+ oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getPackageData(self, seqid, iprot, oprot):
- args = getPackageData_args()
+ def process_addLinks(self, seqid, iprot, oprot):
+ args = addLinks_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getPackageData_result()
+ result = addLinks_result()
try:
- result.success = self._handler.getPackageData(args.pid)
+ self._handler.addLinks(args.pid, args.links)
except PackageDoesNotExists, e:
result.e = e
- oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid)
+ oprot.writeMessageBegin("addLinks", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getPackageInfo(self, seqid, iprot, oprot):
- args = getPackageInfo_args()
+ def process_addLocalFile(self, seqid, iprot, oprot):
+ args = addLocalFile_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getPackageInfo_result()
+ result = addLocalFile_result()
try:
- result.success = self._handler.getPackageInfo(args.pid)
+ self._handler.addLocalFile(args.pid, args.name, args.path)
except PackageDoesNotExists, e:
result.e = e
- oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid)
+ oprot.writeMessageBegin("addLocalFile", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getFileData(self, seqid, iprot, oprot):
- args = getFileData_args()
+ def process_deleteFiles(self, seqid, iprot, oprot):
+ args = deleteFiles_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 = deleteFiles_result()
+ self._handler.deleteFiles(args.fids)
+ oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getQueue(self, seqid, iprot, oprot):
- args = getQueue_args()
+ def process_deletePackages(self, seqid, iprot, oprot):
+ args = deletePackages_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getQueue_result()
- result.success = self._handler.getQueue()
- oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid)
+ result = deletePackages_result()
+ self._handler.deletePackages(args.pids)
+ oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -2826,134 +3640,151 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getQueueData(self, seqid, iprot, oprot):
- args = getQueueData_args()
+ def process_addToCollector(self, seqid, iprot, oprot):
+ args = addToCollector_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getQueueData_result()
- result.success = self._handler.getQueueData()
- oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid)
+ result = addToCollector_result()
+ self._handler.addToCollector(args.links)
+ oprot.writeMessageBegin("addToCollector", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getCollectorData(self, seqid, iprot, oprot):
- args = getCollectorData_args()
+ def process_addFromCollector(self, seqid, iprot, oprot):
+ args = addFromCollector_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getCollectorData_result()
- result.success = self._handler.getCollectorData()
- oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid)
+ result = addFromCollector_result()
+ result.success = self._handler.addFromCollector(args.name, args.paused)
+ oprot.writeMessageBegin("addFromCollector", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getPackageOrder(self, seqid, iprot, oprot):
- args = getPackageOrder_args()
+ def process_renameCollPack(self, seqid, iprot, oprot):
+ args = renameCollPack_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getPackageOrder_result()
- result.success = self._handler.getPackageOrder(args.destination)
- oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid)
+ result = renameCollPack_result()
+ self._handler.renameCollPack(args.name, args.new_name)
+ oprot.writeMessageBegin("renameCollPack", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getFileOrder(self, seqid, iprot, oprot):
- args = getFileOrder_args()
+ def process_deleteCollPack(self, seqid, iprot, oprot):
+ args = deleteCollPack_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getFileOrder_result()
- result.success = self._handler.getFileOrder(args.pid)
- oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid)
+ result = deleteCollPack_result()
+ self._handler.deleteCollPack(args.name)
+ oprot.writeMessageBegin("deleteCollPack", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_generateAndAddPackages(self, seqid, iprot, oprot):
- args = generateAndAddPackages_args()
+ def process_deleteCollLink(self, seqid, iprot, oprot):
+ args = deleteCollLink_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 = deleteCollLink_result()
+ self._handler.deleteCollLink(args.url)
+ oprot.writeMessageBegin("deleteCollLink", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_addPackage(self, seqid, iprot, oprot):
- args = addPackage_args()
+ def process_getAllFiles(self, seqid, iprot, oprot):
+ args = getAllFiles_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 = getAllFiles_result()
+ result.success = self._handler.getAllFiles()
+ oprot.writeMessageBegin("getAllFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_addFiles(self, seqid, iprot, oprot):
- args = addFiles_args()
+ def process_getAllUnfinishedFiles(self, seqid, iprot, oprot):
+ args = getAllUnfinishedFiles_args()
args.read(iprot)
iprot.readMessageEnd()
- result = addFiles_result()
- self._handler.addFiles(args.pid, args.links)
- oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid)
+ result = getAllUnfinishedFiles_result()
+ result.success = self._handler.getAllUnfinishedFiles()
+ oprot.writeMessageBegin("getAllUnfinishedFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_uploadContainer(self, seqid, iprot, oprot):
- args = uploadContainer_args()
+ def process_getFileTree(self, seqid, iprot, oprot):
+ args = getFileTree_args()
args.read(iprot)
iprot.readMessageEnd()
- result = uploadContainer_result()
- self._handler.uploadContainer(args.filename, args.data)
- oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
+ result = getFileTree_result()
+ result.success = self._handler.getFileTree(args.pid, args.full)
+ oprot.writeMessageBegin("getFileTree", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_deleteFiles(self, seqid, iprot, oprot):
- args = deleteFiles_args()
+ def process_getUnfinishedFileTree(self, seqid, iprot, oprot):
+ args = getUnfinishedFileTree_args()
args.read(iprot)
iprot.readMessageEnd()
- result = deleteFiles_result()
- self._handler.deleteFiles(args.fids)
- oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
+ result = getUnfinishedFileTree_result()
+ result.success = self._handler.getUnfinishedFileTree(args.pid, args.full)
+ oprot.writeMessageBegin("getUnfinishedFileTree", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_deletePackages(self, seqid, iprot, oprot):
- args = deletePackages_args()
+ def process_getPackageContent(self, seqid, iprot, oprot):
+ args = getPackageContent_args()
args.read(iprot)
iprot.readMessageEnd()
- result = deletePackages_result()
- self._handler.deletePackages(args.pids)
- oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
+ result = getPackageContent_result()
+ result.success = self._handler.getPackageContent(args.pid)
+ oprot.writeMessageBegin("getPackageContent", 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_pushToQueue(self, seqid, iprot, oprot):
- args = pushToQueue_args()
+ def process_getFileInfo(self, seqid, iprot, oprot):
+ args = getFileInfo_args()
args.read(iprot)
iprot.readMessageEnd()
- result = pushToQueue_result()
- self._handler.pushToQueue(args.pid)
- oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid)
+ result = getFileInfo_result()
+ try:
+ result.success = self._handler.getFileInfo(args.fid)
+ except FileDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getFileInfo", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_pullFromQueue(self, seqid, iprot, oprot):
- args = pullFromQueue_args()
+ def process_findFiles(self, seqid, iprot, oprot):
+ args = findFiles_args()
args.read(iprot)
iprot.readMessageEnd()
- result = pullFromQueue_result()
- self._handler.pullFromQueue(args.pid)
- oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid)
+ result = findFiles_result()
+ result.success = self._handler.findFiles(args.pattern)
+ oprot.writeMessageBegin("findFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -2991,13 +3822,13 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_stopAllDownloads(self, seqid, iprot, oprot):
- args = stopAllDownloads_args()
+ def process_restartFailed(self, seqid, iprot, oprot):
+ args = restartFailed_args()
args.read(iprot)
iprot.readMessageEnd()
- result = stopAllDownloads_result()
- self._handler.stopAllDownloads()
- oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid)
+ result = restartFailed_result()
+ self._handler.restartFailed()
+ oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -3013,13 +3844,55 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_setPackageName(self, seqid, iprot, oprot):
- args = setPackageName_args()
+ 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_setPackagePaused(self, seqid, iprot, oprot):
+ args = setPackagePaused_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPackagePaused_result()
+ try:
+ self._handler.setPackagePaused(args.pid, args.paused)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("setPackagePaused", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setPackageFolder(self, seqid, iprot, oprot):
+ args = setPackageFolder_args()
args.read(iprot)
iprot.readMessageEnd()
- result = setPackageName_result()
- self._handler.setPackageName(args.pid, args.name)
- oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid)
+ result = setPackageFolder_result()
+ try:
+ result.success = self._handler.setPackageFolder(args.pid, args.path)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("setPackageFolder", 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()
@@ -3029,7 +3902,10 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = movePackage_result()
- self._handler.movePackage(args.destination, args.pid)
+ try:
+ result.success = self._handler.movePackage(args.pid, args.root)
+ except PackageDoesNotExists, e:
+ result.e = e
oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
@@ -3040,7 +3916,10 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = moveFiles_result()
- self._handler.moveFiles(args.fids, args.pid)
+ try:
+ result.success = self._handler.moveFiles(args.fids, args.pid)
+ except PackageDoesNotExists, e:
+ result.e = e
oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
@@ -3051,55 +3930,74 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = orderPackage_result()
- self._handler.orderPackage(args.pid, args.position)
+ self._handler.orderPackage(args.pids, 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()
+ def process_orderFiles(self, seqid, iprot, oprot):
+ args = orderFiles_args()
args.read(iprot)
iprot.readMessageEnd()
- result = orderFile_result()
- self._handler.orderFile(args.fid, args.position)
- oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid)
+ result = orderFiles_result()
+ self._handler.orderFiles(args.fids, args.pid, args.position)
+ oprot.writeMessageBegin("orderFiles", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_setPackageData(self, seqid, iprot, oprot):
- args = setPackageData_args()
+ def process_isInteractionWaiting(self, seqid, iprot, oprot):
+ args = isInteractionWaiting_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 = isInteractionWaiting_result()
+ result.success = self._handler.isInteractionWaiting(args.mode)
+ oprot.writeMessageBegin("isInteractionWaiting", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_deleteFinished(self, seqid, iprot, oprot):
- args = deleteFinished_args()
+ def process_getInteractionTask(self, seqid, iprot, oprot):
+ args = getInteractionTask_args()
args.read(iprot)
iprot.readMessageEnd()
- result = deleteFinished_result()
- result.success = self._handler.deleteFinished()
- oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid)
+ result = getInteractionTask_result()
+ result.success = self._handler.getInteractionTask(args.mode)
+ oprot.writeMessageBegin("getInteractionTask", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_restartFailed(self, seqid, iprot, oprot):
- args = restartFailed_args()
+ def process_setInteractionResult(self, seqid, iprot, oprot):
+ args = setInteractionResult_args()
args.read(iprot)
iprot.readMessageEnd()
- result = restartFailed_result()
- self._handler.restartFailed()
- oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
+ result = setInteractionResult_result()
+ self._handler.setInteractionResult(args.iid, args.result)
+ oprot.writeMessageBegin("setInteractionResult", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_generateDownloadLink(self, seqid, iprot, oprot):
+ args = generateDownloadLink_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generateDownloadLink_result()
+ result.success = self._handler.generateDownloadLink(args.fid, args.timeout)
+ oprot.writeMessageBegin("generateDownloadLink", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getNotifications(self, seqid, iprot, oprot):
+ args = getNotifications_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getNotifications_result()
+ result.success = self._handler.getNotifications()
+ oprot.writeMessageBegin("getNotifications", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -3142,12 +4040,23 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = updateAccount_result()
- self._handler.updateAccount(args.plugin, args.account, args.password, args.options)
+ self._handler.updateAccount(args.plugin, args.account, args.password)
oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
+ def process_updateAccountInfo(self, seqid, iprot, oprot):
+ args = updateAccountInfo_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = updateAccountInfo_result()
+ self._handler.updateAccountInfo(args.account)
+ oprot.writeMessageBegin("updateAccountInfo", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
def process_removeAccount(self, seqid, iprot, oprot):
args = removeAccount_args()
args.read(iprot)
@@ -3175,7 +4084,7 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = getUserData_result()
- result.success = self._handler.getUserData(args.username, args.password)
+ result.success = self._handler.getUserData()
oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
@@ -3192,40 +4101,46 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getServices(self, seqid, iprot, oprot):
- args = getServices_args()
+ def process_addUser(self, seqid, iprot, oprot):
+ args = addUser_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getServices_result()
- result.success = self._handler.getServices()
- oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid)
+ result = addUser_result()
+ result.success = self._handler.addUser(args.username, args.password)
+ oprot.writeMessageBegin("addUser", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_hasService(self, seqid, iprot, oprot):
- args = hasService_args()
+ def process_updateUserData(self, seqid, iprot, oprot):
+ args = updateUserData_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 = updateUserData_result()
+ self._handler.updateUserData(args.data)
+ oprot.writeMessageBegin("updateUserData", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_call(self, seqid, iprot, oprot):
- args = call_args()
+ def process_removeUser(self, seqid, iprot, oprot):
+ args = removeUser_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 = removeUser_result()
+ self._handler.removeUser(args.uid)
+ oprot.writeMessageBegin("removeUser", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setPassword(self, seqid, iprot, oprot):
+ args = setPassword_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPassword_result()
+ result.success = self._handler.setPassword(args.username, args.old_password, args.new_password)
+ oprot.writeMessageBegin("setPassword", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -3252,46 +4167,56 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_isCaptchaWaiting(self, seqid, iprot, oprot):
- args = isCaptchaWaiting_args()
+ def process_getAddonHandler(self, seqid, iprot, oprot):
+ args = getAddonHandler_args()
args.read(iprot)
iprot.readMessageEnd()
- result = isCaptchaWaiting_result()
- result.success = self._handler.isCaptchaWaiting()
- oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid)
+ result = getAddonHandler_result()
+ result.success = self._handler.getAddonHandler()
+ oprot.writeMessageBegin("getAddonHandler", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getCaptchaTask(self, seqid, iprot, oprot):
- args = getCaptchaTask_args()
+ def process_hasAddonHandler(self, seqid, iprot, oprot):
+ args = hasAddonHandler_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getCaptchaTask_result()
- result.success = self._handler.getCaptchaTask(args.exclusive)
- oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid)
+ result = hasAddonHandler_result()
+ result.success = self._handler.hasAddonHandler(args.plugin, args.func)
+ oprot.writeMessageBegin("hasAddonHandler", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_getCaptchaTaskStatus(self, seqid, iprot, oprot):
- args = getCaptchaTaskStatus_args()
+ def process_callAddon(self, seqid, iprot, oprot):
+ args = callAddon_args()
args.read(iprot)
iprot.readMessageEnd()
- result = getCaptchaTaskStatus_result()
- result.success = self._handler.getCaptchaTaskStatus(args.tid)
- oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid)
+ result = callAddon_result()
+ try:
+ self._handler.callAddon(args.plugin, args.func, args.arguments)
+ except ServiceDoesNotExists, e:
+ result.e = e
+ except ServiceException, ex:
+ result.ex = ex
+ oprot.writeMessageBegin("callAddon", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- def process_setCaptchaResult(self, seqid, iprot, oprot):
- args = setCaptchaResult_args()
+ def process_callAddonHandler(self, seqid, iprot, oprot):
+ args = callAddonHandler_args()
args.read(iprot)
iprot.readMessageEnd()
- result = setCaptchaResult_result()
- self._handler.setCaptchaResult(args.tid, args.result)
- oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid)
+ result = callAddonHandler_result()
+ try:
+ self._handler.callAddonHandler(args.plugin, args.func, args.pid_or_fid)
+ except ServiceDoesNotExists, e:
+ result.e = e
+ except ServiceException, ex:
+ result.ex = ex
+ oprot.writeMessageBegin("callAddonHandler", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
@@ -3299,34 +4224,16 @@ class Processor(Iface, TProcessor):
# HELPER FUNCTIONS AND STRUCTURES
-class getConfigValue_args(TBase):
- """
- Attributes:
- - category
- - option
- - section
- """
+class getServerVersion_args(TBase):
__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):
+class getServerVersion_result(TBase):
"""
Attributes:
- success
@@ -3344,38 +4251,34 @@ class getConfigValue_result(TBase):
self.success = success
-class setConfigValue_args(TBase):
+class statusServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class statusServer_result(TBase):
"""
Attributes:
- - category
- - option
- - value
- - section
+ - success
"""
__slots__ = [
- 'category',
- 'option',
- 'value',
- 'section',
+ 'success',
]
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
+ (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None, ), # 0
)
- def __init__(self, category=None, option=None, value=None, section=None,):
- self.category = category
- self.option = option
- self.value = value
- self.section = section
+ def __init__(self, success=None,):
+ self.success = success
-class setConfigValue_result(TBase):
+class pauseServer_args(TBase):
__slots__ = [
]
@@ -3384,7 +4287,7 @@ class setConfigValue_result(TBase):
)
-class getConfig_args(TBase):
+class pauseServer_result(TBase):
__slots__ = [
]
@@ -3393,7 +4296,34 @@ class getConfig_args(TBase):
)
-class getConfig_result(TBase):
+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
@@ -3404,14 +4334,14 @@ class getConfig_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getPluginConfig_args(TBase):
+class freeSpace_args(TBase):
__slots__ = [
]
@@ -3420,7 +4350,7 @@ class getPluginConfig_args(TBase):
)
-class getPluginConfig_result(TBase):
+class freeSpace_result(TBase):
"""
Attributes:
- success
@@ -3431,14 +4361,14 @@ class getPluginConfig_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
+ (0, TType.I64, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class pauseServer_args(TBase):
+class kill_args(TBase):
__slots__ = [
]
@@ -3447,7 +4377,7 @@ class pauseServer_args(TBase):
)
-class pauseServer_result(TBase):
+class kill_result(TBase):
__slots__ = [
]
@@ -3456,7 +4386,7 @@ class pauseServer_result(TBase):
)
-class unpauseServer_args(TBase):
+class restart_args(TBase):
__slots__ = [
]
@@ -3465,7 +4395,7 @@ class unpauseServer_args(TBase):
)
-class unpauseServer_result(TBase):
+class restart_result(TBase):
__slots__ = [
]
@@ -3474,16 +4404,26 @@ class unpauseServer_result(TBase):
)
-class togglePause_args(TBase):
+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 togglePause_result(TBase):
+
+class getLog_result(TBase):
"""
Attributes:
- success
@@ -3494,14 +4434,14 @@ class togglePause_result(TBase):
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
+ (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class statusServer_args(TBase):
+class isTimeDownload_args(TBase):
__slots__ = [
]
@@ -3510,7 +4450,7 @@ class statusServer_args(TBase):
)
-class statusServer_result(TBase):
+class isTimeDownload_result(TBase):
"""
Attributes:
- success
@@ -3521,14 +4461,14 @@ class statusServer_result(TBase):
]
thrift_spec = (
- (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None, ), # 0
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class freeSpace_args(TBase):
+class isTimeReconnect_args(TBase):
__slots__ = [
]
@@ -3537,7 +4477,7 @@ class freeSpace_args(TBase):
)
-class freeSpace_result(TBase):
+class isTimeReconnect_result(TBase):
"""
Attributes:
- success
@@ -3548,14 +4488,14 @@ class freeSpace_result(TBase):
]
thrift_spec = (
- (0, TType.I64, 'success', None, None, ), # 0
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getServerVersion_args(TBase):
+class toggleReconnect_args(TBase):
__slots__ = [
]
@@ -3564,7 +4504,7 @@ class getServerVersion_args(TBase):
)
-class getServerVersion_result(TBase):
+class toggleReconnect_result(TBase):
"""
Attributes:
- success
@@ -3575,14 +4515,14 @@ class getServerVersion_result(TBase):
]
thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class kill_args(TBase):
+class getProgressInfo_args(TBase):
__slots__ = [
]
@@ -3591,16 +4531,25 @@ class kill_args(TBase):
)
-class kill_result(TBase):
+class getProgressInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT,(ProgressInfo, ProgressInfo.thrift_spec)), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
-class restart_args(TBase):
+
+class getConfig_args(TBase):
__slots__ = [
]
@@ -3609,35 +4558,34 @@ class restart_args(TBase):
)
-class restart_result(TBase):
+class getConfig_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigHolder, ConfigHolder.thrift_spec)), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
-class getLog_args(TBase):
- """
- Attributes:
- - offset
- """
+
+class getGlobalPlugins_args(TBase):
__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):
+class getGlobalPlugins_result(TBase):
"""
Attributes:
- success
@@ -3648,14 +4596,14 @@ class getLog_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
+ (0, TType.LIST, 'success', (TType.STRUCT,(ConfigInfo, ConfigInfo.thrift_spec)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class isTimeDownload_args(TBase):
+class getUserPlugins_args(TBase):
__slots__ = [
]
@@ -3664,7 +4612,7 @@ class isTimeDownload_args(TBase):
)
-class isTimeDownload_result(TBase):
+class getUserPlugins_result(TBase):
"""
Attributes:
- success
@@ -3675,23 +4623,33 @@ class isTimeDownload_result(TBase):
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
+ (0, TType.LIST, 'success', (TType.STRUCT,(ConfigInfo, ConfigInfo.thrift_spec)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class isTimeReconnect_args(TBase):
+class configurePlugin_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 isTimeReconnect_result(TBase):
+
+class configurePlugin_result(TBase):
"""
Attributes:
- success
@@ -3702,75 +4660,103 @@ class isTimeReconnect_result(TBase):
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
+ (0, TType.STRUCT, 'success', (ConfigHolder, ConfigHolder.thrift_spec), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class toggleReconnect_args(TBase):
+class saveConfig_args(TBase):
+ """
+ Attributes:
+ - config
+ """
__slots__ = [
+ 'config',
]
thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'config', (ConfigHolder, ConfigHolder.thrift_spec), None, ), # 1
)
+ def __init__(self, config=None,):
+ self.config = config
-class toggleReconnect_result(TBase):
- """
- Attributes:
- - success
- """
+
+class saveConfig_result(TBase):
__slots__ = [
- 'success',
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
)
- def __init__(self, success=None,):
- self.success = success
-
-class generatePackages_args(TBase):
+class deleteConfig_args(TBase):
"""
Attributes:
- - links
+ - plugin
"""
__slots__ = [
- 'links',
+ 'plugin',
]
thrift_spec = (
None, # 0
- (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
+ (1, TType.STRING, 'plugin', None, None, ), # 1
)
- def __init__(self, links=None,):
- self.links = links
+ def __init__(self, plugin=None,):
+ self.plugin = plugin
-class generatePackages_result(TBase):
+class deleteConfig_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setConfigHandler_args(TBase):
"""
Attributes:
- - success
+ - plugin
+ - iid
+ - value
"""
__slots__ = [
- 'success',
+ 'plugin',
+ 'iid',
+ 'value',
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.I32, 'iid', None, None, ), # 2
+ (3, TType.STRING, 'value', None, None, ), # 3
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, plugin=None, iid=None, value=None,):
+ self.plugin = plugin
+ self.iid = iid
+ self.value = value
+
+
+class setConfigHandler_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
class checkURLs_args(TBase):
@@ -3970,16 +4956,26 @@ class pollResults_result(TBase):
self.success = success
-class statusDownloads_args(TBase):
+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 statusDownloads_result(TBase):
+class generatePackages_result(TBase):
"""
Attributes:
- success
@@ -3990,146 +4986,192 @@ class statusDownloads_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(DownloadInfo, DownloadInfo.thrift_spec)), None, ), # 0
+ (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getPackageData_args(TBase):
+class generateAndAddPackages_args(TBase):
"""
Attributes:
- - pid
+ - links
+ - paused
"""
__slots__ = [
- 'pid',
+ 'links',
+ 'paused',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
+ (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
+ (2, TType.BOOL, 'paused', None, None, ), # 2
)
- def __init__(self, pid=None,):
- self.pid = pid
+ def __init__(self, links=None, paused=None,):
+ self.links = links
+ self.paused = paused
-class getPackageData_result(TBase):
+class generateAndAddPackages_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
+ (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
)
- def __init__(self, success=None, e=None,):
+ def __init__(self, success=None,):
self.success = success
- self.e = e
-class getPackageInfo_args(TBase):
+class createPackage_args(TBase):
"""
Attributes:
- - pid
+ - name
+ - folder
+ - root
+ - password
+ - site
+ - comment
+ - paused
"""
__slots__ = [
- 'pid',
+ 'name',
+ 'folder',
+ 'root',
+ 'password',
+ 'site',
+ 'comment',
+ 'paused',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'folder', None, None, ), # 2
+ (3, TType.I32, 'root', None, None, ), # 3
+ (4, TType.STRING, 'password', None, None, ), # 4
+ (5, TType.STRING, 'site', None, None, ), # 5
+ (6, TType.STRING, 'comment', None, None, ), # 6
+ (7, TType.BOOL, 'paused', None, None, ), # 7
)
- def __init__(self, pid=None,):
- self.pid = pid
+ def __init__(self, name=None, folder=None, root=None, password=None, site=None, comment=None, paused=None,):
+ self.name = name
+ self.folder = folder
+ self.root = root
+ self.password = password
+ self.site = site
+ self.comment = comment
+ self.paused = paused
-class getPackageInfo_result(TBase):
+class createPackage_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
+ (0, TType.I32, 'success', None, None, ), # 0
)
- def __init__(self, success=None, e=None,):
+ def __init__(self, success=None,):
self.success = success
- self.e = e
-class getFileData_args(TBase):
+class addPackage_args(TBase):
"""
Attributes:
- - fid
+ - name
+ - links
+ - password
"""
__slots__ = [
- 'fid',
+ 'name',
+ 'links',
+ 'password',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
+ (3, TType.STRING, 'password', None, None, ), # 3
)
- def __init__(self, fid=None,):
- self.fid = fid
+ def __init__(self, name=None, links=None, password=None,):
+ self.name = name
+ self.links = links
+ self.password = password
-class getFileData_result(TBase):
+class addPackage_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
+ (0, TType.I32, 'success', None, None, ), # 0
)
- def __init__(self, success=None, e=None,):
+ def __init__(self, success=None,):
self.success = success
- self.e = e
-class getQueue_args(TBase):
+class addPackageP_args(TBase):
+ """
+ Attributes:
+ - name
+ - links
+ - password
+ - paused
+ """
__slots__ = [
+ 'name',
+ 'links',
+ 'password',
+ 'paused',
]
thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
+ (3, TType.STRING, 'password', None, None, ), # 3
+ (4, TType.BOOL, 'paused', None, None, ), # 4
)
+ def __init__(self, name=None, links=None, password=None, paused=None,):
+ self.name = name
+ self.links = links
+ self.password = password
+ self.paused = paused
+
-class getQueue_result(TBase):
+class addPackageP_result(TBase):
"""
Attributes:
- success
@@ -4140,23 +5182,49 @@ class getQueue_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
+ (0, TType.I32, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getCollector_args(TBase):
+class addPackageChild_args(TBase):
+ """
+ Attributes:
+ - name
+ - links
+ - password
+ - root
+ - paused
+ """
__slots__ = [
+ 'name',
+ 'links',
+ 'password',
+ 'root',
+ 'paused',
]
thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
+ (3, TType.STRING, 'password', None, None, ), # 3
+ (4, TType.I32, 'root', None, None, ), # 4
+ (5, TType.BOOL, 'paused', None, None, ), # 5
)
+ def __init__(self, name=None, links=None, password=None, root=None, paused=None,):
+ self.name = name
+ self.links = links
+ self.password = password
+ self.root = root
+ self.paused = paused
-class getCollector_result(TBase):
+
+class addPackageChild_result(TBase):
"""
Attributes:
- success
@@ -4167,23 +5235,37 @@ class getCollector_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
+ (0, TType.I32, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getQueueData_args(TBase):
+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 getQueueData_result(TBase):
+class uploadContainer_result(TBase):
"""
Attributes:
- success
@@ -4194,97 +5276,167 @@ class getQueueData_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
+ (0, TType.I32, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getCollectorData_args(TBase):
+class addLinks_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 getCollectorData_result(TBase):
+class addLinks_result(TBase):
"""
Attributes:
- - success
+ - e
"""
__slots__ = [
- 'success',
+ 'e',
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
+ None, # 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, e=None,):
+ self.e = e
-class getPackageOrder_args(TBase):
+class addLocalFile_args(TBase):
"""
Attributes:
- - destination
+ - pid
+ - name
+ - path
"""
__slots__ = [
- 'destination',
+ 'pid',
+ 'name',
+ 'path',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
+ (1, TType.I32, 'pid', None, None, ), # 1
+ (2, TType.STRING, 'name', None, None, ), # 2
+ (3, TType.STRING, 'path', None, None, ), # 3
)
- def __init__(self, destination=None,):
- self.destination = destination
+ def __init__(self, pid=None, name=None, path=None,):
+ self.pid = pid
+ self.name = name
+ self.path = path
-class getPackageOrder_result(TBase):
+class addLocalFile_result(TBase):
"""
Attributes:
- - success
+ - e
"""
__slots__ = [
- 'success',
+ 'e',
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
+ None, # 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, e=None,):
+ self.e = e
-class getFileOrder_args(TBase):
+class deleteFiles_args(TBase):
"""
Attributes:
- - pid
+ - fids
"""
__slots__ = [
- 'pid',
+ 'fids',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
+ (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
)
- def __init__(self, pid=None,):
- self.pid = pid
+ def __init__(self, fids=None,):
+ self.fids = fids
+
+
+class deleteFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
-class getFileOrder_result(TBase):
+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 getCollector_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getCollector_result(TBase):
"""
Attributes:
- success
@@ -4295,37 +5447,65 @@ class getFileOrder_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
+ (0, TType.LIST, 'success', (TType.STRUCT,(LinkStatus, LinkStatus.thrift_spec)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class generateAndAddPackages_args(TBase):
+class addToCollector_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,):
+ def __init__(self, links=None,):
self.links = links
- self.dest = dest
-class generateAndAddPackages_result(TBase):
+class addToCollector_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class addFromCollector_args(TBase):
+ """
+ Attributes:
+ - name
+ - paused
+ """
+
+ __slots__ = [
+ 'name',
+ 'paused',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.BOOL, 'paused', None, None, ), # 2
+ )
+
+ def __init__(self, name=None, paused=None,):
+ self.name = name
+ self.paused = paused
+
+
+class addFromCollector_result(TBase):
"""
Attributes:
- success
@@ -4336,82 +5516,93 @@ class generateAndAddPackages_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
+ (0, TType.I32, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class addPackage_args(TBase):
+class renameCollPack_args(TBase):
"""
Attributes:
- name
- - links
- - dest
+ - new_name
"""
__slots__ = [
'name',
- 'links',
- 'dest',
+ 'new_name',
]
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
+ (2, TType.STRING, 'new_name', None, None, ), # 2
)
- def __init__(self, name=None, links=None, dest=None,):
+ def __init__(self, name=None, new_name=None,):
self.name = name
- self.links = links
- self.dest = dest
+ self.new_name = new_name
-class addPackage_result(TBase):
+class renameCollPack_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deleteCollPack_args(TBase):
"""
Attributes:
- - success
+ - name
"""
__slots__ = [
- 'success',
+ 'name',
]
thrift_spec = (
- (0, TType.I32, 'success', None, None, ), # 0
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, name=None,):
+ self.name = name
+
+
+class deleteCollPack_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
-class addFiles_args(TBase):
+class deleteCollLink_args(TBase):
"""
Attributes:
- - pid
- - links
+ - url
"""
__slots__ = [
- 'pid',
- 'links',
+ 'url',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
+ (1, TType.STRING, 'url', None, None, ), # 1
)
- def __init__(self, pid=None, links=None,):
- self.pid = pid
- self.links = links
+ def __init__(self, url=None,):
+ self.url = url
-class addFiles_result(TBase):
+class deleteCollLink_result(TBase):
__slots__ = [
]
@@ -4420,30 +5611,34 @@ class addFiles_result(TBase):
)
-class uploadContainer_args(TBase):
+class getAllFiles_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAllFiles_result(TBase):
"""
Attributes:
- - filename
- - data
+ - success
"""
__slots__ = [
- 'filename',
- 'data',
+ 'success',
]
thrift_spec = (
- None, # 0
- (1, TType.STRING, 'filename', None, None, ), # 1
- (2, TType.STRING, 'data', None, None, ), # 2
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
)
- def __init__(self, filename=None, data=None,):
- self.filename = filename
- self.data = data
+ def __init__(self, success=None,):
+ self.success = success
-class uploadContainer_result(TBase):
+class getAllUnfinishedFiles_args(TBase):
__slots__ = [
]
@@ -4452,63 +5647,107 @@ class uploadContainer_result(TBase):
)
-class deleteFiles_args(TBase):
+class getAllUnfinishedFiles_result(TBase):
"""
Attributes:
- - fids
+ - success
"""
__slots__ = [
- 'fids',
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getFileTree_args(TBase):
+ """
+ Attributes:
+ - pid
+ - full
+ """
+
+ __slots__ = [
+ 'pid',
+ 'full',
]
thrift_spec = (
None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
+ (1, TType.I32, 'pid', None, None, ), # 1
+ (2, TType.BOOL, 'full', None, None, ), # 2
)
- def __init__(self, fids=None,):
- self.fids = fids
+ def __init__(self, pid=None, full=None,):
+ self.pid = pid
+ self.full = full
-class deleteFiles_result(TBase):
+class getFileTree_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
-class deletePackages_args(TBase):
+
+class getUnfinishedFileTree_args(TBase):
"""
Attributes:
- - pids
+ - pid
+ - full
"""
__slots__ = [
- 'pids',
+ 'pid',
+ 'full',
]
thrift_spec = (
None, # 0
- (1, TType.LIST, 'pids', (TType.I32,None), None, ), # 1
+ (1, TType.I32, 'pid', None, None, ), # 1
+ (2, TType.BOOL, 'full', None, None, ), # 2
)
- def __init__(self, pids=None,):
- self.pids = pids
+ def __init__(self, pid=None, full=None,):
+ self.pid = pid
+ self.full = full
-class deletePackages_result(TBase):
+class getUnfinishedFileTree_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
+
-class pushToQueue_args(TBase):
+class getPackageContent_args(TBase):
"""
Attributes:
- pid
@@ -4527,16 +5766,25 @@ class pushToQueue_args(TBase):
self.pid = pid
-class pushToQueue_result(TBase):
+class getPackageContent_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
+
-class pullFromQueue_args(TBase):
+class getPackageInfo_args(TBase):
"""
Attributes:
- pid
@@ -4555,14 +5803,105 @@ class pullFromQueue_args(TBase):
self.pid = pid
-class pullFromQueue_result(TBase):
+class getPackageInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (PackageInfo, PackageInfo.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 getFileInfo_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 getFileInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (FileInfo, FileInfo.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 findFiles_args(TBase):
+ """
+ Attributes:
+ - pattern
+ """
+
+ __slots__ = [
+ 'pattern',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'pattern', None, None, ), # 1
+ )
+
+ def __init__(self, pattern=None,):
+ self.pattern = pattern
+
+
+class findFiles_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.STRUCT, 'success', (TreeCollection, TreeCollection.thrift_spec), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
+
class restartPackage_args(TBase):
"""
@@ -4648,7 +5987,7 @@ class recheckPackage_result(TBase):
)
-class stopAllDownloads_args(TBase):
+class restartFailed_args(TBase):
__slots__ = [
]
@@ -4657,7 +5996,7 @@ class stopAllDownloads_args(TBase):
)
-class stopAllDownloads_result(TBase):
+class restartFailed_result(TBase):
__slots__ = [
]
@@ -4694,69 +6033,197 @@ class stopDownloads_result(TBase):
)
-class setPackageName_args(TBase):
+class stopAllDownloads_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopAllDownloads_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setPackagePaused_args(TBase):
"""
Attributes:
- pid
- - name
+ - paused
"""
__slots__ = [
'pid',
- 'name',
+ 'paused',
]
thrift_spec = (
None, # 0
(1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
+ (2, TType.BOOL, 'paused', None, None, ), # 2
)
- def __init__(self, pid=None, name=None,):
+ def __init__(self, pid=None, paused=None,):
self.pid = pid
- self.name = name
+ self.paused = paused
+
+
+class setPackagePaused_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 setPackageName_result(TBase):
+class setPackageFolder_args(TBase):
+ """
+ Attributes:
+ - pid
+ - path
+ """
__slots__ = [
+ 'pid',
+ 'path',
]
thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None, ), # 1
+ (2, TType.STRING, 'path', None, None, ), # 2
)
+ def __init__(self, pid=None, path=None,):
+ self.pid = pid
+ self.path = path
+
+
+class setPackageFolder_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, 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 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 movePackage_args(TBase):
"""
Attributes:
- - destination
- pid
+ - root
"""
__slots__ = [
- 'destination',
'pid',
+ 'root',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
- (2, TType.I32, 'pid', None, None, ), # 2
+ (1, TType.I32, 'pid', None, None, ), # 1
+ (2, TType.I32, 'root', None, None, ), # 2
)
- def __init__(self, destination=None, pid=None,):
- self.destination = destination
+ def __init__(self, pid=None, root=None,):
self.pid = pid
+ self.root = root
class movePackage_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
__slots__ = [
+ 'success',
+ 'e',
]
thrift_spec = (
+ (0, TType.BOOL, 'success', None, 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 moveFiles_args(TBase):
"""
@@ -4782,34 +6249,47 @@ class moveFiles_args(TBase):
class moveFiles_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
__slots__ = [
+ 'success',
+ 'e',
]
thrift_spec = (
+ (0, TType.BOOL, 'success', None, 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 orderPackage_args(TBase):
"""
Attributes:
- - pid
+ - pids
- position
"""
__slots__ = [
- 'pid',
+ 'pids',
'position',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
+ (1, TType.LIST, 'pids', (TType.I32,None), None, ), # 1
(2, TType.I16, 'position', None, None, ), # 2
)
- def __init__(self, pid=None, position=None,):
- self.pid = pid
+ def __init__(self, pids=None, position=None,):
+ self.pids = pids
self.position = position
@@ -4822,30 +6302,34 @@ class orderPackage_result(TBase):
)
-class orderFile_args(TBase):
+class orderFiles_args(TBase):
"""
Attributes:
- - fid
+ - fids
+ - pid
- position
"""
__slots__ = [
- 'fid',
+ 'fids',
+ 'pid',
'position',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.I16, 'position', None, None, ), # 2
+ (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
+ (2, TType.I32, 'pid', None, None, ), # 2
+ (3, TType.I16, 'position', None, None, ), # 3
)
- def __init__(self, fid=None, position=None,):
- self.fid = fid
+ def __init__(self, fids=None, pid=None, position=None,):
+ self.fids = fids
+ self.pid = pid
self.position = position
-class orderFile_result(TBase):
+class orderFiles_result(TBase):
__slots__ = [
]
@@ -4854,58 +6338,136 @@ class orderFile_result(TBase):
)
-class setPackageData_args(TBase):
+class isInteractionWaiting_args(TBase):
"""
Attributes:
- - pid
- - data
+ - mode
"""
__slots__ = [
- 'pid',
- 'data',
+ 'mode',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.MAP, 'data', (TType.STRING,None,TType.STRING,None), None, ), # 2
+ (1, TType.I16, 'mode', None, None, ), # 1
)
- def __init__(self, pid=None, data=None,):
- self.pid = pid
- self.data = data
+ def __init__(self, mode=None,):
+ self.mode = mode
-class setPackageData_result(TBase):
+class isInteractionWaiting_result(TBase):
"""
Attributes:
- - e
+ - success
"""
__slots__ = [
- 'e',
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None, ), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getInteractionTask_args(TBase):
+ """
+ Attributes:
+ - mode
+ """
+
+ __slots__ = [
+ 'mode',
]
thrift_spec = (
None, # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
+ (1, TType.I16, 'mode', None, None, ), # 1
)
- def __init__(self, e=None,):
- self.e = e
+ def __init__(self, mode=None,):
+ self.mode = mode
-class deleteFinished_args(TBase):
+class getInteractionTask_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.STRUCT, 'success', (InteractionTask, InteractionTask.thrift_spec), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
-class deleteFinished_result(TBase):
+
+class setInteractionResult_args(TBase):
+ """
+ Attributes:
+ - iid
+ - result
+ """
+
+ __slots__ = [
+ 'iid',
+ 'result',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'iid', None, None, ), # 1
+ (2, TType.STRING, 'result', None, None, ), # 2
+ )
+
+ def __init__(self, iid=None, result=None,):
+ self.iid = iid
+ self.result = result
+
+
+class setInteractionResult_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class generateDownloadLink_args(TBase):
+ """
+ Attributes:
+ - fid
+ - timeout
+ """
+
+ __slots__ = [
+ 'fid',
+ 'timeout',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None, ), # 1
+ (2, TType.I16, 'timeout', None, None, ), # 2
+ )
+
+ def __init__(self, fid=None, timeout=None,):
+ self.fid = fid
+ self.timeout = timeout
+
+
+class generateDownloadLink_result(TBase):
"""
Attributes:
- success
@@ -4916,14 +6478,14 @@ class deleteFinished_result(TBase):
]
thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
+ (0, TType.STRING, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class restartFailed_args(TBase):
+class getNotifications_args(TBase):
__slots__ = [
]
@@ -4932,14 +6494,23 @@ class restartFailed_args(TBase):
)
-class restartFailed_result(TBase):
+class getNotifications_result(TBase):
+ """
+ Attributes:
+ - success
+ """
__slots__ = [
+ 'success',
]
thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT,(InteractionTask, InteractionTask.thrift_spec)), None, ), # 0
)
+ def __init__(self, success=None,):
+ self.success = success
+
class getEvents_args(TBase):
"""
@@ -5048,14 +6619,12 @@ class updateAccount_args(TBase):
- plugin
- account
- password
- - options
"""
__slots__ = [
'plugin',
'account',
'password',
- 'options',
]
thrift_spec = (
@@ -5063,14 +6632,12 @@ class updateAccount_args(TBase):
(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,):
+ def __init__(self, plugin=None, account=None, password=None,):
self.plugin = plugin
self.account = account
self.password = password
- self.options = options
class updateAccount_result(TBase):
@@ -5082,6 +6649,34 @@ class updateAccount_result(TBase):
)
+class updateAccountInfo_args(TBase):
+ """
+ Attributes:
+ - account
+ """
+
+ __slots__ = [
+ 'account',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'account', (AccountInfo, AccountInfo.thrift_spec), None, ), # 1
+ )
+
+ def __init__(self, account=None,):
+ self.account = account
+
+
+class updateAccountInfo_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
class removeAccount_args(TBase):
"""
Attributes:
@@ -5156,27 +6751,13 @@ class login_result(TBase):
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):
"""
@@ -5216,23 +6797,37 @@ class getAllUserData_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(UserData, UserData.thrift_spec)), None, ), # 0
+ (0, TType.MAP, 'success', (TType.I32,None,TType.STRUCT,(UserData, UserData.thrift_spec)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getServices_args(TBase):
+class addUser_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 getServices_result(TBase):
+class addUser_result(TBase):
"""
Attributes:
- success
@@ -5243,97 +6838,112 @@ class getServices_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
+ (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class hasService_args(TBase):
+class updateUserData_args(TBase):
"""
Attributes:
- - plugin
- - func
+ - data
"""
__slots__ = [
- 'plugin',
- 'func',
+ 'data',
]
thrift_spec = (
None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
+ (1, TType.STRUCT, 'data', (UserData, UserData.thrift_spec), None, ), # 1
)
- def __init__(self, plugin=None, func=None,):
- self.plugin = plugin
- self.func = func
+ def __init__(self, data=None,):
+ self.data = data
+
+class updateUserData_result(TBase):
-class hasService_result(TBase):
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class removeUser_args(TBase):
"""
Attributes:
- - success
+ - uid
"""
__slots__ = [
- 'success',
+ 'uid',
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
+ None, # 0
+ (1, TType.I32, 'uid', None, None, ), # 1
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, uid=None,):
+ self.uid = uid
+
+
+class removeUser_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
-class call_args(TBase):
+class setPassword_args(TBase):
"""
Attributes:
- - info
+ - username
+ - old_password
+ - new_password
"""
__slots__ = [
- 'info',
+ 'username',
+ 'old_password',
+ 'new_password',
]
thrift_spec = (
None, # 0
- (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None, ), # 1
+ (1, TType.STRING, 'username', None, None, ), # 1
+ (2, TType.STRING, 'old_password', None, None, ), # 2
+ (3, TType.STRING, 'new_password', None, None, ), # 3
)
- def __init__(self, info=None,):
- self.info = info
+ def __init__(self, username=None, old_password=None, new_password=None,):
+ self.username = username
+ self.old_password = old_password
+ self.new_password = new_password
-class call_result(TBase):
+class setPassword_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
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
- def __init__(self, success=None, ex=None, e=None,):
+ def __init__(self, success=None,):
self.success = success
- self.ex = ex
- self.e = e
class getAllInfo_args(TBase):
@@ -5356,7 +6966,7 @@ class getAllInfo_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
+ (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRUCT,(AddonInfo, AddonInfo.thrift_spec))), None, ), # 0
)
def __init__(self, success=None,):
@@ -5393,14 +7003,14 @@ class getInfoByPlugin_result(TBase):
]
thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRING,None), None, ), # 0
+ (0, TType.LIST, 'success', (TType.STRUCT,(AddonInfo, AddonInfo.thrift_spec)), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class isCaptchaWaiting_args(TBase):
+class getAddonHandler_args(TBase):
__slots__ = [
]
@@ -5409,7 +7019,7 @@ class isCaptchaWaiting_args(TBase):
)
-class isCaptchaWaiting_result(TBase):
+class getAddonHandler_result(TBase):
"""
Attributes:
- success
@@ -5420,33 +7030,37 @@ class isCaptchaWaiting_result(TBase):
]
thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
+ (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRUCT,(AddonService, AddonService.thrift_spec))), None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getCaptchaTask_args(TBase):
+class hasAddonHandler_args(TBase):
"""
Attributes:
- - exclusive
+ - plugin
+ - func
"""
__slots__ = [
- 'exclusive',
+ 'plugin',
+ 'func',
]
thrift_spec = (
None, # 0
- (1, TType.BOOL, 'exclusive', None, None, ), # 1
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.STRING, 'func', None, None, ), # 2
)
- def __init__(self, exclusive=None,):
- self.exclusive = exclusive
+ def __init__(self, plugin=None, func=None,):
+ self.plugin = plugin
+ self.func = func
-class getCaptchaTask_result(TBase):
+class hasAddonHandler_result(TBase):
"""
Attributes:
- success
@@ -5457,78 +7071,109 @@ class getCaptchaTask_result(TBase):
]
thrift_spec = (
- (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None, ), # 0
+ (0, TType.BOOL, 'success', None, None, ), # 0
)
def __init__(self, success=None,):
self.success = success
-class getCaptchaTaskStatus_args(TBase):
+class callAddon_args(TBase):
"""
Attributes:
- - tid
+ - plugin
+ - func
+ - arguments
"""
__slots__ = [
- 'tid',
+ 'plugin',
+ 'func',
+ 'arguments',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.STRING, 'func', None, None, ), # 2
+ (3, TType.LIST, 'arguments', (TType.STRING,None), None, ), # 3
)
- def __init__(self, tid=None,):
- self.tid = tid
+ def __init__(self, plugin=None, func=None, arguments=None,):
+ self.plugin = plugin
+ self.func = func
+ self.arguments = arguments
-class getCaptchaTaskStatus_result(TBase):
+class callAddon_result(TBase):
"""
Attributes:
- - success
+ - e
+ - ex
"""
__slots__ = [
- 'success',
+ 'e',
+ 'ex',
]
thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
+ None, # 0
+ (1, TType.STRUCT, 'e', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None, ), # 1
+ (2, TType.STRUCT, 'ex', (ServiceException, ServiceException.thrift_spec), None, ), # 2
)
- def __init__(self, success=None,):
- self.success = success
+ def __init__(self, e=None, ex=None,):
+ self.e = e
+ self.ex = ex
-class setCaptchaResult_args(TBase):
+class callAddonHandler_args(TBase):
"""
Attributes:
- - tid
- - result
+ - plugin
+ - func
+ - pid_or_fid
"""
__slots__ = [
- 'tid',
- 'result',
+ 'plugin',
+ 'func',
+ 'pid_or_fid',
]
thrift_spec = (
None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
- (2, TType.STRING, 'result', None, None, ), # 2
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.STRING, 'func', None, None, ), # 2
+ (3, TType.I32, 'pid_or_fid', None, None, ), # 3
)
- def __init__(self, tid=None, result=None,):
- self.tid = tid
- self.result = result
+ def __init__(self, plugin=None, func=None, pid_or_fid=None,):
+ self.plugin = plugin
+ self.func = func
+ self.pid_or_fid = pid_or_fid
-class setCaptchaResult_result(TBase):
+class callAddonHandler_result(TBase):
+ """
+ Attributes:
+ - e
+ - ex
+ """
__slots__ = [
+ 'e',
+ 'ex',
]
thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'e', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None, ), # 1
+ (2, TType.STRUCT, 'ex', (ServiceException, ServiceException.thrift_spec), None, ), # 2
)
+ def __init__(self, e=None, ex=None,):
+ self.e = e
+ self.ex = ex
+
diff --git a/module/remote/thriftbackend/thriftgen/pyload/constants.py b/module/remote/thriftbackend/thriftgen/pyload/constants.py
index f8960dc63..7842b3177 100644
--- a/module/remote/thriftbackend/thriftgen/pyload/constants.py
+++ b/module/remote/thriftbackend/thriftgen/pyload/constants.py
@@ -1,5 +1,5 @@
#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
+# Autogenerated by Thrift Compiler (0.8.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
diff --git a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
index 1299b515d..4b2f6497c 100644
--- a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
+++ b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -1,5 +1,5 @@
#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
+# Autogenerated by Thrift Compiler (0.8.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
@@ -12,219 +12,311 @@ from thrift.protocol.TBase import TBase, TExceptionBase
class DownloadStatus(TBase):
- Finished = 0
+ NA = 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
+ Paused = 4
+ Finished = 5
+ Skipped = 6
+ Failed = 7
+ Starting = 8
+ Waiting = 9
+ Downloading = 10
+ TempOffline = 11
+ Aborted = 12
+ Decrypting = 13
+ Processing = 14
+ Custom = 15
+ Unknown = 16
_VALUES_TO_NAMES = {
- 0: "Finished",
+ 0: "NA",
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",
+ 4: "Paused",
+ 5: "Finished",
+ 6: "Skipped",
+ 7: "Failed",
+ 8: "Starting",
+ 9: "Waiting",
+ 10: "Downloading",
+ 11: "TempOffline",
+ 12: "Aborted",
+ 13: "Decrypting",
+ 14: "Processing",
+ 15: "Custom",
+ 16: "Unknown",
}
_NAMES_TO_VALUES = {
- "Finished": 0,
+ "NA": 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,
+ "Paused": 4,
+ "Finished": 5,
+ "Skipped": 6,
+ "Failed": 7,
+ "Starting": 8,
+ "Waiting": 9,
+ "Downloading": 10,
+ "TempOffline": 11,
+ "Aborted": 12,
+ "Decrypting": 13,
+ "Processing": 14,
+ "Custom": 15,
+ "Unknown": 16,
}
-class Destination(TBase):
- Collector = 0
- Queue = 1
+class MediaType(TBase):
+ All = 0
+ Other = 1
+ Audio = 2
+ Image = 4
+ Video = 8
+ Document = 16
+ Archive = 32
_VALUES_TO_NAMES = {
- 0: "Collector",
- 1: "Queue",
+ 0: "All",
+ 1: "Other",
+ 2: "Audio",
+ 4: "Image",
+ 8: "Video",
+ 16: "Document",
+ 32: "Archive",
}
_NAMES_TO_VALUES = {
- "Collector": 0,
- "Queue": 1,
+ "All": 0,
+ "Other": 1,
+ "Audio": 2,
+ "Image": 4,
+ "Video": 8,
+ "Document": 16,
+ "Archive": 32,
}
-class ElementType(TBase):
- Package = 0
- File = 1
+class FileStatus(TBase):
+ Ok = 0
+ Missing = 1
+ Remote = 2
_VALUES_TO_NAMES = {
- 0: "Package",
- 1: "File",
+ 0: "Ok",
+ 1: "Missing",
+ 2: "Remote",
}
_NAMES_TO_VALUES = {
- "Package": 0,
- "File": 1,
+ "Ok": 0,
+ "Missing": 1,
+ "Remote": 2,
+ }
+
+class PackageStatus(TBase):
+ Ok = 0
+ Paused = 1
+ Folder = 2
+ Remote = 3
+
+ _VALUES_TO_NAMES = {
+ 0: "Ok",
+ 1: "Paused",
+ 2: "Folder",
+ 3: "Remote",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Ok": 0,
+ "Paused": 1,
+ "Folder": 2,
+ "Remote": 3,
}
class Input(TBase):
- NONE = 0
- TEXT = 1
- TEXTBOX = 2
- PASSWORD = 3
- BOOL = 4
- CLICK = 5
- CHOICE = 6
- MULTIPLE = 7
- LIST = 8
- TABLE = 9
+ NA = 0
+ Text = 1
+ Textbox = 2
+ Password = 3
+ Bool = 4
+ Click = 5
+ Select = 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",
+ 0: "NA",
+ 1: "Text",
+ 2: "Textbox",
+ 3: "Password",
+ 4: "Bool",
+ 5: "Click",
+ 6: "Select",
+ 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,
+ "NA": 0,
+ "Text": 1,
+ "Textbox": 2,
+ "Password": 3,
+ "Bool": 4,
+ "Click": 5,
+ "Select": 6,
+ "Multiple": 7,
+ "List": 8,
+ "Table": 9,
}
class Output(TBase):
- CAPTCHA = 1
- QUESTION = 2
- NOTIFICATION = 4
+ All = 0
+ Notification = 1
+ Captcha = 2
+ Query = 4
_VALUES_TO_NAMES = {
- 1: "CAPTCHA",
- 2: "QUESTION",
- 4: "NOTIFICATION",
+ 0: "All",
+ 1: "Notification",
+ 2: "Captcha",
+ 4: "Query",
}
_NAMES_TO_VALUES = {
- "CAPTCHA": 1,
- "QUESTION": 2,
- "NOTIFICATION": 4,
+ "All": 0,
+ "Notification": 1,
+ "Captcha": 2,
+ "Query": 4,
}
+class Permission(TBase):
+ All = 0
+ Add = 1
+ Delete = 2
+ Modify = 4
+ Download = 8
+ Accounts = 16
+ Interaction = 32
+ Plugins = 64
-class DownloadInfo(TBase):
+ _VALUES_TO_NAMES = {
+ 0: "All",
+ 1: "Add",
+ 2: "Delete",
+ 4: "Modify",
+ 8: "Download",
+ 16: "Accounts",
+ 32: "Interaction",
+ 64: "Plugins",
+ }
+
+ _NAMES_TO_VALUES = {
+ "All": 0,
+ "Add": 1,
+ "Delete": 2,
+ "Modify": 4,
+ "Download": 8,
+ "Accounts": 16,
+ "Interaction": 32,
+ "Plugins": 64,
+ }
+
+class Role(TBase):
+ Admin = 0
+ User = 1
+
+ _VALUES_TO_NAMES = {
+ 0: "Admin",
+ 1: "User",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Admin": 0,
+ "User": 1,
+ }
+
+
+class DownloadProgress(TBase):
"""
Attributes:
- fid
- - name
+ - pid
- speed
- - eta
- - format_eta
- - bleft
- - size
- - format_size
- - percent
- status
- - statusmsg
- - format_wait
- - wait_until
- - packageID
- - packageName
- - plugin
"""
__slots__ = [
'fid',
- 'name',
+ 'pid',
'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
+ (2, TType.I32, 'pid', None, None, ), # 2
(3, TType.I64, 'speed', None, None, ), # 3
+ (4, TType.I32, 'status', None, None, ), # 4
+ )
+
+ def __init__(self, fid=None, pid=None, speed=None, status=None,):
+ self.fid = fid
+ self.pid = pid
+ self.speed = speed
+ self.status = status
+
+
+class ProgressInfo(TBase):
+ """
+ Attributes:
+ - plugin
+ - name
+ - statusmsg
+ - eta
+ - format_eta
+ - done
+ - total
+ - download
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'name',
+ 'statusmsg',
+ 'eta',
+ 'format_eta',
+ 'done',
+ 'total',
+ 'download',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.STRING, 'name', None, None, ), # 2
+ (3, TType.STRING, 'statusmsg', 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
+ (6, TType.I64, 'done', None, None, ), # 6
+ (7, TType.I64, 'total', None, None, ), # 7
+ (8, TType.STRUCT, 'download', (DownloadProgress, DownloadProgress.thrift_spec), None, ), # 8
)
- 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
+ def __init__(self, plugin=None, name=None, statusmsg=None, eta=None, format_eta=None, done=None, total=None, download=None,):
+ self.plugin = plugin
self.name = name
- self.speed = speed
+ self.statusmsg = statusmsg
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
+ self.done = done
+ self.total = total
+ self.download = download
class ServerStatus(TBase):
@@ -270,159 +362,165 @@ class ServerStatus(TBase):
self.reconnect = reconnect
-class ConfigItem(TBase):
+class DownloadInfo(TBase):
"""
Attributes:
- - name
- - description
- - value
- - type
+ - url
+ - plugin
+ - hash
+ - status
+ - statusmsg
+ - error
"""
__slots__ = [
- 'name',
- 'description',
- 'value',
- 'type',
+ 'url',
+ 'plugin',
+ 'hash',
+ 'status',
+ 'statusmsg',
+ 'error',
]
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
+ (1, TType.STRING, 'url', None, None, ), # 1
+ (2, TType.STRING, 'plugin', None, None, ), # 2
+ (3, TType.STRING, 'hash', None, None, ), # 3
+ (4, TType.I32, 'status', None, None, ), # 4
+ (5, TType.STRING, 'statusmsg', None, None, ), # 5
+ (6, TType.STRING, 'error', None, None, ), # 6
)
- def __init__(self, name=None, description=None, value=None, type=None,):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
+ def __init__(self, url=None, plugin=None, hash=None, status=None, statusmsg=None, error=None,):
+ self.url = url
+ self.plugin = plugin
+ self.hash = hash
+ self.status = status
+ self.statusmsg = statusmsg
+ self.error = error
-class ConfigSection(TBase):
+class FileInfo(TBase):
"""
Attributes:
+ - fid
- name
- - description
- - items
- - outline
+ - package
+ - owner
+ - size
+ - status
+ - media
+ - added
+ - fileorder
+ - download
"""
__slots__ = [
+ 'fid',
'name',
- 'description',
- 'items',
- 'outline',
+ 'package',
+ 'owner',
+ 'size',
+ 'status',
+ 'media',
+ 'added',
+ 'fileorder',
+ 'download',
]
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
+ (1, TType.I32, 'fid', None, None, ), # 1
+ (2, TType.STRING, 'name', None, None, ), # 2
+ (3, TType.I32, 'package', None, None, ), # 3
+ (4, TType.I32, 'owner', None, None, ), # 4
+ (5, TType.I64, 'size', None, None, ), # 5
+ (6, TType.I32, 'status', None, None, ), # 6
+ (7, TType.I32, 'media', None, None, ), # 7
+ (8, TType.I64, 'added', None, None, ), # 8
+ (9, TType.I16, 'fileorder', None, None, ), # 9
+ (10, TType.STRUCT, 'download', (DownloadInfo, DownloadInfo.thrift_spec), None, ), # 10
)
- def __init__(self, name=None, description=None, items=None, outline=None,):
+ def __init__(self, fid=None, name=None, package=None, owner=None, size=None, status=None, media=None, added=None, fileorder=None, download=None,):
+ self.fid = fid
self.name = name
- self.description = description
- self.items = items
- self.outline = outline
+ self.package = package
+ self.owner = owner
+ self.size = size
+ self.status = status
+ self.media = media
+ self.added = added
+ self.fileorder = fileorder
+ self.download = download
-class FileData(TBase):
+class PackageStats(TBase):
"""
Attributes:
- - fid
- - url
- - name
- - plugin
- - size
- - format_size
- - status
- - statusmsg
- - packageID
- - error
- - order
+ - linkstotal
+ - linksdone
+ - sizetotal
+ - sizedone
"""
__slots__ = [
- 'fid',
- 'url',
- 'name',
- 'plugin',
- 'size',
- 'format_size',
- 'status',
- 'statusmsg',
- 'packageID',
- 'error',
- 'order',
+ 'linkstotal',
+ 'linksdone',
+ 'sizetotal',
+ 'sizedone',
]
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
+ (1, TType.I16, 'linkstotal', None, None, ), # 1
+ (2, TType.I16, 'linksdone', None, None, ), # 2
+ (3, TType.I64, 'sizetotal', None, None, ), # 3
+ (4, TType.I64, 'sizedone', None, None, ), # 4
)
- 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
+ def __init__(self, linkstotal=None, linksdone=None, sizetotal=None, sizedone=None,):
+ self.linkstotal = linkstotal
+ self.linksdone = linksdone
+ self.sizetotal = sizetotal
+ self.sizedone = sizedone
-class PackageData(TBase):
+class PackageInfo(TBase):
"""
Attributes:
- pid
- name
- folder
+ - root
+ - owner
- site
+ - comment
- password
- - dest
- - order
- - linksdone
- - sizedone
- - sizetotal
- - linkstotal
- - links
+ - added
+ - status
+ - packageorder
+ - stats
- fids
+ - pids
"""
__slots__ = [
'pid',
'name',
'folder',
+ 'root',
+ 'owner',
'site',
+ 'comment',
'password',
- 'dest',
- 'order',
- 'linksdone',
- 'sizedone',
- 'sizetotal',
- 'linkstotal',
- 'links',
+ 'added',
+ 'status',
+ 'packageorder',
+ 'stats',
'fids',
+ 'pids',
]
thrift_spec = (
@@ -430,32 +528,100 @@ class PackageData(TBase):
(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
+ (4, TType.I32, 'root', None, None, ), # 4
+ (5, TType.I32, 'owner', None, None, ), # 5
+ (6, TType.STRING, 'site', None, None, ), # 6
+ (7, TType.STRING, 'comment', None, None, ), # 7
+ (8, TType.STRING, 'password', None, None, ), # 8
+ (9, TType.I64, 'added', None, None, ), # 9
+ (10, TType.I32, 'status', None, None, ), # 10
+ (11, TType.I16, 'packageorder', None, None, ), # 11
+ (12, TType.STRUCT, 'stats', (PackageStats, PackageStats.thrift_spec), None, ), # 12
(13, TType.LIST, 'fids', (TType.I32,None), None, ), # 13
+ (14, TType.LIST, 'pids', (TType.I32,None), None, ), # 14
)
- 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,):
+ def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, status=None, packageorder=None, stats=None, fids=None, pids=None,):
self.pid = pid
self.name = name
self.folder = folder
+ self.root = root
+ self.owner = owner
self.site = site
+ self.comment = comment
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.added = added
+ self.status = status
+ self.packageorder = packageorder
+ self.stats = stats
self.fids = fids
+ self.pids = pids
+
+
+class TreeCollection(TBase):
+ """
+ Attributes:
+ - root
+ - files
+ - packages
+ """
+
+ __slots__ = [
+ 'root',
+ 'files',
+ 'packages',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'root', (PackageInfo, PackageInfo.thrift_spec), None, ), # 1
+ (2, TType.MAP, 'files', (TType.I32,None,TType.STRUCT,(FileInfo, FileInfo.thrift_spec)), None, ), # 2
+ (3, TType.MAP, 'packages', (TType.I32,None,TType.STRUCT,(PackageInfo, PackageInfo.thrift_spec)), None, ), # 3
+ )
+
+ def __init__(self, root=None, files=None, packages=None,):
+ self.root = root
+ self.files = files
+ self.packages = packages
+
+
+class LinkStatus(TBase):
+ """
+ Attributes:
+ - url
+ - name
+ - plugin
+ - size
+ - status
+ - packagename
+ """
+
+ __slots__ = [
+ 'url',
+ 'name',
+ 'plugin',
+ 'size',
+ 'status',
+ 'packagename',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'url', None, None, ), # 1
+ (2, TType.STRING, 'name', None, None, ), # 2
+ (3, TType.STRING, 'plugin', None, None, ), # 3
+ (4, TType.I64, 'size', None, None, ), # 4
+ (5, TType.I32, 'status', None, None, ), # 5
+ (6, TType.STRING, 'packagename', None, None, ), # 6
+ )
+
+ def __init__(self, url=None, name=None, plugin=None, size=None, status=None, packagename=None,):
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.status = status
+ self.packagename = packagename
class InteractionTask(TBase):
@@ -463,10 +629,9 @@ class InteractionTask(TBase):
Attributes:
- iid
- input
- - structure
- - preset
- - output
- data
+ - output
+ - default_value
- title
- description
- plugin
@@ -475,10 +640,9 @@ class InteractionTask(TBase):
__slots__ = [
'iid',
'input',
- 'structure',
- 'preset',
- 'output',
'data',
+ 'output',
+ 'default_value',
'title',
'description',
'plugin',
@@ -488,235 +652,343 @@ class InteractionTask(TBase):
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
+ (3, TType.LIST, 'data', (TType.STRING,None), None, ), # 3
+ (4, TType.I32, 'output', None, None, ), # 4
+ (5, TType.STRING, 'default_value', None, None, ), # 5
+ (6, TType.STRING, 'title', None, None, ), # 6
+ (7, TType.STRING, 'description', None, None, ), # 7
+ (8, TType.STRING, 'plugin', None, None, ), # 8
)
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,):
+ def __init__(self, iid=None, input=None, data=None, output=None, default_value=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.output = output
+ self.default_value = default_value
self.title = title
self.description = description
self.plugin = plugin
-class CaptchaTask(TBase):
+class AddonService(TBase):
"""
Attributes:
- - tid
- - data
+ - func_name
+ - description
+ - arguments
+ - media
+ """
+
+ __slots__ = [
+ 'func_name',
+ 'description',
+ 'arguments',
+ 'media',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'func_name', None, None, ), # 1
+ (2, TType.STRING, 'description', None, None, ), # 2
+ (3, TType.LIST, 'arguments', (TType.STRING,None), None, ), # 3
+ (4, TType.I16, 'media', None, None, ), # 4
+ )
+
+ def __init__(self, func_name=None, description=None, arguments=None, media=None,):
+ self.func_name = func_name
+ self.description = description
+ self.arguments = arguments
+ self.media = media
+
+
+class AddonInfo(TBase):
+ """
+ Attributes:
+ - func_name
+ - description
+ - value
+ """
+
+ __slots__ = [
+ 'func_name',
+ 'description',
+ 'value',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'func_name', None, None, ), # 1
+ (2, TType.STRING, 'description', None, None, ), # 2
+ (3, TType.STRING, 'value', None, None, ), # 3
+ )
+
+ def __init__(self, func_name=None, description=None, value=None,):
+ self.func_name = func_name
+ self.description = description
+ self.value = value
+
+
+class ConfigItem(TBase):
+ """
+ Attributes:
+ - name
+ - label
+ - description
- type
- - resultType
+ - default_value
+ - value
"""
__slots__ = [
- 'tid',
- 'data',
+ 'name',
+ 'label',
+ 'description',
'type',
- 'resultType',
+ 'default_value',
+ 'value',
]
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
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'label', None, None, ), # 2
+ (3, TType.STRING, 'description', None, None, ), # 3
+ (4, TType.STRING, 'type', None, None, ), # 4
+ (5, TType.STRING, 'default_value', None, None, ), # 5
+ (6, TType.STRING, 'value', None, None, ), # 6
)
- def __init__(self, tid=None, data=None, type=None, resultType=None,):
- self.tid = tid
- self.data = data
+ def __init__(self, name=None, label=None, description=None, type=None, default_value=None, value=None,):
+ self.name = name
+ self.label = label
+ self.description = description
self.type = type
- self.resultType = resultType
+ self.default_value = default_value
+ self.value = value
+
+
+class ConfigHolder(TBase):
+ """
+ Attributes:
+ - name
+ - label
+ - description
+ - long_description
+ - items
+ - info
+ - handler
+ """
+
+ __slots__ = [
+ 'name',
+ 'label',
+ 'description',
+ 'long_description',
+ 'items',
+ 'info',
+ 'handler',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'label', None, None, ), # 2
+ (3, TType.STRING, 'description', None, None, ), # 3
+ (4, TType.STRING, 'long_description', None, None, ), # 4
+ (5, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 5
+ (6, TType.LIST, 'info', (TType.STRUCT,(AddonInfo, AddonInfo.thrift_spec)), None, ), # 6
+ (7, TType.LIST, 'handler', (TType.STRUCT,(InteractionTask, InteractionTask.thrift_spec)), None, ), # 7
+ )
+
+ def __init__(self, name=None, label=None, description=None, long_description=None, items=None, info=None, handler=None,):
+ self.name = name
+ self.label = label
+ self.description = description
+ self.long_description = long_description
+ self.items = items
+ self.info = info
+ self.handler = handler
+
+
+class ConfigInfo(TBase):
+ """
+ Attributes:
+ - name
+ - label
+ - description
+ - saved
+ - activated
+ """
+
+ __slots__ = [
+ 'name',
+ 'label',
+ 'description',
+ 'saved',
+ 'activated',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'label', None, None, ), # 2
+ (3, TType.STRING, 'description', None, None, ), # 3
+ (4, TType.BOOL, 'saved', None, None, ), # 4
+ (5, TType.BOOL, 'activated', None, None, ), # 5
+ )
+
+ def __init__(self, name=None, label=None, description=None, saved=None, activated=None,):
+ self.name = name
+ self.label = label
+ self.description = description
+ self.saved = saved
+ self.activated = activated
class EventInfo(TBase):
"""
Attributes:
- eventname
- - id
- - type
- - destination
+ - event_args
"""
__slots__ = [
'eventname',
- 'id',
- 'type',
- 'destination',
+ 'event_args',
]
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
+ (2, TType.LIST, 'event_args', (TType.STRING,None), None, ), # 2
)
- def __init__(self, eventname=None, id=None, type=None, destination=None,):
+ def __init__(self, eventname=None, event_args=None,):
self.eventname = eventname
- self.id = id
- self.type = type
- self.destination = destination
+ self.event_args = event_args
class UserData(TBase):
"""
Attributes:
+ - uid
- name
- email
- role
- permission
+ - folder
+ - traffic
+ - dllimit
+ - dlquota
+ - hddquota
+ - user
- templateName
"""
__slots__ = [
+ 'uid',
'name',
'email',
'role',
'permission',
+ 'folder',
+ 'traffic',
+ 'dllimit',
+ 'dlquota',
+ 'hddquota',
+ 'user',
'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
+ (1, TType.I32, 'uid', None, None, ), # 1
+ (2, TType.STRING, 'name', None, None, ), # 2
+ (3, TType.STRING, 'email', None, None, ), # 3
+ (4, TType.I16, 'role', None, None, ), # 4
+ (5, TType.I16, 'permission', None, None, ), # 5
+ (6, TType.STRING, 'folder', None, None, ), # 6
+ (7, TType.I64, 'traffic', None, None, ), # 7
+ (8, TType.I16, 'dllimit', None, None, ), # 8
+ (9, TType.STRING, 'dlquota', None, None, ), # 9
+ (10, TType.I64, 'hddquota', None, None, ), # 10
+ (11, TType.I32, 'user', None, None, ), # 11
+ (12, TType.STRING, 'templateName', None, None, ), # 12
)
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,):
+ def __init__(self, uid=None, name=None, email=None, role=None, permission=None, folder=None, traffic=None, dllimit=None, dlquota=None, hddquota=None, user=None, templateName=None,):
+ self.uid = uid
self.name = name
self.email = email
self.role = role
self.permission = permission
+ self.folder = folder
+ self.traffic = traffic
+ self.dllimit = dllimit
+ self.dlquota = dlquota
+ self.hddquota = hddquota
+ self.user = user
self.templateName = templateName
class AccountInfo(TBase):
"""
Attributes:
- - validuntil
- - login
- - options
+ - plugin
+ - loginname
+ - owner
- valid
+ - validuntil
- trafficleft
- maxtraffic
- premium
- - type
+ - activated
+ - shared
+ - options
"""
__slots__ = [
- 'validuntil',
- 'login',
- 'options',
+ 'plugin',
+ 'loginname',
+ 'owner',
'valid',
+ 'validuntil',
'trafficleft',
'maxtraffic',
'premium',
- 'type',
+ 'activated',
+ 'shared',
+ 'options',
]
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
+ (1, TType.STRING, 'plugin', None, None, ), # 1
+ (2, TType.STRING, 'loginname', None, None, ), # 2
+ (3, TType.I32, 'owner', 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
+ (5, TType.I64, 'validuntil', None, None, ), # 5
+ (6, TType.I64, 'trafficleft', None, None, ), # 6
+ (7, TType.I64, 'maxtraffic', None, None, ), # 7
+ (8, TType.BOOL, 'premium', None, None, ), # 8
+ (9, TType.BOOL, 'activated', None, None, ), # 9
+ (10, TType.BOOL, 'shared', None, None, ), # 10
+ (11, TType.MAP, 'options', (TType.STRING,None,TType.STRING,None), None, ), # 11
)
- 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
+ def __init__(self, plugin=None, loginname=None, owner=None, valid=None, validuntil=None, trafficleft=None, maxtraffic=None, premium=None, activated=None, shared=None, options=None,):
+ self.plugin = plugin
+ self.loginname = loginname
+ self.owner = owner
self.valid = valid
+ self.validuntil = validuntil
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
+ self.activated = activated
+ self.shared = shared
+ self.options = options
class OnlineCheck(TBase):
@@ -734,7 +1006,7 @@ class OnlineCheck(TBase):
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
+ (2, TType.MAP, 'data', (TType.STRING,None,TType.STRUCT,(LinkStatus, LinkStatus.thrift_spec)), None, ), # 2
)
def __init__(self, rid=None, data=None,):
@@ -786,6 +1058,28 @@ class FileDoesNotExists(TExceptionBase):
return repr(self)
+class UserDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - user
+ """
+
+ __slots__ = [
+ 'user',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'user', None, None, ), # 1
+ )
+
+ def __init__(self, user=None,):
+ self.user = user
+
+ def __str__(self):
+ return repr(self)
+
+
class ServiceDoesNotExists(TExceptionBase):
"""
Attributes:
diff --git a/module/setup.py b/module/setup.py
index 42b24859f..88188d1f7 100644
--- a/module/setup.py
+++ b/module/setup.py
@@ -41,15 +41,15 @@ class Setup():
self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
def start(self):
- langs = self.config.getMetaData("general", "language")["type"].split(";")
+ 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
+ #l10n Input shorthand for yes
self.yes = _("y")
- #Input shorthand for no
+ #l10n Input shorthand for no
self.no = _("n")
# print ""
@@ -82,7 +82,7 @@ class Setup():
print _("When you are ready for system check, hit enter.")
raw_input()
- basic, ssl, captcha, gui, web, js = self.system_check()
+ basic, ssl, captcha, web, js = self.system_check()
print ""
if not basic:
@@ -101,7 +101,6 @@ class Setup():
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"))
@@ -114,7 +113,7 @@ class Setup():
print ""
if len(avail) < 5:
- print _("Featues missing: ")
+ print _("Features missing: ")
print
if not self.check_module("Crypto"):
@@ -125,7 +124,7 @@ class Setup():
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 you only want to access locally to pyLoad ssl is not useful.")
print ""
if not captcha:
@@ -133,16 +132,11 @@ class Setup():
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.")
+ print _("You can abort the setup now and fix some dependencies if you want.")
con = self.ask(_("Continue with setup?"), self.yes, bool=True)
@@ -152,7 +146,7 @@ class Setup():
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.")
+ "If you use pyLoad on a server or the home partition lives on an internal 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()
@@ -232,10 +226,6 @@ class Setup():
print ""
- gui = self.check_module("PyQt4")
- self.print_dep("PyQt4", gui)
-
- print ""
jinja = True
try:
@@ -246,7 +236,7 @@ class Setup():
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 _("please upgrade or deinstall it, pyLoad includes a sufficient jinja2 library.")
print
jinja = False
except:
@@ -263,7 +253,7 @@ class Setup():
js = True if JsEngine.ENGINE else False
self.print_dep(_("JS engine"), js)
- return basic, ssl, captcha, gui, web, js
+ return basic, ssl, captcha, web, js
def conf_basic(self):
print ""
@@ -288,7 +278,7 @@ class Setup():
print ""
langs = self.config.getMetaData("general", "language")
- self.config["general"]["language"] = self.ask(_("Language"), "en", langs["type"].split(";"))
+ 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")
@@ -317,7 +307,7 @@ class Setup():
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 "lightweight:", _("Very fast alternative written in C, requires libev and linux knowledge.")
print "\t", _("Get it from here: https://github.com/jonashaag/bjoern, compile it")
print "\t", _("and copy bjoern.so to module/lib")
@@ -375,10 +365,10 @@ class Setup():
print ""
print _("Users")
print "-----"
- users = db.listUsers()
+ users = db.getAllUserData()
noaction = False
- for user in users:
- print user
+ for user in users.itervalues():
+ print user.name
print "-----"
print ""
elif action == "3":
@@ -388,6 +378,7 @@ class Setup():
db.removeUser(username)
noaction = False
elif action == "4":
+ db.syncSave()
break
finally:
if not noaction:
@@ -400,7 +391,7 @@ class Setup():
languages=[self.config["general"]["language"], "en"], fallback=True)
translation.install(True)
- print _("Setting new configpath, current configuration will not be transfered!")
+ print _("Setting new configpath, current configuration will not be transferred!")
path = self.ask(_("Configpath"), abspath(""))
try:
path = join(pypath, path)
@@ -489,10 +480,10 @@ class Setup():
input = default
if bool:
- # yes, true,t are inputs for booleans with value true
+ #l10n 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
+ #l10n no, false,f are inputs for booleans with value false
elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]:
return False
else:
diff --git a/module/threads/AddonThread.py b/module/threads/AddonThread.py
new file mode 100644
index 000000000..b6a552e0e
--- /dev/null
+++ b/module/threads/AddonThread.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from traceback import print_exc
+
+from BaseThread import BaseThread
+
+class AddonThread(BaseThread):
+ """thread for addons"""
+
+ def __init__(self, m, function, args, kwargs):
+ """Constructor"""
+ BaseThread.__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): #TODO: approach via func_code
+ 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)
+ except Exception, e:
+ if hasattr(self.f, "im_self"):
+ addon = self.f.im_self
+ addon.logError(_("An Error occured"), e)
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(addon.__name__, plugin=addon)
+
+ finally:
+ local = copy(self.active)
+ for x in local:
+ self.finishFile(x)
+
+ self.m.localThreads.remove(self) \ No newline at end of file
diff --git a/module/threads/BaseThread.py b/module/threads/BaseThread.py
new file mode 100644
index 000000000..7a0ee5ee4
--- /dev/null
+++ b/module/threads/BaseThread.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import locale
+
+from threading import Thread
+from time import strftime, gmtime
+from sys import exc_info
+from types import MethodType
+from pprint import pformat
+from traceback import format_exc
+
+from module.utils.fs import listdir, join, save_join, stat, exists
+
+class BaseThread(Thread):
+ """abstract base class for thread types"""
+
+ def __init__(self, manager):
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.m = manager #thread manager
+ self.core = manager.core
+ self.log = manager.core.log
+
+ def writeDebugReport(self, name, pyfile=None, plugin=None):
+ """ writes a debug report to disk """
+
+ dump_name = "debug_%s_%s.zip" % (name, strftime("%d-%m-%Y_%H-%M-%S"))
+ if pyfile:
+ dump = self.getFileDump(pyfile)
+ else:
+ dump = self.getPluginDump(plugin)
+
+ try:
+ import zipfile
+
+ zip = zipfile.ZipFile(dump_name, "w")
+
+ if exists(join("tmp", name)):
+ for f in listdir(join("tmp", name)):
+ try:
+ # avoid encoding errors
+ zip.write(join("tmp", name, f), save_join(name, f))
+ except:
+ pass
+
+ info = zipfile.ZipInfo(save_join(name, "debug_Report.txt"), gmtime())
+ info.external_attr = 0644 << 16L # change permissions
+ zip.writestr(info, dump)
+
+ info = zipfile.ZipInfo(save_join(name, "system_Report.txt"), gmtime())
+ info.external_attr = 0644 << 16L
+ zip.writestr(info, self.getSystemDump())
+
+ zip.close()
+
+ if not stat(dump_name).st_size:
+ raise Exception("Empty Zipfile")
+
+ except Exception, e:
+ self.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.log.info("Debug Report written to %s" % dump_name)
+ return dump_name
+
+ def getFileDump(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"
+
+ dump += "\n\nCONFIG: \n\n"
+ dump += pformat(self.m.core.config.values) + "\n"
+
+ return dump
+
+ #TODO
+ def getPluginDump(self, plugin):
+ return ""
+
+ def getSystemDump(self):
+ return ""
+
+ def clean(self, pyfile):
+ """ set thread inactive and release pyfile """
+ self.active = False
+ pyfile.release()
diff --git a/module/threads/DecrypterThread.py b/module/threads/DecrypterThread.py
new file mode 100644
index 000000000..39448a620
--- /dev/null
+++ b/module/threads/DecrypterThread.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from time import sleep
+from traceback import print_exc
+
+from module.utils import uniqify
+from module.plugins.Base import Retry
+from module.plugins.Crypter import Package
+
+from BaseThread import BaseThread
+
+class DecrypterThread(BaseThread):
+ """thread for decrypting"""
+
+ def __init__(self, manager, data, pid):
+ """constructor"""
+ BaseThread.__init__(self, manager)
+ self.data = data
+ self.pid = pid
+
+ self.start()
+
+ def run(self):
+ plugin_map = {}
+ for url, plugin in self.data:
+ if plugin in plugin_map:
+ plugin_map[plugin].append(url)
+ else:
+ plugin_map[plugin] = [url]
+
+ self.decrypt(plugin_map)
+
+ def decrypt(self, plugin_map):
+ pack = self.m.core.files.getPackage(self.pid)
+ result = []
+
+ for name, urls in plugin_map.iteritems():
+ klass = self.m.core.pluginManager.loadClass("crypter", name)
+ plugin = klass(self.m.core, pack, pack.password)
+ plugin_result = []
+
+ try:
+ try:
+ plugin_result = plugin._decrypt(urls)
+ except Retry:
+ sleep(1)
+ plugin_result = plugin._decrypt(urls)
+ except Exception, e:
+ plugin.logError(_("Decrypting failed"), e)
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(plugin.__name__, plugin=plugin)
+
+ plugin.logDebug("Decrypted", plugin_result)
+ result.extend(plugin_result)
+
+ #TODO
+ result = uniqify(result)
+ pack_names = {}
+ urls = []
+
+ for p in result:
+ if isinstance(p, Package):
+ if p.name in pack_names:
+ pack_names[p.name].urls.extend(p.urls)
+ else:
+ pack_names[p.name] = p
+ else:
+ urls.append(p)
+
+ if urls:
+ self.log.info(_("Decrypted %(count)d links into package %(name)s") % {"count": len(urls), "name": pack.name})
+ self.m.core.api.addFiles(self.pid, urls)
+
+ for p in pack_names.itervalues():
+ self.m.core.api.addPackage(p.name, p.urls, pack.password)
+
+ if not result:
+ self.log.info(_("No links decrypted"))
+
diff --git a/module/threads/DownloadThread.py b/module/threads/DownloadThread.py
new file mode 100644
index 000000000..0269b0660
--- /dev/null
+++ b/module/threads/DownloadThread.py
@@ -0,0 +1,223 @@
+#!/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 time import sleep, time
+from traceback import print_exc
+from sys import exc_clear
+from pycurl import error
+
+from module.plugins.Base import Fail, Retry, Abort
+from module.plugins.Hoster import Reconnect, SkipDownload
+from module.network.HTTPRequest import BadHeader
+
+from BaseThread import BaseThread
+
+class DownloadThread(BaseThread):
+ """thread for downloading files from 'real' hoster plugins"""
+
+ def __init__(self, manager):
+ """Constructor"""
+ BaseThread.__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 queuing
+
+ pyfile.plugin.checkForSameFiles(starting=True)
+ self.log.info(_("Download starts: %s" % pyfile.name))
+
+ # start download
+ self.core.addonManager.downloadPreparing(pyfile)
+ pyfile.plugin.preprocessing(self)
+
+ self.log.info(_("Download finished: %s") % pyfile.name)
+ self.core.addonManager.downloadFinished(pyfile)
+ self.core.files.checkPackageFinished(pyfile)
+
+ except NotImplementedError:
+ self.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.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.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]
+
+ # TODO: activate former skipped downloads
+
+ if msg == "offline":
+ pyfile.setStatus("offline")
+ self.log.warning(_("Download is offline: %s") % pyfile.name)
+ elif msg == "temp. offline":
+ pyfile.setStatus("temp. offline")
+ self.log.warning(_("Download is temporary offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ self.core.addonManager.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.log.debug("pycurl exception %s: %s" % (code, msg))
+
+ if code in (7, 18, 28, 52, 56):
+ self.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.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ self.clean(pyfile)
+ else:
+ self.queue.put(pyfile)
+
+ continue
+
+ else:
+ pyfile.setStatus("failed")
+ self.log.error("pycurl error %s: %s" % (code, msg))
+ if self.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile.plugin.__name__, pyfile)
+
+ self.core.addonManager.downloadFailed(pyfile)
+
+ self.clean(pyfile)
+ continue
+
+ except SkipDownload, e:
+ pyfile.setStatus("skipped")
+
+ self.log.info(_("Download skipped: %(name)s due to %(plugin)s")
+ % {"name": pyfile.name, "plugin": e.message})
+
+ self.clean(pyfile)
+
+ self.core.files.checkPackageFinished(pyfile)
+
+ self.active = False
+ self.core.files.save()
+
+ continue
+
+
+ except Exception, e:
+ if isinstance(e, BadHeader) and e.code == 500:
+ pyfile.setStatus("temp. offline")
+ self.log.warning(_("Download is temporary offline: %s") % pyfile.name)
+ pyfile.error = _("Internal Server Error")
+
+ else:
+ pyfile.setStatus("failed")
+ self.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile.plugin.__name__, pyfile)
+
+ self.core.addonManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ finally:
+ self.core.files.save()
+ pyfile.checkIfProcessed()
+ exc_clear()
+
+
+ #pyfile.plugin.req.clean()
+
+ self.active = False
+ pyfile.finishIfDone()
+ self.core.files.save()
+
+
+ def put(self, job):
+ """assign a job to the thread"""
+ self.queue.put(job)
+
+
+ def stop(self):
+ """stops the thread"""
+ self.put("quit")
diff --git a/module/threads/InfoThread.py b/module/threads/InfoThread.py
new file mode 100644
index 000000000..a8a2c6e7e
--- /dev/null
+++ b/module/threads/InfoThread.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from time import time
+from traceback import print_exc
+
+from module.Api import LinkStatus
+from module.common.packagetools import parseNames
+from module.utils import has_method, accumulate
+
+from BaseThread import BaseThread
+
+class InfoThread(BaseThread):
+ def __init__(self, manager, data, pid=-1, rid=-1):
+ """Constructor"""
+ BaseThread.__init__(self, manager)
+
+ self.data = data
+ self.pid = pid # package id
+ # [ .. (name, plugin) .. ]
+
+ self.rid = rid #result id
+
+ self.cache = [] #accumulated data
+
+ self.start()
+
+ def run(self):
+ """run method"""
+
+ plugins = accumulate(self.data)
+ crypter = {}
+
+ # filter out crypter plugins
+ for name in self.m.core.pluginManager.getPlugins("crypter"):
+ if name in plugins:
+ crypter[name] = plugins[name]
+ del plugins[name]
+
+ #directly write to database
+ if self.pid > -1:
+ for pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPluginModule(pluginname)
+ klass = self.m.core.pluginManager.getPluginClass(pluginname)
+ if has_method(klass, "getInfo"):
+ self.fetchForPlugin(pluginname, klass, urls, self.updateDB)
+ self.m.core.files.save()
+ elif has_method(plugin, "getInfo"):
+ self.log.debug("Deprecated .getInfo() method on module level, use classmethod instead")
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
+ self.m.core.files.save()
+
+ else: #post the results
+ for name, urls in crypter:
+ #attach container content
+ try:
+ data = self.decrypt(name, urls)
+ except:
+ print_exc()
+ self.m.log.error("Could not decrypt container.")
+ data = []
+
+ accumulate(data, plugins)
+
+ self.m.infoResults[self.rid] = {}
+
+ for pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
+ klass = getattr(plugin, pluginname)
+ if has_method(klass, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True)
+ #force to process cache
+ if self.cache:
+ self.updateResult(pluginname, [], True)
+ elif has_method(plugin, "getInfo"):
+ self.log.debug("Deprecated .getInfo() method on module level, use staticmethod instead")
+ 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, LinkStatus(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 decrypt(self, plugin, urls):
+ self.m.log.debug("Pre decrypting %s" % plugin)
+ klass = self.m.core.pluginManager.loadClass("crypter", plugin)
+
+ # only decrypt files
+ if has_method(klass, "decryptFile"):
+ urls = klass.decrypt(urls)
+ data, crypter = self.m.core.pluginManager.parseUrls(urls)
+ return data
+
+ return []
diff --git a/module/ThreadManager.py b/module/threads/ThreadManager.py
index 8937f4a29..e3407aac3 100644
--- a/module/ThreadManager.py
+++ b/module/threads/ThreadManager.py
@@ -1,22 +1,20 @@
#!/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
-"""
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
from os.path import exists, join
import re
@@ -28,11 +26,14 @@ from random import choice
import pycurl
-import PluginThread
-from module.PyFile import PyFile
+from module.datatypes import PyFile
from module.network.RequestFactory import getURL
-from module.utils import freeSpace, lock
+from module.utils import lock, uniqify
+from module.utils.fs import free_space
+from DecrypterThread import DecrypterThread
+from DownloadThread import DownloadThread
+from InfoThread import InfoThread
class ThreadManager:
"""manages the download threads, assign jobs, reconnect etc"""
@@ -44,7 +45,7 @@ class ThreadManager:
self.log = core.log
self.threads = [] # thread list
- self.localThreads = [] #hook+decrypter threads
+ self.localThreads = [] #addon+decrypter threads
self.pause = True
@@ -63,42 +64,43 @@ class ThreadManager:
# threads which are fetching hoster results
self.infoResults = {}
- #timeout for cache purge
+ # 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")):
+ for i in range(self.core.config.get("download", "max_downloads")):
self.createThread()
def createThread(self):
"""create a download thread"""
- thread = PluginThread.DownloadThread(self)
+ thread = DownloadThread(self)
self.threads.append(thread)
def createInfoThread(self, data, pid):
- """
- start a thread whichs fetches online status and other infos
- data = [ .. () .. ]
- """
+ """ start a thread which fetches online status and other info's """
self.timestamp = time() + 5 * 60
-
- PluginThread.InfoThread(self, data, pid)
+ if data: InfoThread(self, data, pid)
@lock
- def createResultThread(self, data, add=False):
+ def createResultThread(self, data):
""" 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)
+ InfoThread(self, data, rid=rid)
return rid
+ @lock
+ def createDecryptThread(self, data, pid):
+ """ Start decrypting of entered data, all links in one package are accumulated to one thread."""
+ if data: DecrypterThread(self, data, pid)
+
@lock
def getInfoResult(self, rid):
@@ -130,7 +132,7 @@ class ThreadManager:
def work(self):
- """run all task which have to be done (this is for repetivive call by core)"""
+ """run all task which have to be done (this is for repetetive call by core)"""
try:
self.tryReconnect()
except Exception, e:
@@ -156,7 +158,6 @@ class ThreadManager:
self.infoResults.clear()
self.log.debug("Cleared Result cache")
- #----------------------------------------------------------------------
def tryReconnect(self):
"""checks if reconnect needed"""
@@ -186,7 +187,7 @@ class ThreadManager:
ip = self.getIP()
- self.core.hookManager.beforeReconnecting(ip)
+ self.core.addonManager.beforeReconnecting(ip)
self.log.debug("Old IP: %s" % ip)
@@ -203,7 +204,7 @@ class ThreadManager:
reconn.wait()
sleep(1)
ip = self.getIP()
- self.core.hookManager.afterReconnecting(ip)
+ self.core.addonManager.afterReconnecting(ip)
self.log.info(_("Reconnected, new IP: %s") % ip)
@@ -227,9 +228,8 @@ class ThreadManager:
return ip
- #----------------------------------------------------------------------
def checkThreadCount(self):
- """checks if there are need for increasing or reducing thread count"""
+ """checks if there is a need for increasing or reducing thread count"""
if len(self.threads) == self.core.config.get("download", "max_downloads"):
return True
@@ -242,7 +242,7 @@ class ThreadManager:
def cleanPycurl(self):
- """ make a global curl cleanup (currently ununused) """
+ """ make a global curl cleanup (currently unused) """
if self.processingIds():
return False
pycurl.global_cleanup()
@@ -251,9 +251,9 @@ class ThreadManager:
self.log.debug("Cleaned up pycurl")
return True
- #----------------------------------------------------------------------
+
def assignJob(self):
- """assing a job to a thread if possible"""
+ """assign a job to a thread if possible"""
if self.pause or not self.core.api.isTimeDownload(): return
@@ -262,14 +262,10 @@ class ThreadManager:
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
+ inuse = [(x.active.pluginname, x.active.plugin.getDownloadLimit()) for x in self.threads if x.active and x.active.hasPlugin()]
+ inuse = [(x[0], x[1], len([y for y in self.threads if y.active and y.active.pluginname == x[0]])) for x in inuse]
+ occ = tuple(sorted(uniqify([x[0] for x in inuse if 0 < x[1] <= x[2]])))
- occ.sort()
- occ = tuple(set(occ))
job = self.core.files.getJob(occ)
if job:
try:
@@ -282,36 +278,20 @@ class ThreadManager:
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)
-
+ spaceLeft = free_space(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:
- 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)
+ #put job back
+ if occ not in self.core.files.jobCache:
+ self.core.files.jobCache[occ] = []
+ self.core.files.jobCache[occ].append(job.id)
def cleanup(self):
"""do global cleanup, should be called when finished with pycurl"""
diff --git a/module/threads/__init__.py b/module/threads/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/module/threads/__init__.py
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/__init__.py b/module/utils/__init__.py
new file mode 100644
index 000000000..4ecc53a12
--- /dev/null
+++ b/module/utils/__init__.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+
+""" Store all usefull functions here """
+
+import os
+import time
+import re
+from string import maketrans
+from itertools import islice
+from htmlentitydefs import name2codepoint
+
+def decode(string):
+ """ decode string with utf if possible """
+ try:
+ if type(string) == str:
+ return string.decode("utf8", "replace")
+ else:
+ return string
+ except:
+ return string
+
+def encode(string):
+ """ decode string to utf if possible """
+ try:
+ if type(string) == unicode:
+ return string.encode("utf8", "replace")
+ else:
+ return string
+ 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 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 to_list(value):
+ return value if type(value) == list else ([value] if value is not None else [])
+
+def formatSize(size):
+ print "Deprecated formatSize, use format_size"
+ return format_size(size)
+
+def format_size(bytes):
+ bytes = int(bytes)
+ steps = 0
+ sizes = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB")
+ while bytes > 1000:
+ bytes /= 1024.0
+ steps += 1
+ return "%.2f %s" % (bytes, sizes[steps])
+
+def formatSpeed(speed):
+ print "Deprecated formatSpeed, use format_speed"
+ return format_speed(speed)
+
+def format_speed(speed):
+ return format_size(speed) + "/s"
+
+def format_time(seconds):
+ 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 uniqify(seq): #by Dave Kirby
+ """ removes duplicates from list, preserve order """
+ seen = set()
+ return [x for x in seq if x not in seen and not seen.add(x)]
+
+def bits_set(bits, compare):
+ """ checks if all bits are set in compare, or bits is 0 """
+ return bits == (bits & compare)
+
+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 chunks(iterable, size):
+ it = iter(iterable)
+ item = list(islice(it, size))
+ while item:
+ yield item
+ item = list(islice(it, size))
+
+
+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 has_method(obj, name):
+ """ checks if 'name' was defined in obj, (false if it was inhereted) """
+ return name in obj.__dict__
+
+def accumulate(it, inv_map=None):
+ """ accumulate (key, value) data to {value : [keylist]} dictionary """
+ if not inv_map:
+ inv_map = {}
+
+ for key, value in it:
+ if value in inv_map:
+ inv_map[value].append(key)
+ else:
+ inv_map[value] = [key]
+
+ return inv_map
+
+def to_string(value):
+ return str(value) if not isinstance(value, basestring) else value
+
+def to_int(string, default=0):
+ """ return int from string or default """
+ try:
+ return int(string)
+ except ValueError:
+ return default
+
+def to_dict(obj):
+ ret = {"class" : obj.__class__.__name__}
+ for att in obj.__slots__:
+ ret[att] = getattr(obj, att)
+ return ret
+
+def from_string(value, typ=None):
+ """ cast value to given type, unicode for strings """
+
+ # value is no string
+ if not isinstance(value, basestring):
+ return value
+
+ value = decode(value)
+
+ if 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
+ else:
+ return value
+
+def get_index(l, value):
+ """ .index method that also works on tuple and python 2.5 """
+ for pos, t in enumerate(l):
+ if t == value:
+ return pos
+
+ # Matches behavior of list.index
+ raise ValueError("list.index(x): x not in list")
+
+
+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 remove_chars("ab'cdgdsf''ds'", "'ghd")
diff --git a/module/utils/fs.py b/module/utils/fs.py
new file mode 100644
index 000000000..1894bc49a
--- /dev/null
+++ b/module/utils/fs.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+from os.path import join
+from . import decode, remove_chars
+
+# File System Encoding functions:
+# Use fs_encode before accessing files on disk, it will encode the string properly
+
+if sys.getfilesystemencoding().startswith('ANSI'):
+ def fs_encode(string):
+ if type(string) == unicode:
+ return string.encode('utf8')
+ return string
+
+ fs_decode = decode #decode utf8
+
+else:
+ fs_encode = fs_decode = lambda x: x # do nothing
+
+# FS utilities
+def chmod(path, mode):
+ try:
+ return os.chmod(fs_encode(path), mode)
+ except :
+ pass
+
+def chown(path, uid, gid):
+ return os.chown(fs_encode(path), uid, gid)
+
+def remove(path):
+ return os.remove(fs_encode(path))
+
+def exists(path):
+ return os.path.exists(fs_encode(path))
+
+def makedirs(path, mode=0755):
+ return os.makedirs(fs_encode(path), mode)
+
+# fs_decode?
+def listdir(path):
+ return os.listdir(fs_encode(path))
+
+def save_filename(name):
+ #remove some chars
+ if os.name == 'nt':
+ return remove_chars(name, '/\\?%*:|"<>,')
+ else:
+ return remove_chars(name, '/\\"')
+
+def stat(name):
+ return os.stat(fs_encode(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]))
+
+def free_space(folder):
+ folder = fs_encode(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_frsize * s.f_bavail
diff --git a/module/web/ServerThread.py b/module/web/ServerThread.py
index 84667e5f6..bf5ba8373 100644
--- a/module/web/ServerThread.py
+++ b/module/web/ServerThread.py
@@ -35,13 +35,6 @@ class WebServer(threading.Thread):
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
@@ -59,12 +52,10 @@ class WebServer(threading.Thread):
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"
+ # threaded is the new default server
+ if self.server == "builtin":
self.server = "threaded"
-
if self.server == "fastcgi":
self.start_fcgi()
elif self.server == "threaded":
@@ -72,9 +63,9 @@ class WebServer(threading.Thread):
elif self.server == "lightweight":
self.start_lightweight()
else:
- self.start_builtin()
+ self.start_fallback()
- def start_builtin(self):
+ def start_fallback(self):
if self.https:
log.warning(_("This server offers no SSL, please consider using threaded instead"))
@@ -94,6 +85,12 @@ class WebServer(threading.Thread):
def start_fcgi(self):
+ from flup.server.threadedserver import ThreadedServer
+ def noop(*args, **kwargs):
+ pass
+
+ ThreadedServer._installSignalHandlers = noop
+
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)
diff --git a/module/web/api_app.py b/module/web/api_app.py
index 1629c1677..b2d7fa5b6 100644
--- a/module/web/api_app.py
+++ b/module/web/api_app.py
@@ -7,10 +7,11 @@ from traceback import format_exc, print_exc
from bottle import route, request, response, HTTPError
-from utils import toDict, set_session
+from utils import set_session, get_user_api
from webinterface import PYLOAD
from module.common.json_layer import json
+from module.utils import remove_chars, to_dict
from module.lib.SafeEval import const_eval as literal_eval
from module.Api import BaseObject
@@ -19,26 +20,32 @@ class TBaseEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, BaseObject):
- return toDict(o)
+ return to_dict(o)
return json.JSONEncoder.default(self, o)
-# accepting positional arguments, as well as kwargs via post and get
+def add_header(r):
+ r.headers.replace("Content-type", "application/json")
+ r.headers.append("Cache-Control", "no-cache, must-revalidate")
+ r.headers.append("Access-Control-Allow-Origin", "*") # allow xhr requests
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#")
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#", method="POST")
+# accepting positional arguments, as well as kwargs via post and get
+# only forbidden path symbol are "?", which is used to separate GET data and #
+@route("/api/<func><args:re:[^#?]*>")
+@route("/api/<func><args:re:[^#?]*>", method="POST")
def call_api(func, args=""):
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
+ add_header(response)
s = request.environ.get('beaker.session')
if 'session' in request.POST:
- s = s.get_by_id(request.POST['session'])
+ # removes "' so it works on json strings
+ s = s.get_by_id(remove_chars(request.POST['session'], "'\""))
- if not s or not s.get("authenticated", False):
+ api = get_user_api(s)
+ if not api:
return HTTPError(403, json.dumps("Forbidden"))
- if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}):
+ if not PYLOAD.isAuthorized(func, api.user):
return HTTPError(401, json.dumps("Unauthorized"))
args = args.split("/")[1:]
@@ -54,16 +61,18 @@ def call_api(func, args=""):
print_exc()
return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()}))
+# Better error codes on invalid input
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"))
+ # TODO: encoding
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
+ # null is invalid json response
if result is None: result = True
return json.dumps(result, cls=TBaseEncoder)
@@ -72,31 +81,31 @@ def callApi(func, *args, **kwargs):
#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")
+ add_header(response)
- user = request.forms.get("username")
+ username = request.forms.get("username")
password = request.forms.get("password")
- info = PYLOAD.checkAuth(user, password)
+ user = PYLOAD.checkAuth(username, password)
- if not info:
+ if not user:
return json.dumps(False)
- s = set_session(request, info)
+ s = set_session(request, user)
# 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:
+ print "Could not get session"
return json.dumps(True)
@route("/api/logout")
+@route("/api/logout", method="POST")
def logout():
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
+ add_header(response)
s = request.environ.get('beaker.session')
s.delete()
diff --git a/module/web/cnl_app.py b/module/web/cnl_app.py
index d8f7c1180..b6a98a0a8 100644
--- a/module/web/cnl_app.py
+++ b/module/web/cnl_app.py
@@ -6,6 +6,8 @@ from urllib import unquote
from base64 import standard_b64decode
from binascii import unhexlify
+from module.utils.fs import save_filename
+
from bottle import route, request, HTTPError
from webinterface import PYLOAD, DL_ROOT, JS
@@ -18,7 +20,7 @@ except:
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':
+ or request.environ.get('HTTP_HOST','0') in ('127.0.0.1:9666', 'localhost:9666'):
return function(*args, **kwargs)
else:
return HTTPError(403, "Forbidden")
@@ -53,7 +55,7 @@ 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_path = join(DL_ROOT, save_filename(package) + ".dlc")
dlc_file = open(dlc_path, "wb")
dlc_file.write(dlc)
dlc_file.close()
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 fc0d148c2..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/default/css/window.css b/module/web/media/default/css/window.css
deleted file mode 100644
index 8b13f55ec..000000000
--- a/module/web/media/default/css/window.css
+++ /dev/null
@@ -1,73 +0,0 @@
-/* ----------- 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/module/web/media/default/img/add_folder.png b/module/web/media/default/img/add_folder.png
deleted file mode 100644
index 8acbc411b..000000000
--- a/module/web/media/default/img/add_folder.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/ajax-loader.gif b/module/web/media/default/img/ajax-loader.gif
deleted file mode 100644
index 2fd8e0737..000000000
--- a/module/web/media/default/img/ajax-loader.gif
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/arrow_right.png b/module/web/media/default/img/arrow_right.png
deleted file mode 100644
index b1a181923..000000000
--- a/module/web/media/default/img/arrow_right.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/big_button.gif b/module/web/media/default/img/big_button.gif
deleted file mode 100644
index 7680490ea..000000000
--- a/module/web/media/default/img/big_button.gif
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/big_button_over.gif b/module/web/media/default/img/big_button_over.gif
deleted file mode 100644
index 2e3ee10d2..000000000
--- a/module/web/media/default/img/big_button_over.gif
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/body.png b/module/web/media/default/img/body.png
deleted file mode 100644
index 7ff1043e0..000000000
--- a/module/web/media/default/img/body.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/button.png b/module/web/media/default/img/button.png
deleted file mode 100644
index 890160614..000000000
--- a/module/web/media/default/img/button.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/closebtn.gif b/module/web/media/default/img/closebtn.gif
deleted file mode 100644
index 3e27e6030..000000000
--- a/module/web/media/default/img/closebtn.gif
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/cog.png b/module/web/media/default/img/cog.png
deleted file mode 100644
index 67de2c6cc..000000000
--- a/module/web/media/default/img/cog.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_add.png b/module/web/media/default/img/control_add.png
deleted file mode 100644
index d39886893..000000000
--- a/module/web/media/default/img/control_add.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_add_blue.png b/module/web/media/default/img/control_add_blue.png
deleted file mode 100644
index d11b7f41d..000000000
--- a/module/web/media/default/img/control_add_blue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_cancel.png b/module/web/media/default/img/control_cancel.png
deleted file mode 100644
index 7b9bc3fba..000000000
--- a/module/web/media/default/img/control_cancel.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_cancel_blue.png b/module/web/media/default/img/control_cancel_blue.png
deleted file mode 100644
index 0c5c96ce3..000000000
--- a/module/web/media/default/img/control_cancel_blue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_pause.png b/module/web/media/default/img/control_pause.png
deleted file mode 100644
index 2d9ce9c4e..000000000
--- a/module/web/media/default/img/control_pause.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_pause_blue.png b/module/web/media/default/img/control_pause_blue.png
deleted file mode 100644
index ec61099b0..000000000
--- a/module/web/media/default/img/control_pause_blue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_play.png b/module/web/media/default/img/control_play.png
deleted file mode 100644
index 0846555d0..000000000
--- a/module/web/media/default/img/control_play.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_play_blue.png b/module/web/media/default/img/control_play_blue.png
deleted file mode 100644
index f8c8ec683..000000000
--- a/module/web/media/default/img/control_play_blue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_stop.png b/module/web/media/default/img/control_stop.png
deleted file mode 100644
index 893bb60e5..000000000
--- a/module/web/media/default/img/control_stop.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/control_stop_blue.png b/module/web/media/default/img/control_stop_blue.png
deleted file mode 100644
index e6f75d232..000000000
--- a/module/web/media/default/img/control_stop_blue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/drag_corner.gif b/module/web/media/default/img/drag_corner.gif
deleted file mode 100644
index befb1adf1..000000000
--- a/module/web/media/default/img/drag_corner.gif
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/error.png b/module/web/media/default/img/error.png
deleted file mode 100644
index c37bd062e..000000000
--- a/module/web/media/default/img/error.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/full.png b/module/web/media/default/img/full.png
deleted file mode 100644
index fea52af76..000000000
--- a/module/web/media/default/img/full.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-login.png b/module/web/media/default/img/head-login.png
deleted file mode 100644
index b59b7cbbf..000000000
--- a/module/web/media/default/img/head-login.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-collector.png b/module/web/media/default/img/head-menu-collector.png
deleted file mode 100644
index 861be40bc..000000000
--- a/module/web/media/default/img/head-menu-collector.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-config.png b/module/web/media/default/img/head-menu-config.png
deleted file mode 100644
index bbf43d4f3..000000000
--- a/module/web/media/default/img/head-menu-config.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-development.png b/module/web/media/default/img/head-menu-development.png
deleted file mode 100644
index fad150fe1..000000000
--- a/module/web/media/default/img/head-menu-development.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-download.png b/module/web/media/default/img/head-menu-download.png
deleted file mode 100644
index 98c5da9db..000000000
--- a/module/web/media/default/img/head-menu-download.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-home.png b/module/web/media/default/img/head-menu-home.png
deleted file mode 100644
index 9d62109aa..000000000
--- a/module/web/media/default/img/head-menu-home.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-index.png b/module/web/media/default/img/head-menu-index.png
deleted file mode 100644
index 44d631064..000000000
--- a/module/web/media/default/img/head-menu-index.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-news.png b/module/web/media/default/img/head-menu-news.png
deleted file mode 100644
index 43950ebc9..000000000
--- a/module/web/media/default/img/head-menu-news.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-queue.png b/module/web/media/default/img/head-menu-queue.png
deleted file mode 100644
index be98793ce..000000000
--- a/module/web/media/default/img/head-menu-queue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-recent.png b/module/web/media/default/img/head-menu-recent.png
deleted file mode 100644
index fc9b0497f..000000000
--- a/module/web/media/default/img/head-menu-recent.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-menu-wiki.png b/module/web/media/default/img/head-menu-wiki.png
deleted file mode 100644
index 07cf0102d..000000000
--- a/module/web/media/default/img/head-menu-wiki.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head-search-noshadow.png b/module/web/media/default/img/head-search-noshadow.png
deleted file mode 100644
index aafdae015..000000000
--- a/module/web/media/default/img/head-search-noshadow.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/head_bg1.png b/module/web/media/default/img/head_bg1.png
deleted file mode 100644
index f2848c3cc..000000000
--- a/module/web/media/default/img/head_bg1.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/images.png b/module/web/media/default/img/images.png
deleted file mode 100644
index 184860d1e..000000000
--- a/module/web/media/default/img/images.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/notice.png b/module/web/media/default/img/notice.png
deleted file mode 100644
index 12cd1aef9..000000000
--- a/module/web/media/default/img/notice.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/package_go.png b/module/web/media/default/img/package_go.png
deleted file mode 100644
index aace63ad6..000000000
--- a/module/web/media/default/img/package_go.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/page-tools-backlinks.png b/module/web/media/default/img/page-tools-backlinks.png
deleted file mode 100644
index 3eb6a9ce3..000000000
--- a/module/web/media/default/img/page-tools-backlinks.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/page-tools-edit.png b/module/web/media/default/img/page-tools-edit.png
deleted file mode 100644
index 188e1c12b..000000000
--- a/module/web/media/default/img/page-tools-edit.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/page-tools-revisions.png b/module/web/media/default/img/page-tools-revisions.png
deleted file mode 100644
index 5c3b8587f..000000000
--- a/module/web/media/default/img/page-tools-revisions.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/parseUri.png b/module/web/media/default/img/parseUri.png
deleted file mode 100644
index 937bded9d..000000000
--- a/module/web/media/default/img/parseUri.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png b/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png
deleted file mode 100644
index 2443cd8b1..000000000
--- a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/reconnect.png b/module/web/media/default/img/reconnect.png
deleted file mode 100644
index 49b269145..000000000
--- a/module/web/media/default/img/reconnect.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_None.png b/module/web/media/default/img/status_None.png
deleted file mode 100644
index 293b13f77..000000000
--- a/module/web/media/default/img/status_None.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_downloading.png b/module/web/media/default/img/status_downloading.png
deleted file mode 100644
index fb4ebc850..000000000
--- a/module/web/media/default/img/status_downloading.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_failed.png b/module/web/media/default/img/status_failed.png
deleted file mode 100644
index c37bd062e..000000000
--- a/module/web/media/default/img/status_failed.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_finished.png b/module/web/media/default/img/status_finished.png
deleted file mode 100644
index 89c8129a4..000000000
--- a/module/web/media/default/img/status_finished.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_offline.png b/module/web/media/default/img/status_offline.png
deleted file mode 100644
index 0cfd58596..000000000
--- a/module/web/media/default/img/status_offline.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_proc.png b/module/web/media/default/img/status_proc.png
deleted file mode 100644
index 67de2c6cc..000000000
--- a/module/web/media/default/img/status_proc.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_queue.png b/module/web/media/default/img/status_queue.png
deleted file mode 100644
index 293b13f77..000000000
--- a/module/web/media/default/img/status_queue.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/status_waiting.png b/module/web/media/default/img/status_waiting.png
deleted file mode 100644
index 2842cc338..000000000
--- a/module/web/media/default/img/status_waiting.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/success.png b/module/web/media/default/img/success.png
deleted file mode 100644
index 89c8129a4..000000000
--- a/module/web/media/default/img/success.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/tab-background.png b/module/web/media/default/img/tab-background.png
deleted file mode 100644
index 29a5d1991..000000000
--- a/module/web/media/default/img/tab-background.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/tabs-border-bottom.png b/module/web/media/default/img/tabs-border-bottom.png
deleted file mode 100644
index 02440f428..000000000
--- a/module/web/media/default/img/tabs-border-bottom.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/user-actions-logout.png b/module/web/media/default/img/user-actions-logout.png
deleted file mode 100644
index 0010931e2..000000000
--- a/module/web/media/default/img/user-actions-logout.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/user-actions-profile.png b/module/web/media/default/img/user-actions-profile.png
deleted file mode 100644
index 46573fff6..000000000
--- a/module/web/media/default/img/user-actions-profile.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/default/img/user-info.png b/module/web/media/default/img/user-info.png
deleted file mode 100644
index 6e643100f..000000000
--- a/module/web/media/default/img/user-info.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/img/dialog-close.png b/module/web/media/img/dialog-close.png
deleted file mode 100644
index 81ebb88b2..000000000
--- a/module/web/media/img/dialog-close.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/img/dialog-question.png b/module/web/media/img/dialog-question.png
deleted file mode 100644
index b0af3db5b..000000000
--- a/module/web/media/img/dialog-question.png
+++ /dev/null
Binary files differ
diff --git a/module/web/media/js/MooDialog_static.js b/module/web/media/js/MooDialog_static.js
deleted file mode 100644
index d497d3d57..000000000
--- a/module/web/media/js/MooDialog_static.js
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
----
-
-name: Overlay
-
-authors:
- - David Walsh (http://davidwalsh.name)
-
-license:
- - MIT-style license
-
-requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
-
-provides:
- - Overlay
-...
-*/
-
-var Overlay = new Class({
-
- Implements: [Options, Events],
-
- options: {
- id: 'overlay',
- color: '#000',
- duration: 500,
- opacity: 0.5,
- zIndex: 5000/*,
- onClick: $empty,
- onClose: $empty,
- onHide: $empty,
- onOpen: $empty,
- onShow: $empty
- */
- },
-
- initialize: function(container, options){
- this.setOptions(options);
- this.container = document.id(container);
-
- this.bound = {
- 'window': {
- resize: this.resize.bind(this),
- scroll: this.scroll.bind(this)
- },
- overlayClick: this.overlayClick.bind(this),
- tweenStart: this.tweenStart.bind(this),
- tweenComplete: this.tweenComplete.bind(this)
- };
-
- this.build().attach();
- },
-
- build: function(){
- this.overlay = new Element('div', {
- id: this.options.id,
- opacity: 0,
- styles: {
- position: (Browser.ie6) ? 'absolute' : 'fixed',
- background: this.options.color,
- left: 0,
- top: 0,
- 'z-index': this.options.zIndex
- }
- }).inject(this.container);
- this.tween = new Fx.Tween(this.overlay, {
- duration: this.options.duration,
- link: 'cancel',
- property: 'opacity'
- });
- this.tween.set('opacity', 0)
- return this;
- }.protect(),
-
- attach: function(){
- window.addEvents(this.bound.window);
- this.overlay.addEvent('click', this.bound.overlayClick);
- this.tween.addEvents({
- onStart: this.bound.tweenStart,
- onComplete: this.bound.tweenComplete
- });
- return this;
- },
-
- detach: function(){
- var args = Array.prototype.slice.call(arguments);
- args.each(function(item){
- if(item == 'window') window.removeEvents(this.bound.window);
- if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
- }, this);
- return this;
- },
-
- overlayClick: function(){
- this.fireEvent('click');
- return this;
- },
-
- tweenStart: function(){
- this.overlay.setStyles({
- width: '100%',
- height: this.container.getScrollSize().y
- });
- return this;
- },
-
- tweenComplete: function(){
- this.fireEvent(this.overlay.get('opacity') == this.options.opacity ? 'show' : 'hide');
- return this;
- },
-
- open: function(){
- this.fireEvent('open');
- this.tween.set('display', 'block');
- this.tween.start(this.options.opacity);
- return this;
- },
-
- close: function(){
- this.fireEvent('close');
- this.tween.start(0).chain(function(){
- this.tween.set('display', 'none');
- }.bind(this));
- return this;
- },
-
- resize: function(){
- this.fireEvent('resize');
- this.overlay.setStyle('height', this.container.getScrollSize().y);
- return this;
- },
-
- scroll: function(){
- this.fireEvent('scroll');
- if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
- return this;
- }
-
-});
-/*
----
-name: MooDialog
-description: The base class of MooDialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Core/Class, Core/Element, Core/Element.Styles, Core/Element.Event]
-provides: [MooDialog, Element.MooDialog]
-...
-*/
-
-
-var MooDialog = new Class({
-
- Implements: [Options, Events],
-
- options: {
- 'class': 'MooDialog',
- title: null,
- scroll: true, // IE
- forceScroll: false,
- useEscKey: true,
- destroyOnHide: true,
- autoOpen: true,
- closeButton: true,
- onInitialize: function(){
- this.wrapper.setStyle('display', 'none');
- },
- onBeforeOpen: function(){
- this.wrapper.setStyle('display', 'block');
- this.fireEvent('show');
- },
- onBeforeClose: function(){
- this.wrapper.setStyle('display', 'none');
- this.fireEvent('hide');
- }/*,
- onOpen: function(){},
- onClose: function(){},
- onShow: function(){},
- onHide: function(){}*/
- },
-
- initialize: function(options){
- this.setOptions(options);
- this.options.inject = this.options.inject || document.body;
- options = this.options;
-
- var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
- this.content = new Element('div.content').inject(wrapper);
- wrapper.setStyle('display', 'none');
-
- if (options.title){
- this.title = new Element('div.title').set('text', options.title).inject(wrapper);
- wrapper.addClass('MooDialogTitle');
- }
-
- if (options.closeButton){
- this.closeButton = new Element('a.close', {
- events: {click: this.close.bind(this)}
- }).inject(wrapper);
- }
-
-
- /*<ie6>*/// IE 6 scroll
- if ((options.scroll && Browser.ie6) || options.forceScroll){
- wrapper.setStyle('position', 'absolute');
- var position = wrapper.getPosition(options.inject);
- window.addEvent('scroll', function(){
- var scroll = document.getScroll();
- wrapper.setPosition({
- x: position.x + scroll.x,
- y: position.y + scroll.y
- });
- });
- }
- /*</ie6>*/
-
- if (options.useEscKey){
- // Add event for the esc key
- document.addEvent('keydown', function(e){
- if (e.key == 'esc') this.close();
- }.bind(this));
- }
-
- this.addEvent('hide', function(){
- if (options.destroyOnHide) this.destroy();
- }.bind(this));
-
- this.fireEvent('initialize', wrapper);
- },
-
- setContent: function(){
- var content = Array.from(arguments);
- if (content.length == 1) content = content[0];
-
- this.content.empty();
-
- var type = typeOf(content);
- if (['string', 'number'].contains(type)) this.content.set('text', content);
- else this.content.adopt(content);
-
- return this;
- },
-
- open: function(){
- this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
- this.opened = true;
- return this;
- },
-
- close: function(){
- this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
- this.opened = false;
- return this;
- },
-
- destroy: function(){
- this.wrapper.destroy();
- },
-
- toElement: function(){
- return this.wrapper;
- }
-
-});
-
-
-Element.implement({
-
- MooDialog: function(options){
- this.store('MooDialog',
- new MooDialog(options).setContent(this).open()
- );
- return this;
- }
-
-});
-/*
----
-name: MooDialog.Fx
-description: Overwrite the default events so the Dialogs are using Fx on open and close
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Cores/Fx.Tween, Overlay]
-provides: MooDialog.Fx
-...
-*/
-
-
-MooDialog.implement('options', {
-
- duration: 400,
- closeOnOverlayClick: true,
-
- onInitialize: function(wrapper){
- this.fx = new Fx.Tween(wrapper, {
- property: 'opacity',
- duration: this.options.duration
- }).set(0);
- this.overlay = new Overlay(this.options.inject, {
- duration: this.options.duration
- });
- if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
- },
-
- onBeforeOpen: function(wrapper){
- this.overlay.open();
- wrapper.setStyle('display', 'block');
- this.fx.start(1).chain(function(){
- this.fireEvent('show');
- }.bind(this));
- },
-
- onBeforeClose: function(wrapper){
- this.overlay.close();
- this.fx.start(0).chain(function(){
- this.fireEvent('hide');
- wrapper.setStyle('display', 'none');
- }.bind(this));
- }
-
-});
-/*
----
-name: MooDialog.Confirm
-description: Creates an Confirm Dialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: MooDialog
-provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
-...
-*/
-
-
-MooDialog.Confirm = new Class({
-
- Extends: MooDialog,
-
- options: {
- okText: 'Ok',
- cancelText: 'Cancel',
- focus: true,
- textPClass: 'MooDialogConfirm'
- },
-
- initialize: function(msg, fn, fn1, options){
- this.parent(options);
- var emptyFn = function(){},
- self = this;
-
- var buttons = [
- {fn: fn || emptyFn, txt: this.options.okText},
- {fn: fn1 || emptyFn, txt: this.options.cancelText}
- ].map(function(button){
- return new Element('input[type=button]', {
- events: {
- click: function(){
- button.fn();
- self.close();
- }
- },
- value: button.txt
- });
- });
-
- this.setContent(
- new Element('p.' + this.options.textPClass, {text: msg}),
- new Element('div.buttons').adopt(buttons)
- );
- if (this.options.autoOpen) this.open();
-
- if(this.options.focus) this.addEvent('show', function(){
- buttons[1].focus();
- });
-
- }
-});
-
-
-Element.implement({
-
- confirmLinkClick: function(msg, options){
- this.addEvent('click', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- location.href = this.get('href');
- }.bind(this), null, options)
- });
- return this;
- },
-
- confirmFormSubmit: function(msg, options){
- this.addEvent('submit', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- this.submit();
- }.bind(this), null, options)
- }.bind(this));
- return this;
- }
-
-});
diff --git a/module/web/media/js/MooDropMenu_static.js b/module/web/media/js/MooDropMenu_static.js
deleted file mode 100644
index b9cd8cc10..000000000
--- a/module/web/media/js/MooDropMenu_static.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
----
-description: This provides a simple Drop Down menu with infinit levels
-
-license: MIT-style
-
-authors:
-- Arian Stolwijk
-
-requires:
- - Core/Class.Extras
- - Core/Element.Event
- - Core/Selectors
-
-provides: [MooDropMenu, Element.MooDropMenu]
-
-...
-*/
-
-var MooDropMenu = new Class({
-
- Implements: [Options, Events],
-
- options: {
- onOpen: function(el){
- el.removeClass('close').addClass('open');
- },
- onClose: function(el){
- el.removeClass('open').addClass('close');
- },
- onInitialize: function(el){
- el.removeClass('open').addClass('close');
- },
- mouseoutDelay: 200,
- mouseoverDelay: 0,
- listSelector: 'ul',
- itemSelector: 'li'
- },
-
- initialize: function(menu, options, level){
- this.setOptions(options);
- options = this.options;
-
- var menu = this.menu = document.id(menu);
-
- menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
-
- this.fireEvent('initialize', el);
-
- var parent = el.getParent(options.itemSelector),
- timer;
-
- parent.addEvents({
-
- 'mouseenter': function(){
- parent.store('DropDownOpen', true);
-
- clearTimeout(timer);
- if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
- else this.fireEvent('open', el);
-
- }.bind(this),
-
- 'mouseleave': function(){
- parent.store('DropDownOpen', false);
-
- clearTimeout(timer);
- timer = (function(){
- if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
- }).delay(options.mouseoutDelay, this);
-
- }.bind(this)
- });
-
- }, this);
- },
-
- toElement: function(){
- return this.menu
- }
-
-});
-
-/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
-Element.implement({
- MooDropMenu: function(options){
- return this.store('MooDropMenu', new MooDropMenu(this, options));
- }
-});
diff --git a/module/web/media/js/admin.coffee b/module/web/media/js/admin.coffee
deleted file mode 100644
index 82b0dd3ec..000000000
--- a/module/web/media/js/admin.coffee
+++ /dev/null
@@ -1,58 +0,0 @@
-root = this
-
-window.addEvent "domready", ->
-
- root.passwordDialog = new MooDialog {destroyOnHide: false}
- root.passwordDialog.setContent $ 'password_box'
-
- $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
- $("login_password_button").addEvent "click", (e) ->
-
- newpw = $("login_new_password").get("value")
- newpw2 = $("login_new_password2").get("value")
-
- if newpw is newpw2
- form = $("password_form")
- form.set "send", {
- onSuccess: (data) ->
- root.notify.alert "Success", {
- 'className': 'success'
- }
- onFailure: (data) ->
- root.notify.alert "Error", {
- 'className': 'error'
- }
- }
-
- form.send()
-
- root.passwordDialog.close()
- else
- alert '{{_("Passwords did not match.")}}'
-
- e.stop()
-
- for item in $$(".change_password")
- id = item.get("id")
- user = id.split("|")[1]
- $("user_login").set("value", user)
- item.addEvent "click", (e) -> root.passwordDialog.open()
-
- $('quit-pyload').addEvent "click", (e) ->
- new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
- new Request.JSON({
- url: '/api/kill'
- method: 'get'
- }).send()
- , ->
- e.stop()
-
- $('restart-pyload').addEvent "click", (e) ->
- new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
- new Request.JSON({
- url: '/api/restart'
- method: 'get'
- onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
- }).send()
- , ->
- e.stop() \ No newline at end of file
diff --git a/module/web/media/js/admin.js b/module/web/media/js/admin.js
deleted file mode 100644
index d34d310a0..000000000
--- a/module/web/media/js/admin.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/base.coffee b/module/web/media/js/base.coffee
deleted file mode 100644
index 3b5d33e82..000000000
--- a/module/web/media/js/base.coffee
+++ /dev/null
@@ -1,173 +0,0 @@
-# External scope
-root = this
-
-# helper functions
-humanFileSize = (size) ->
- filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
- loga = Math.log(size) / Math.log(1024)
- i = Math.floor(loga)
- a = Math.pow(1024, i)
- if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
-
-parseUri = () ->
- oldString = $("add_links").value
- regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
- resu = oldString.match regxp
- return if resu == null
- res = "";
-
- for part in resu
- if part.indexOf(" ") != -1
- res = res + part.replace(" ", " \n")
- else if part.indexOf("\t") != -1
- res = res + part.replace("\t", " \n")
- else if part.indexOf("\r") != -1
- res = res + part.replace("\r", " \n")
- else if part.indexOf("\"") != -1
- res = res + part.replace("\"", " \n")
- else if part.indexOf("<") != -1
- res = res + part.replace("<", " \n")
- else if part.indexOf("'") != -1
- res = res + part.replace("'", " \n")
- else
- res = res + part.replace("\n", " \n")
-
- $("add_links").value = res;
-
-
-Array::remove = (from, to) ->
- rest = this.slice((to || from) + 1 || this.length)
- this.length = from < 0 ? this.length + from : from
- return [] if this.length == 0
- return this.push.apply(this, rest)
-
-
-document.addEvent "domready", ->
-
- # global notification
- root.notify = new Purr {
- 'mode': 'top'
- 'position': 'center'
- }
-
- root.captchaBox = new MooDialog {destroyOnHide: false}
- root.captchaBox.setContent $ 'cap_box'
-
- root.addBox = new MooDialog {destroyOnHide: false}
- root.addBox.setContent $ 'add_box'
-
- $('add_form').onsubmit = ->
- $('add_form').target = 'upload_target'
- if $('add_name').value is "" and $('add_file').value is ""
- alert '{{_("Please Enter a packagename.")}}'
- return false
- else
- root.addBox.close()
- return true
-
- $('add_reset').addEvent 'click', -> root.addBox.close()
-
- $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
- $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
- $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
- $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
-
-
- # captcha events
-
- $('cap_info').addEvent 'click', ->
- load_captcha "get", ""
- root.captchaBox.open()
- $('cap_reset').addEvent 'click', -> root.captchaBox.close()
- $('cap_form').addEvent 'submit', (e) ->
- submit_captcha()
- e.stop()
-
- $('cap_positional').addEvent 'click', on_captcha_click
-
- new Request.JSON({
- url: "/json/status"
- onSuccess: LoadJsonToContent
- secure: false
- async: true
- initialDelay: 0
- delay: 4000
- limit: 3000
- }).startTimer()
-
-LoadJsonToContent = (data) ->
- $("speed").set 'text', humanFileSize(data.speed)+"/s"
- $("aktiv").set 'text', data.active
- $("aktiv_from").set 'text', data.queue
- $("aktiv_total").set 'text', data.total
-
- if data.captcha
- if $("cap_info").getStyle("display") != "inline"
- $("cap_info").setStyle 'display', 'inline'
- root.notify.alert '{{_("New Captcha Request")}}', {
- 'className': 'notify'
- }
- else
- $("cap_info").setStyle 'display', 'none'
-
-
- if data.download
- $("time").set 'text', ' {{_("on")}}'
- $("time").setStyle 'background-color', "#8ffc25"
- else
- $("time").set 'text', ' {{_("off")}}'
- $("time").setStyle 'background-color', "#fc6e26"
-
- if data.reconnect
- $("reconnect").set 'text', ' {{_("on")}}'
- $("reconnect").setStyle 'background-color', "#8ffc25"
- else
- $("reconnect").set 'text', ' {{_("off")}}'
- $("reconnect").setStyle 'background-color', "#fc6e26"
-
- return null
-
-
-set_captcha = (data) ->
- $('cap_id').set 'value', data.id
- if (data.result_type is 'textual')
- $('cap_textual_img').set 'src', data.src
- $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
- $('cap_submit').setStyle 'display', 'inline'
- $('cap_textual').setStyle 'display', 'block'
- $('cap_positional').setStyle 'display', 'none'
-
- else if (data.result_type == 'positional')
- $('cap_positional_img').set('src', data.src)
- $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
- $('cap_submit').setStyle('display', 'none')
- $('cap_textual').setStyle('display', 'none')
-
-
-load_captcha = (method, post) ->
- new Request.JSON({
- url: "/json/set_captcha"
- onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
- secure: false
- async: true
- method: method
- }).send(post)
-
-clear_captcha = ->
- $('cap_textual').setStyle 'display', 'none'
- $('cap_textual_img').set 'src', ''
- $('cap_positional').setStyle 'display', 'none'
- $('cap_positional_img').set 'src', ''
- $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
-
-submit_captcha = ->
- load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') );
- $('cap_result').set('value', '')
- false
-
-on_captcha_click = (e) ->
- position = e.target.getPosition()
- x = e.page.x - position.x
- y = e.page.y - position.y
- $('cap_result').value = x + "," + y
- submit_captcha() \ No newline at end of file
diff --git a/module/web/media/js/base.js b/module/web/media/js/base.js
deleted file mode 100644
index c68b1047a..000000000
--- a/module/web/media/js/base.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/mootools-core-1.4.1.js b/module/web/media/js/mootools-core-1.4.1.js
deleted file mode 100644
index 835b4bbe2..000000000
--- a/module/web/media/js/mootools-core-1.4.1.js
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
----
-MooTools: the javascript framework
-
-web build:
- - http://mootools.net/core/76bf47062d6c1983d66ce47ad66aa0e0
-
-packager build:
- - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
-
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-(function(){this.MooTools={version:"1.4.1",build:"d1fb25710e3c5482a219ab9dc675a4e0ad2176b6"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family){return i.$family();
-}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if(i.callee){return"arguments";
-}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
-while(s){if(s===i){return true;}s=s.parent;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];
-}f.prototype.overloadSetter=function(s){var i=this;return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);
-}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;
-return function(u){var v,t;if(s||typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);
-}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;
-}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;};};Array.from=function(i){if(i==null){return[];
-}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);return isFinite(i)?i:null;
-};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;return this;
-}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
-}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
-};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
-u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
-});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
-}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,w,u){var t=(w!=Object),A=w.prototype;if(t){w=new a(s,w);
-}for(var x=0,v=u.length;x<v;x++){var B=u[x],z=w[B],y=A[B];if(z){z.protect();}if(t&&y){delete A[B];A[B]=y.protect();}}if(t){w.implement(A);}return d;};d("String",String,["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
-Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
-}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
-}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
-return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
-while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
-}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
-y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
-v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
-});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
-b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,e){var c=[];for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&d.call(e,this[b],b,this)){c.push(this[b]);
-}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);
-for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;
-}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);return this.map(function(c){return c[a].apply(c,b);
-});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];}return d;},link:function(c){var a={};for(var e=0,b=this.length;
-e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;},append:function(a){this.push.apply(this,a);
-return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
-},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
-},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
-for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
-}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
-}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";
-}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});String.implement({test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
-},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:String(this).indexOf(a)>-1;},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");
-},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
-});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
-});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
-},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
-return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
-}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
-return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
-}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
-};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
-b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
-},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
-g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
-if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
-},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
-for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
-}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
-}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
-}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
-}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
-}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
-break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var k=this.document;var i=k.window=this;
-var b=1;this.$uid=(i.ActiveXObject)?function(e){return(e.uid||(e.uid=[b++]))[0];}:function(e){return e.uid||(e.uid=b++);};$uid(i);$uid(k);var a=navigator.userAgent.toLowerCase(),c=navigator.platform.toLowerCase(),j=a.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],f=j[1]=="ie"&&k.documentMode;
-var o=this.Browser={extend:Function.prototype.extend,name:(j[1]=="version")?j[3]:j[1],version:f||parseFloat((j[1]=="opera"&&j[4])?j[4]:j[2]),Platform:{name:a.match(/ip(?:ad|od|hone)/)?"ios":(a.match(/(?:webos|android)/)||c.match(/mac|win|linux/)||["other"])[0]},Features:{xpath:!!(k.evaluate),air:!!(i.runtime),query:!!(k.querySelector),json:!!(i.JSON)},Plugins:{}};
-o[o.name]=true;o[o.name+parseInt(o.version,10)]=true;o.Platform[o.Platform.name]=true;o.Request=(function(){var q=function(){return new XMLHttpRequest();
-};var p=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");};return Function.attempt(function(){q();
-return q;},function(){p();return p;},function(){e();return e;});})();o.Features.xhr=!!(o.Request);var h=(Function.attempt(function(){return navigator.plugins["Shockwave Flash"].description;
-},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);o.Plugins.Flash={version:Number(h[0]||"0."+h[1])||0,build:Number(h[2])||0};
-o.exec=function(p){if(!p){return p;}if(i.execScript){i.execScript(p);}else{var e=k.createElement("script");e.setAttribute("type","text/javascript");e.text=p;
-k.head.appendChild(e);k.head.removeChild(e);}return p;};String.implement("stripScripts",function(p){var e="";var q=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(r,s){e+=s+"\n";
-return"";});if(p===true){o.exec(e);}else{if(typeOf(p)=="function"){p(e,q);}}return q;});o.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
-this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,p){i[e]=p;});this.Document=k.$constructor=new Type("Document",function(){});
-k.$family=Function.from("document").hide();Document.mirror(function(e,p){k[e]=p;});k.html=k.documentElement;if(!k.head){k.head=k.getElementsByTagName("head")[0];
-}if(k.execCommand){try{k.execCommand("BackgroundImageCache",false,true);}catch(g){}}if(this.attachEvent&&!this.addEventListener){var d=function(){this.detachEvent("onunload",d);
-k.head=k.html=k.window=null;};this.attachEvent("onunload",d);}var m=Array.from;try{m(k.html.childNodes);}catch(g){Array.from=function(p){if(typeof p!="string"&&Type.isEnumerable(p)&&typeOf(p)!="array"){var e=p.length,q=new Array(e);
-while(e--){q[e]=p[e];}return q;}return m(p);};var l=Array.prototype,n=l.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var p=l[e];
-Array[e]=function(q){return p.apply(Array.from(q),n.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
-}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
-var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
-this.key=b[d];if(i=="keydown"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
-}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
-this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
-if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
-while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
-this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
-this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
-},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
-}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
-})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
-}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
-g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
-}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
-};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
-break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
-}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
-return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
-}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
-return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
-for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
-return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
-return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
-this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
-},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
-}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
-},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
-}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
-if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
-(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
-var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
-return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
-}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
-o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
-};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
-function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
-if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
-}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
-}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
-}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
-break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
-case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
-};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
-};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
-k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
-};k.setDocument=function(x){var u=x.nodeType;if(u==9){}else{if(u){x=x.ownerDocument;}else{if(x.navigator){x=x.document;}else{return;}}}if(this.document===x){return;
-}this.document=x;var z=x.documentElement,v=this.getUIDXML(z),p=m[v],B;if(p){for(B in p){this[B]=p[B];}return;}p=m[v]={};p.root=z;p.isXMLDocument=this.isXML(x);
-p.brokenStarGEBTN=p.starSelectsClosedQSA=p.idGetsName=p.brokenMixedCaseQSA=p.brokenGEBCN=p.brokenCheckedQSA=p.brokenEmptyAttributeQSA=p.isHTMLDocument=p.nativeMatchesSelector=false;
-var n,o,y,r,s;var t,c="slick_uniqueid";var A=x.createElement("div");var q=x.body||x.getElementsByTagName("body")[0]||z;q.appendChild(A);try{A.innerHTML='<a id="'+c+'"></a>';
-p.isHTMLDocument=!!x.getElementById(c);}catch(w){}if(p.isHTMLDocument){A.style.display="none";A.appendChild(x.createComment(""));o=(A.getElementsByTagName("*").length>1);
-try{A.innerHTML="foo</foo>";t=A.getElementsByTagName("*");n=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");}catch(w){}p.brokenStarGEBTN=o||n;try{A.innerHTML='<a name="'+c+'"></a><b id="'+c+'"></b>';
-p.idGetsName=x.getElementById(c)===A.firstChild;}catch(w){}if(A.getElementsByClassName){try{A.innerHTML='<a class="f"></a><a class="b"></a>';A.getElementsByClassName("b").length;
-A.firstChild.className="b";r=(A.getElementsByClassName("b").length!=2);}catch(w){}try{A.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(A.getElementsByClassName("a").length!=2);
-}catch(w){}p.brokenGEBCN=r||y;}if(A.querySelectorAll){try{A.innerHTML="foo</foo>";t=A.querySelectorAll("*");p.starSelectsClosedQSA=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");
-}catch(w){}try{A.innerHTML='<a class="MiX"></a>';p.brokenMixedCaseQSA=!A.querySelectorAll(".MiX").length;}catch(w){}try{A.innerHTML='<select><option selected="selected">a</option></select>';
-p.brokenCheckedQSA=(A.querySelectorAll(":checked").length==0);}catch(w){}try{A.innerHTML='<a class=""></a>';p.brokenEmptyAttributeQSA=(A.querySelectorAll('[class*=""]').length!=0);
-}catch(w){}}try{A.innerHTML='<form action="s"><input id="action"/></form>';s=(A.firstChild.getAttribute("action")!="s");}catch(w){}p.nativeMatchesSelector=z.matchesSelector||z.mozMatchesSelector||z.webkitMatchesSelector;
-if(p.nativeMatchesSelector){try{p.nativeMatchesSelector.call(z,":slick");p.nativeMatchesSelector=null;}catch(w){}}}try{z.slick_expando=1;delete z.slick_expando;
-p.getUID=this.getUIDHTML;}catch(w){p.getUID=this.getUIDXML;}q.removeChild(A);A=t=q=null;p.getAttribute=(p.isHTMLDocument&&s)?function(E,C){var F=this.attributeGetters[C];
-if(F){return F.call(E);}var D=E.getAttributeNode(C);return(D)?D.nodeValue:null;}:function(D,C){var E=this.attributeGetters[C];return(E)?E.call(D):D.getAttribute(C);
-};p.hasAttribute=(z&&this.isNativeCode(z.hasAttribute))?function(D,C){return D.hasAttribute(C);}:function(D,C){D=D.getAttributeNode(C);return !!(D&&(D.specified||D.nodeValue));
-};p.contains=(z&&this.isNativeCode(z.contains))?function(C,D){return C.contains(D);}:(z&&z.compareDocumentPosition)?function(C,D){return C===D||!!(C.compareDocumentPosition(D)&16);
-}:function(C,D){if(D){do{if(D===C){return true;}}while((D=D.parentNode));}return false;};p.documentSorter=(z.compareDocumentPosition)?function(D,C){if(!D.compareDocumentPosition||!C.compareDocumentPosition){return 0;
-}return D.compareDocumentPosition(C)&4?-1:D===C?0:1;}:("sourceIndex" in z)?function(D,C){if(!D.sourceIndex||!C.sourceIndex){return 0;}return D.sourceIndex-C.sourceIndex;
-}:(x.createRange)?function(F,D){if(!F.ownerDocument||!D.ownerDocument){return 0;}var E=F.ownerDocument.createRange(),C=D.ownerDocument.createRange();E.setStart(F,0);
-E.setEnd(F,0);C.setStart(D,0);C.setEnd(D,0);return E.compareBoundaryPoints(Range.START_TO_END,C);}:null;z=null;for(B in p){this[B]=p[B];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};
-k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);
-if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);
-simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;}E=U.getElementsByTagName(v);if(s){return E[0]||null;
-}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;}A=U.getElementById(v);
-if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
-}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
-if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
-for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
-}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
-}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
-}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
-A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
-}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
-return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
-if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
-if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
-this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
-}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
-}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
-if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
-}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
-if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
-return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
-}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
-}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
-if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
-}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
-k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
-}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
-if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
-}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
-}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
-if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=o.getAttribute("class")||o.className;
-if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
-}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
-if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
-if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
-}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
-if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
-}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
-}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
-if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
-this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
-}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
-break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
-return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
-},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+c+1);
-},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
-while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
-}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
-}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
-},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"class":function(){return this.getAttribute("class")||this.className;
-},"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");
-},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;
-},type:function(){return this.getAttribute("type");},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;
-}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});e.version="1.1.6";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);
-};e.contains=function(c,n){k.setDocument(c);return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);
-return k.hasAttribute(n,c);};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
-return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
-};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
-return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
-var Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];
-b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;f<c;f++){a=d[f];if(g[a.key]!=null){continue;
-}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
-}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;
-}var b={};b[a]=function(){var h=[],e=arguments,j=true;for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");
-}return(j)?new Elements(h):h;};Elements.implement(b);});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$family":Function.from("element").hide()};
-Element.mirror(function(a,b){Element.Prototype[a]=b;});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
-}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
-b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
-for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
-}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
-b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
-c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
-for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
-b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var g=Array.prototype.splice,b={"0":0,"1":1,length:2};
-g.call(b,1,1);if(b[1]==1){Elements.implement("splice",function(){var h=this.length;var e=g.apply(this,arguments);while(h>=this.length){delete this[h--];
-}return e;}.protect());}Elements.implement(Array.prototype);Array.mirror(Elements);var f;try{var a=document.createElement("<input name=x>");f=(a.name=="x");
-}catch(c){}var d=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,h){if(h&&h.checked!=null){h.defaultChecked=h.checked;
-}if(f&&h){e="<"+e;if(h.name){e+=' name="'+d(h.name)+'"';}if(h.type){e+=' type="'+d(h.type)+'"';}e+=">";delete h.name;delete h.type;}return this.id(this.createElement(e)).set(h);
-}});})();Document.implement({newTextNode:function(a){return this.createTextNode(a);},getDocument:function(){return this;},getWindow:function(){return this.window;
-},id:(function(){var a={string:function(d,c,b){d=Slick.find(b,"#"+d.replace(/(\W)/g,"\\$1"));return(d)?a.element(d,c):null;},element:function(b,c){$uid(b);
-if(!c&&!b.$family&&!(/^(?:object|embed)$/i).test(b.tagName)){Object.append(b,Element.Prototype);}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d);
-}return null;}};a.textnode=a.whitespace=a.window=a.document=function(b){return b;};return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=typeOf(c);
-return(a[b])?a[b](c,e,d||document):null;};})()});if(window.$==null){Window.implement("$",function(a,b){return document.id(a,b,this.document);});}Window.implement({getDocument:function(){return this.document;
-},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(a){return Slick.search(this,a,new Elements);},getElement:function(a){return document.id(Slick.find(this,a));
-}});var contains={contains:function(a){return Slick.contains(this,a);}};if(!document.contains){Document.implement(contains);}if(!document.createElement("div").contains){Element.implement(contains);
-}var injectCombinator=function(d,c){if(!d){return c;}d=Object.clone(Slick.parse(d));var b=d.expressions;for(var a=b.length;a--;){b[a][0].combinator=c;}return d;
-};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(a,b){Element.implement(b,function(c){return this.getElement(injectCombinator(c,a));
-});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(a,b){Element.implement(b,function(c){return this.getElements(injectCombinator(c,a));
-});});Element.implement({getFirst:function(a){return document.id(Slick.search(this,injectCombinator(a,">"))[0]);},getLast:function(a){return document.id(Slick.search(this,injectCombinator(a,">")).getLast());
-},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(a){return document.id(Slick.find(this,"#"+(""+a).replace(/(\W)/g,"\\$1")));
-},match:function(a){return !a||Slick.match(this,a);}});if(window.$$==null){Window.implement("$$",function(a){if(arguments.length==1){if(typeof a=="string"){return Slick.search(this.document,a,new Elements);
-}else{if(Type.isEnumerable(a)){return new Elements(a);}}}return new Elements(arguments);});}(function(){var b={before:function(n,m){var o=m.parentNode;
-if(o){o.insertBefore(n,m);}},after:function(n,m){var o=m.parentNode;if(o){o.insertBefore(n,m.nextSibling);}},bottom:function(n,m){m.appendChild(n);},top:function(n,m){m.insertBefore(n,m.firstChild);
-}};b.inside=b.bottom;var k={},d={};var i={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","readOnly","rowSpan","tabIndex","useMap"],function(m){i[m.toLowerCase()]=m;
-});Object.append(i,{html:"innerHTML",text:(function(){var m=document.createElement("div");return(m.textContent==null)?"innerText":"textContent";})()});
-Object.forEach(i,function(n,m){d[m]=function(o,p){o[n]=p;};k[m]=function(o){return o[n];};});var a=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
-var h={};Array.forEach(a,function(m){var n=m.toLowerCase();h[n]=m;d[n]=function(o,p){o[m]=!!p;};k[n]=function(o){return !!o[m];};});Object.append(d,{"class":function(m,n){("className" in m)?m.className=n:m.setAttribute("class",n);
-},"for":function(m,n){("htmlFor" in m)?m.htmlFor=n:m.setAttribute("for",n);},style:function(m,n){(m.style)?m.style.cssText=n:m.setAttribute("style",n);
-}});Element.implement({setProperty:function(n,o){var m=n.toLowerCase();if(o==null){if(!h[m]){this.removeAttribute(n);return this;}o=false;}var p=d[m];if(p){p(this,o);
-}else{this.setAttribute(n,o);}return this;},setProperties:function(m){for(var n in m){this.setProperty(n,m[n]);}return this;},getProperty:function(o){var n=k[o.toLowerCase()];
-if(n){return n(this);}var m=Slick.getAttribute(this,o);return(!m&&!Slick.hasAttribute(this,o))?null:m;},getProperties:function(){var m=Array.from(arguments);
-return m.map(this.getProperty,this).associate(m);},removeProperty:function(m){return this.setProperty(m,null);},removeProperties:function(){Array.each(arguments,this.removeProperty,this);
-return this;},set:function(o,n){var m=Element.Properties[o];(m&&m.set)?m.set.call(this,n):this.setProperty(o,n);}.overloadSetter(),get:function(n){var m=Element.Properties[n];
-return(m&&m.get)?m.get.apply(this):this.getProperty(n);}.overloadGetter(),erase:function(n){var m=Element.Properties[n];(m&&m.erase)?m.erase.apply(this):this.removeProperty(n);
-return this;},hasClass:function(m){return this.className.clean().contains(m," ");},addClass:function(m){if(!this.hasClass(m)){this.className=(this.className+" "+m).clean();
-}return this;},removeClass:function(m){this.className=this.className.replace(new RegExp("(^|\\s)"+m+"(?:\\s|$)"),"$1");return this;},toggleClass:function(m,n){if(n==null){n=!this.hasClass(m);
-}return(n)?this.addClass(m):this.removeClass(m);},adopt:function(){var p=this,m,r=Array.flatten(arguments),q=r.length;if(q>1){p=m=document.createDocumentFragment();
-}for(var o=0;o<q;o++){var n=document.id(r[o],true);if(n){p.appendChild(n);}}if(m){this.appendChild(m);}return this;},appendText:function(n,m){return this.grab(this.getDocument().newTextNode(n),m);
-},grab:function(n,m){b[m||"bottom"](document.id(n,true),this);return this;},inject:function(n,m){b[m||"bottom"](this,document.id(n,true));return this;},replaces:function(m){m=document.id(m,true);
-m.parentNode.replaceChild(this,m);return this;},wraps:function(n,m){n=document.id(n,true);return this.replaces(n).grab(n,m);},getSelected:function(){this.selectedIndex;
-return new Elements(Array.from(this.options).filter(function(m){return m.selected;}));},toQueryString:function(){var m=[];this.getElements("input, select, textarea").each(function(o){var n=o.type;
-if(!o.name||o.disabled||n=="submit"||n=="reset"||n=="file"||n=="image"){return;}var p=(o.get("tag")=="select")?o.getSelected().map(function(q){return document.id(q).get("value");
-}):((n=="radio"||n=="checkbox")&&!o.checked)?null:o.get("value");Array.from(p).each(function(q){if(typeof q!="undefined"){m.push(encodeURIComponent(o.name)+"="+encodeURIComponent(q));
-}});});return m.join("&");}});var j={},e={};var c=function(m){return(e[m]||(e[m]={}));};var g=function(n){var m=n.uid;if(n.removeEvents){n.removeEvents();
-}if(n.clearAttributes){n.clearAttributes();}if(m!=null){delete j[m];delete e[m];}return n;};var l={input:"checked",option:"selected",textarea:"value"};
-Element.implement({destroy:function(){var m=g(this).getElementsByTagName("*");Array.each(m,g);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
-return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(r,p){r=r!==false;var w=this.cloneNode(r),o=[w],q=[this],u;
-if(r){o.append(Array.from(w.getElementsByTagName("*")));q.append(Array.from(this.getElementsByTagName("*")));}for(u=o.length;u--;){var s=o[u],v=q[u];if(!p){s.removeAttribute("id");
-}if(s.clearAttributes){s.clearAttributes();s.mergeAttributes(v);s.removeAttribute("uid");if(s.options){var z=s.options,m=v.options;for(var t=z.length;t--;
-){z[t].selected=m[t].selected;}}}var n=l[v.tagName.toLowerCase()];if(n&&v[n]){s[n]=v[n];}}if(Browser.ie){var x=w.getElementsByTagName("object"),y=this.getElementsByTagName("object");
-for(u=x.length;u--;){x[u].outerHTML=y[u].outerHTML;}}return document.id(w);}});[Element,Window,Document].invoke("implement",{addListener:function(p,o){if(p=="unload"){var m=o,n=this;
-o=function(){n.removeListener("unload",o);m();};}else{j[$uid(this)]=this;}if(this.addEventListener){this.addEventListener(p,o,!!arguments[2]);}else{this.attachEvent("on"+p,o);
-}return this;},removeListener:function(n,m){if(this.removeEventListener){this.removeEventListener(n,m,!!arguments[2]);}else{this.detachEvent("on"+n,m);
-}return this;},retrieve:function(n,m){var p=c($uid(this)),o=p[n];if(m!=null&&o==null){o=p[n]=m;}return o!=null?o:null;},store:function(n,m){var o=c($uid(this));
-o[n]=m;return this;},eliminate:function(m){var n=c($uid(this));delete n[m];return this;}});if(window.attachEvent&&!window.addEventListener){window.addListener("unload",function(){Object.each(j,g);
-if(window.CollectGarbage){CollectGarbage();}});}Element.Properties={};Element.Properties.style={set:function(m){this.style.cssText=m;},get:function(){return this.style.cssText;
-},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var s=Function.attempt(function(){var u=document.createElement("table");
-u.innerHTML="<tr><td></td></tr>";});var t=document.createElement("div");var o={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
-o.thead=o.tfoot=o.tbody;t.innerHTML="<nav></nav>";var n=t.childNodes.length==1;if(!n){var q="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),p=document.createDocumentFragment(),m=q.length;
-while(m--){p.createElement(q[m]);}p.appendChild(t);}var r={set:function(v){if(typeOf(v)=="array"){v=v.join("");}var w=(!s&&o[this.get("tag")]);if(!w&&!n){w=[0,"",""];
-}if(w){var x=t;x.innerHTML=w[1]+v+w[2];for(var u=w[0];u--;){x=x.firstChild;}this.empty().adopt(x.childNodes);}else{this.innerHTML=v;}}};r.erase=r.set;return r;
-})();var f=document.createElement("form");f.innerHTML="<select><option>s</option></select>";if(f.firstChild.value!="s"){Element.Properties.value={set:function(r){var n=this.get("tag");
-if(n!="select"){return this.setProperty("value",r);}var o=this.getElements("option");for(var p=0;p<o.length;p++){var q=o[p],m=q.getAttributeNode("value"),s=(m&&m.specified)?q.value:q.get("text");
-if(s==r){return q.selected=true;}}},get:function(){var o=this,n=o.get("tag");if(n!="select"&&n!="option"){return this.getProperty("value");}if(n=="select"&&!(o=o.getSelected()[0])){return"";
-}var m=o.getAttributeNode("value");return(m&&m.specified)?o.value:o.get("text");}};}})();(function(){var f=document.html;Element.Properties.styles={set:function(i){this.setStyles(i);
-}};var h=(f.style.opacity!=null),a=(f.style.filter!=null),g=/alpha\(opacity=([\d.]+)\)/i;var b=function(j,i){j.store("$opacity",i);j.style.visibility=i>0?"visible":"hidden";
-};var d=(h?function(j,i){j.style.opacity=i;}:(a?function(j,i){if(!j.currentStyle||!j.currentStyle.hasLayout){j.style.zoom=1;}i=(i*100).limit(0,100).round();
-i=(i==100)?"":"alpha(opacity="+i+")";var k=j.style.filter||j.getComputedStyle("filter")||"";j.style.filter=g.test(k)?k.replace(g,i):k+i;}:b));var e=(h?function(j){var i=j.style.opacity||j.getComputedStyle("opacity");
-return(i=="")?1:i.toFloat();}:(a?function(j){var k=(j.style.filter||j.getComputedStyle("filter")),i;if(k){i=k.match(g);}return(i==null||k==null)?1:(i[1]/100);
-}:function(j){var i=j.retrieve("$opacity");if(i==null){i=(j.style.visibility=="hidden"?0:1);}return i;}));var c=(f.style.cssFloat==null)?"styleFloat":"cssFloat";
-Element.implement({getComputedStyle:function(k){if(this.currentStyle){return this.currentStyle[k.camelCase()];}var j=Element.getDocument(this).defaultView,i=j?j.getComputedStyle(this,null):null;
-return(i)?i.getPropertyValue((k==c)?"float":k.hyphenate()):null;},setStyle:function(j,i){if(j=="opacity"){d(this,parseFloat(i));return this;}j=(j=="float"?c:j).camelCase();
-if(typeOf(i)!="string"){var k=(Element.Styles[j]||"@").split(" ");i=Array.from(i).map(function(m,l){if(!k[l]){return"";}return(typeOf(m)=="number")?k[l].replace("@",Math.round(m)):m;
-}).join(" ");}else{if(i==String(Number(i))){i=Math.round(i);}}this.style[j]=i;return this;},getStyle:function(o){if(o=="opacity"){return e(this);}o=(o=="float"?c:o).camelCase();
-var i=this.style[o];if(!i||o=="zIndex"){i=[];for(var n in Element.ShortStyles){if(o!=n){continue;}for(var m in Element.ShortStyles[n]){i.push(this.getStyle(m));
-}return i.join(" ");}i=this.getComputedStyle(o);}if(i){i=String(i);var k=i.match(/rgba?\([\d\s,]+\)/);if(k){i=i.replace(k[0],k[0].rgbToHex());}}if(Browser.opera||(Browser.ie&&isNaN(parseFloat(i)))){if((/^(height|width)$/).test(o)){var j=(o=="width")?["left","right"]:["top","bottom"],l=0;
-j.each(function(p){l+=this.getStyle("border-"+p+"-width").toInt()+this.getStyle("padding-"+p).toInt();},this);return this["offset"+o.capitalize()]-l+"px";
-}if(Browser.opera&&String(i).indexOf("px")!=-1){return i;}if((/^border(.+)Width|margin|padding/).test(o)){return"0px";}}return i;},setStyles:function(j){for(var i in j){this.setStyle(i,j[i]);
-}return this;},getStyles:function(){var i={};Array.flatten(arguments).each(function(j){i[j]=this.getStyle(j);},this);return i;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
-Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(o){var n=Element.ShortStyles;
-var j=Element.Styles;["margin","padding"].each(function(p){var q=p+o;n[p][q]=j[q]="@px";});var m="border"+o;n.border[m]=j[m]="@px @ rgb(@, @, @)";var l=m+"Width",i=m+"Style",k=m+"Color";
-n[m]={};n.borderWidth[l]=n[m][l]=j[l]="@px";n.borderStyle[i]=n[m][i]=j[i]="@";n.borderColor[k]=n[m][k]=j[k]="rgb(@, @, @)";});})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);
-}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;
-}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);
-}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());
-if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");
-if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];
-if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;
-},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);
-}return this;}var c=this.retrieve("events");if(!c){return this;}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);
-},this);delete c[b];}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
-}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
-}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};
-var a=function(b){var c=b.relatedTarget;if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));
-};Element.Events={mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.firefox)?"DOMMouseScroll":"mousewheel"}};
-if(!window.addEventListener){Element.NativeEvents.propertychange=2;Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";
-},condition:function(b){return !!(this.type!="radio"||this.checked);}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;
-var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover"},mouseleave:{base:"mouseout"},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
-var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
-n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
-if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
-}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
-}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
-}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
-});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
-}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(w&&w.condition){var l=q,m=w.condition;q=function(C,B){return l(C,B)&&m.call(C,B,v);
-};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);}}:function(B,C){if(!C&&B&&B.target){C=B.target;
-}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];
-if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);}delete p[u];q[m]=p;return j.call(this,r,w);}var o,v;
-if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);
-}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});})();(function(){var h=document.createElement("div"),e=document.createElement("div");
-h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);
-};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();
-}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};
-},getScroll:function(){if(a(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};
-while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}var n=(k(m,"position")=="static")?i:l;while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}try{return m.offsetParent;}catch(n){}return null;},getOffsets:function(){if(this.getBoundingClientRect&&!Browser.Platform.ios){var r=this.getBoundingClientRect(),o=document.id(this.getDocument().documentElement),q=o.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
-return{x:r.left.toInt()+t.x+((s)?0:q.x)-o.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-o.clientTop};}var n=this,m={x:0,y:0};if(a(this)){return m;}while(n&&!a(n)){m.x+=n.offsetLeft;
-m.y+=n.offsetTop;if(Browser.firefox){if(!c(n)){m.x+=b(n);m.y+=g(n);}var p=n.parentNode;if(p&&k(p,"overflow")!="visible"){m.x+=b(p);m.y+=g(p);}}else{if(n!=this&&Browser.safari){m.x+=b(n);
-m.y+=g(n);}}n=n.offsetParent;}if(Browser.firefox&&!c(this)){m.x-=b(this);m.y-=g(this);}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();
-var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();
-}var m=this.getPosition(o),n=this.getSize();var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
-},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
-},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
-return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
-return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
-}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
-}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
-},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
-},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
-},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
-this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
-this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
-}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
-},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
-return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
-this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
-this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
-d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
-}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
-this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
-},resume:function(){if((this.frame<this.frames)&&!this.isRunning()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];
-return g&&g.contains(this);}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};var e={},c={};var a=function(){var h=Date.now();
-for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);
-}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(c,d,b){b=Array.from(b);
-if(b[1]==null){b[1]=b[0];b[0]=c.getStyle(d);}var a=b.map(this.parse);return{from:a[0],to:a[1]};},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);
-return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};
-}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
-});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
-});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var c={},b=new RegExp("^"+a.escapeRegExp()+"$");
-Array.each(document.styleSheets,function(f,e){var d=f.href;if(d&&d.contains("://")&&!d.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;
-}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!b.test(j)){return;}Object.each(Element.Styles,function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;
-}l=String(k.style[i]);c[i]=((/^rgb/).test(l))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=c;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
-}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
-});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
-},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
-b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
-}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
-}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
-this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(c){var d=this.get("tween"),f,e,a;
-if(c==null){c="toggle";}switch(c){case"in":f="start";e=1;break;case"out":f="start";e=0;break;case"show":f="set";e=1;break;case"hide":f="set";e=0;break;
-case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);f="start";e=b?0:1;this.store("fade:flag",!b);a=true;break;default:f="start";e=c;
-}if(!a){this.eliminate("fade:flag");}d[f]("opacity",e);if(f=="set"||e!=0){this.setStyle("visibility",e==0?"hidden":"visible");}else{d.chain(function(){this.element.setStyle("visibility","hidden");
-});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
-}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
-b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
-},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
-for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
-for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
-return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
-return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
-a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
-var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
-}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
-},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
-return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
-},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
-});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
-this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
-this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
-}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
-}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
-}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
-},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
-},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
-return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
-}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
-}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
-o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
-break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
-j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
-}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.contains("?")?"&":"?")+String.uniqueID();
-}if(j&&e=="get"){f+=(f.contains("?")?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
-}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
-}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
-}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
-}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
-if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
-if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
-return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
-this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
-Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
-c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
-c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
-}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
-}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
-},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
-}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
-}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
-};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
-return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
-}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
-Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
-case"null":return"null";}return null;};JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure||JSON.secure){if(JSON.parse){return JSON.parse(string);
-}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");}}return eval("("+string+")");
-};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
-},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
-}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
-this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
-}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
-}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
-return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
-Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
-};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
-k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
-if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
-c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
-}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
-}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document);(function(){var Swiff=this.Swiff=new Class({Implements:Options,options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"window",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;
-},initialize:function(path,options){this.instance="Swiff_"+String.uniqueID();this.setOptions(options);options=this.options;var id=this.id=options.id||this.instance;
-var container=document.id(options.container);Swiff.CallBacks[this.instance]={};var params=options.params,vars=options.vars,callBacks=options.callBacks;
-var properties=Object.append({height:options.height,width:options.width},options.properties);var self=this;for(var callBack in callBacks){Swiff.CallBacks[this.instance][callBack]=(function(option){return function(){return option.apply(self.object,arguments);
-};})(callBacks[callBack]);vars[callBack]="Swiff.CallBacks."+this.instance+"."+callBack;}params.flashVars=Object.toQueryString(vars);if(Browser.ie){properties.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
-params.movie=path;}else{properties.type="application/x-shockwave-flash";}properties.data=path;var build='<object id="'+id+'"';for(var property in properties){build+=" "+property+'="'+properties[property]+'"';
-}build+=">";for(var param in params){if(params[param]){build+='<param name="'+param+'" value="'+params[param]+'" />';}}build+="</object>";this.object=((container)?container.empty():new Element("div")).set("html",build).firstChild;
-},replaces:function(element){element=document.id(element,true);element.parentNode.replaceChild(this.toElement(),element);return this;},inject:function(element){document.id(element,true).appendChild(this.toElement());
-return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].append(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");
-return eval(rs);};})(); \ No newline at end of file
diff --git a/module/web/media/js/mootools-more-1.4.0.1.js b/module/web/media/js/mootools-more-1.4.0.1.js
deleted file mode 100644
index f3f8e4ee1..000000000
--- a/module/web/media/js/mootools-more-1.4.0.1.js
+++ /dev/null
@@ -1,216 +0,0 @@
-// MooTools: the javascript framework.
-// Load this file's selection again by visiting: http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
-// Or build this file again with packager using: packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
-/*
----
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-MooTools.More={version:"1.4.0.1",build:"a4244edf2aa97ac8a196fc96082dd35af1abab87"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
-}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
-if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
-if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
-c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
-return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
-});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
-};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
-g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
-}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
-},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
-};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
-},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
-if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
-},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
-e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
-a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
-},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
-},setOffsetOption:function(f,d){var c={x:0,y:0},g=f.measure(function(){return document.id(this.getOffsetParent());}),e=g.getScroll();if(!g||g==f.getDocument().body){return;
-}c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};
-},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});},getPosition:function(e,d){var c={};
-d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();
-c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);
-}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;
-return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
-switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
-break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
-["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
-},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
-},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
-c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
-},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
-break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
-c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
-}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
-return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);var IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:(Browser.ie6||(Browser.firefox&&Browser.version<3&&Browser.Platform.mac))},property:"IframeShim",initialize:function(b,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var c=this.element.getStyle("zIndex").toInt();
-if(!c){c=1;var b=this.element.getStyle("position");if(b=="static"||!b){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",c);
-}c=((this.options.zIndex!=null||this.options.zIndex===0)&&c>this.options.zIndex)?this.options.zIndex:c-1;if(c<0){c=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:c,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
-var a=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",a);
-}else{a();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
-}var a=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){a.x=a.x-(this.options.margin*2);a.y=a.y-(this.options.margin*2);
-this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:a.x,height:a.y}).position({relativeTo:this.element,offset:this.options.offset});
-return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
-}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
-}});window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
-this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
-if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||this.target==document.body?"inside":"after";
-b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
-this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
-this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
-},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
-var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
-return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
-return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
-}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
-this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
-},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
-}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
-this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
-},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
-this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
-this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
-}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
-}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
-b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
-}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
-if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
-if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
-},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
-},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
-}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
-}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
-if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
-}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
-return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
-}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
-}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
-while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
-if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
-if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
-}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
-}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
-},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
-e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
-}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
-["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
-while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
-})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
-}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
-});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
-},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
-}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
-var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
-},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
-if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
-b.addEvent("onFormValidate",this.onFormValidate);this.element.validate();return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
-if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
-this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
-if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
-this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
-}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
-},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
-},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
-},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
-},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
-if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
-if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
-};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:!Browser.ie6,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
-},opacity:1,hideInputs:Browser.ie?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
-this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
-var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
-this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
-}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
-}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
-this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
-}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
-Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
-this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
-}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
-}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
-}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
-if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
-return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
-Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
-return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
-}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
-b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
-}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
-this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.ie)?"selectstart":"mousedown";if(Browser.ie&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
-Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
-this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
-return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
-}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
-}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
-}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
-if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
-}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
-}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
-this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
-}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
-}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
-}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
-}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
-if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
-a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
-this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
-b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
-}if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));
-}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();
-}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
-["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
-g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
-if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
-n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
-l+=((Browser.ie6||Browser.ie7)?0:k.top)+m.top;}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};},getDroppableCoordinates:function(c){var b=c.getCoordinates();
-if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
-var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
-}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
-}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
-this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{}},initialize:function(a,b){this.setOptions(b);
-this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
-}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
-var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
-},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
-},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
-return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
-},this));},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);
-}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
-});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
-}});}return c.inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
-if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
-this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
-},start:function(b,a){if(!this.idle||b.rightClick||["button","input","a","textarea"].contains(b.target.get("tag"))){return;}this.idle=false;this.element=a;
-this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
-this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
-}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
-},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);if(this.effect){var b=this.element.getStyles("width","height"),d=this.clone,c=d.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
-var a=function(){this.removeEvent("cancel",a);d.destroy();};this.effect.element=d;this.effect.start({top:c.top,left:c.left,width:b.width,height:b.height,opacity:0.25}).addEvent("cancel",a).chain(a);
-}else{this.clone.destroy();}this.reset();},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
-}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
-}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
-}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
-this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
-}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
-}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
-break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
-var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
-}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
-},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
-}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
-};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
-if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
-},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
-var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
-case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
-return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/module/web/media/js/package_ui.js b/module/web/media/js/package_ui.js
deleted file mode 100644
index 3ea965649..000000000
--- a/module/web/media/js/package_ui.js
+++ /dev/null
@@ -1,377 +0,0 @@
-var root = this;
-
-document.addEvent("domready", function() {
- root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
- root.load.set("opacity", 0);
-
-
- root.packageBox = new MooDialog({destroyOnHide: false});
- root.packageBox.setContent($('pack_box'));
-
- $('pack_reset').addEvent('click', function() {
- $('pack_form').reset();
- root.packageBox.close();
- });
-});
-
-function indicateLoad() {
- //$("load-indicator").reveal();
- root.load.start("opacity", 1)
-}
-
-function indicateFinish() {
- root.load.start("opacity", 0)
-}
-
-function indicateSuccess() {
- indicateFinish();
- root.notify.alert('{{_("Success")}}.', {
- 'className': 'success'
- });
-}
-
-function indicateFail() {
- indicateFinish();
- root.notify.alert('{{_("Failed")}}.', {
- 'className': 'error'
- });
-}
-
-var PackageUI = new Class({
- initialize: function(url, type) {
- this.url = url;
- this.type = type;
- this.packages = [];
- this.parsePackages();
-
- this.sorts = new Sortables($("package-list"), {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".package_drag",
- onComplete: this.saveSort.bind(this)
- });
-
- $("del_finished").addEvent("click", this.deleteFinished.bind(this));
- $("restart_failed").addEvent("click", this.restartFailed.bind(this));
-
- },
-
- parsePackages: function() {
- $("package-list").getChildren("li").each(function(ele) {
- var id = ele.getFirst().get("id").match(/[0-9]+/);
- this.packages.push(new Package(this, id, ele))
- }.bind(this))
- },
-
- loadPackages: function() {
- },
-
- deleteFinished: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/deleteFinished',
- onSuccess: function(data) {
- if (data.length > 0) {
- window.location.reload()
- } else {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- restartFailed: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/restartFailed',
- onSuccess: function(data) {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- startSort: function(ele, copy) {
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("pid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
-var Package = new Class({
- initialize: function(ui, id, ele, data) {
- this.ui = ui;
- this.id = id;
- this.linksLoaded = false;
-
- if (!ele) {
- this.createElement(data);
- } else {
- this.ele = ele;
- this.order = ele.getElements("div.order")[0].get("html");
- this.ele.store("order", this.order);
- this.ele.store("pid", this.id);
- this.parseElement();
- }
-
- var pname = this.ele.getElements(".packagename")[0];
- this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
- this.buttons.set("opacity", 0);
-
- pname.addEvent("mouseenter", function(e) {
- this.buttons.start("opacity", 1)
- }.bind(this));
-
- pname.addEvent("mouseleave", function(e) {
- this.buttons.start("opacity", 0)
- }.bind(this));
-
-
- },
-
- createElement: function() {
- alert("create")
- },
-
- parseElement: function() {
- var imgs = this.ele.getElements('img');
-
- this.name = this.ele.getElements('.name')[0];
- this.folder = this.ele.getElements('.folder')[0];
- this.password = this.ele.getElements('.password')[0];
-
- imgs[1].addEvent('click', this.deletePackage.bind(this));
- imgs[2].addEvent('click', this.restartPackage.bind(this));
- imgs[3].addEvent('click', this.editPackage.bind(this));
- imgs[4].addEvent('click', this.movePackage.bind(this));
-
- this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
-
- },
-
- loadLinks: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package/' + this.id,
- onSuccess: this.createLinks.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- createLinks: function(data) {
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.set("html", "");
- data.links.each(function(link) {
- link.id = link.fid;
- var li = new Element("li", {
- "style": {
- "margin-left": 0
- }
- });
-
- var html = "<span style='cursor: move' class='child_status sorthandle'><img src='/media/default/img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({"icon": link.icon});
- html += "<span style='font-size: 15px'>{name}</span><br /><div class='child_secrow'>".substitute({"name": link.name});
- html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({"statusmsg": link.statusmsg, "error":link.error});
- html += "<span class='child_status'>{format_size}</span>".substitute({"format_size": link.format_size});
- html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({"plugin": link.plugin});
- html += "<img title='{{_("Delete Link")}}' style='cursor: pointer;' width='10px' height='10px' src='/media/default/img/delete.png' />&nbsp;&nbsp;";
- html += "<img title='{{_("Restart Link")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='/media/default/img/arrow_refresh.png' /></div>";
-
- var div = new Element("div", {
- "id": "file_" + link.id,
- "class": "child",
- "html": html
- });
-
- li.store("order", link.order);
- li.store("lid", link.id);
-
- li.adopt(div);
- ul.adopt(li);
- });
- this.sorts = new Sortables(ul, {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".sorthandle",
- onComplete: this.saveSort.bind(this)
- });
- this.registerLinkEvents();
- this.linksLoaded = true;
- indicateFinish();
- this.toggle();
- },
-
- registerLinkEvents: function() {
- this.ele.getElements('.child').each(function(child) {
- var lid = child.get('id').match(/[0-9]+/);
- var imgs = child.getElements('.child_secrow img');
- imgs[0].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/deleteFiles/[' + this + "]",
- onSuccess: function() {
- $('file_' + this).nix()
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
-
- imgs[1].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/restartFile/' + this,
- onSuccess: function() {
- var ele = $('file_' + this);
- var imgs = ele.getElements("img");
- imgs[0].set("src", "/media/default/img/status_queue.png");
- var spans = ele.getElements(".child_status");
- spans[1].set("html", "queued");
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
- });
- },
-
- toggle: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- } else {
- if (!this.linksLoaded) {
- this.loadLinks();
- } else {
- child.reveal();
- }
- }
- },
-
-
- deletePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/deletePackages/[' + this.id + "]",
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- //hide_pack();
- event.stop();
- },
-
- restartPackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/restartPackage/' + this.id,
- onSuccess: function() {
- this.close();
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- close: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- }
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.erase("html");
- this.linksLoaded = false;
- },
-
- movePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- editPackage: function(event) {
- $("pack_form").removeEvents("submit");
- $("pack_form").addEvent("submit", this.savePackage.bind(this));
-
- $("pack_id").set("value", this.id);
- $("pack_name").set("value", this.name.get("text"));
- $("pack_folder").set("value", this.folder.get("text"));
- $("pack_pws").set("value", this.password.get("text"));
-
- root.packageBox.open();
- event.stop();
- },
-
- savePackage: function(event) {
- $("pack_form").send();
- this.name.set("text", $("pack_name").get("value"));
- this.folder.set("text", $("pack_folder").get("value"));
- this.password.set("text", $("pack_pws").get("value"));
- root.packageBox.close();
- event.stop();
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("lid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/link_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
diff --git a/module/web/media/js/purr_static.js b/module/web/media/js/purr_static.js
deleted file mode 100644
index 7e0aee949..000000000
--- a/module/web/media/js/purr_static.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
----
-script: purr.js
-
-description: Class to create growl-style popup notifications.
-
-license: MIT-style
-
-authors: [atom smith]
-
-requires:
-- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
-
-provides: [Purr, Element.alert]
-...
-*/
-
-
-var Purr = new Class({
-
- 'options': {
- 'mode': 'top',
- 'position': 'left',
- 'elementAlertClass': 'purr-element-alert',
- 'elements': {
- 'wrapper': 'div',
- 'alert': 'div',
- 'buttonWrapper': 'div',
- 'button': 'button'
- },
- 'elementOptions': {
- 'wrapper': {
- 'styles': {
- 'position': 'fixed',
- 'z-index': '9999'
- },
- 'class': 'purr-wrapper'
- },
- 'alert': {
- 'class': 'purr-alert',
- 'styles': {
- 'opacity': '.85'
- }
- },
- 'buttonWrapper': {
- 'class': 'purr-button-wrapper'
- },
- 'button': {
- 'class': 'purr-button'
- }
- },
- 'alert': {
- 'buttons': [],
- 'clickDismiss': true,
- 'hoverWait': true,
- 'hideAfter': 5000,
- 'fx': {
- 'duration': 500
- },
- 'highlightRepeat': false,
- 'highlight': { // false to disable highlighting
- 'start': '#FF0',
- 'end': false
- }
- }
- },
-
- 'Implements': [Options, Events, Chain],
-
- 'initialize': function(options){
- this.setOptions(options);
- this.createWrapper();
- return this;
- },
-
- 'bindAlert': function(){
- return this.alert.bind(this);
- },
-
- 'createWrapper': function(){
- this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
- if(this.options.mode == 'top')
- {
- this.wrapper.setStyle('top', 0);
- }
- else
- {
- this.wrapper.setStyle('bottom', 0);
- }
- document.id(document.body).grab(this.wrapper);
- this.positionWrapper(this.options.position);
- },
-
- 'positionWrapper': function(position){
- if(typeOf(position) == 'object')
- {
-
- var wrapperCoords = this.getWrapperCoords();
-
- this.wrapper.setStyles({
- 'bottom': '',
- 'left': position.x,
- 'top': position.y - wrapperCoords.height,
- 'position': 'absolute'
- });
- }
- else if(position == 'left')
- {
- this.wrapper.setStyle('left', 0);
- }
- else if(position == 'right')
- {
- this.wrapper.setStyle('right', 0);
- }
- else
- {
- this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
- }
- return this;
- },
-
- 'getWrapperCoords': function(){
- this.wrapper.setStyle('visibility', 'hidden');
- var measurer = this.alert('need something in here to measure');
- var coords = this.wrapper.getCoordinates();
- measurer.destroy();
- this.wrapper.setStyle('visibility','');
- return coords;
- },
-
- 'alert': function(msg, options){
-
- options = Object.merge({}, this.options.alert, options || {});
-
- var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
-
- if(typeOf(msg) == 'string')
- {
- alert.set('html', msg);
- }
- else if(typeOf(msg) == 'element')
- {
- alert.grab(msg);
- }
- else if(typeOf(msg) == 'array')
- {
- var alerts = [];
- msg.each(function(m){
- alerts.push(this.alert(m, options));
- }, this);
- return alerts;
- }
-
- alert.store('options', options);
-
- if(options.buttons.length > 0)
- {
- options.clickDismiss = false;
- options.hideAfter = false;
- options.hoverWait = false;
- var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
- alert.grab(buttonWrapper);
- options.buttons.each(function(button){
- if(button.text !== undefined)
- {
- var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
- callbackButton.set('html', button.text);
- if(button.callback !== undefined)
- {
- callbackButton.addEvent('click', button.callback.pass(alert));
- }
- if(button.dismiss !== undefined && button.dismiss)
- {
- callbackButton.addEvent('click', this.dismiss.pass(alert, this));
- }
- buttonWrapper.grab(callbackButton);
- }
- }, this);
- }
- if(options.className !== undefined)
- {
- alert.addClass(options.className);
- }
-
- this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
-
- var fx = Object.merge(this.options.alert.fx, options.fx);
- var alertFx = new Fx.Morph(alert, fx);
- alert.store('fx', alertFx);
- this.fadeIn(alert);
-
- if(options.highlight)
- {
- alertFx.addEvent('complete', function(){
- alert.highlight(options.highlight.start, options.highlight.end);
- if(options.highlightRepeat)
- {
- alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
- }
- });
- }
- if(options.hideAfter)
- {
- this.dismiss(alert);
- }
-
- if(options.clickDismiss)
- {
- alert.addEvent('click', function(){
- this.holdUp = false;
- this.dismiss(alert, true);
- }.bind(this));
- }
-
- if(options.hoverWait)
- {
- alert.addEvents({
- 'mouseenter': function(){
- this.holdUp = true;
- }.bind(this),
- 'mouseleave': function(){
- this.holdUp = false;
- }.bind(this)
- });
- }
-
- return alert;
- },
-
- 'fadeIn': function(alert){
- var alertFx = alert.retrieve('fx');
- alertFx.set({
- 'opacity': 0
- });
- alertFx.start({
- 'opacity': [this.options.elementOptions.alert.styles.opacity, '.9'].pick()
- });
- },
-
- 'dismiss': function(alert, now){
- now = now || false;
- var options = alert.retrieve('options');
- if(now)
- {
- this.fadeOut(alert);
- }
- else
- {
- this.fadeOut.delay(options.hideAfter, this, alert);
- }
- },
-
- 'fadeOut': function(alert){
- if(this.holdUp)
- {
- this.dismiss.delay(100, this, [alert, true]);
- return null;
- }
- var alertFx = alert.retrieve('fx');
- if(!alertFx)
- {
- return null;
- }
- var to = {
- 'opacity': 0
- };
- if(this.options.mode == 'top')
- {
- to['margin-top'] = '-'+alert.offsetHeight+'px';
- }
- else
- {
- to['margin-bottom'] = '-'+alert.offsetHeight+'px';
- }
- alertFx.start(to);
- alertFx.addEvent('complete', function(){
- alert.destroy();
- });
- }
-});
-
-Element.implement({
-
- 'alert': function(msg, options){
- var alert = this.retrieve('alert');
- if(!alert)
- {
- options = options || {
- 'mode':'top'
- };
- alert = new Purr(options);
- this.store('alert', alert);
- }
-
- var coords = this.getCoordinates();
-
- alert.alert(msg, options);
-
- alert.wrapper.setStyles({
- 'bottom': '',
- 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
- 'top': coords.top - (alert.wrapper.getHeight()),
- 'position': 'absolute'
- });
-
- }
-
-}); \ No newline at end of file
diff --git a/module/web/media/js/settings.coffee b/module/web/media/js/settings.coffee
deleted file mode 100644
index 9205233e3..000000000
--- a/module/web/media/js/settings.coffee
+++ /dev/null
@@ -1,107 +0,0 @@
-root = this
-
-window.addEvent 'domready', ->
- root.accountDialog = new MooDialog {destroyOnHide: false}
- root.accountDialog.setContent $ 'account_box'
-
- new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
-
- $$('ul.nav').each (nav) ->
- new MooDropMenu nav, {
- onOpen: (el) -> el.fade 'in'
- onClose: (el) -> el.fade 'out'
- onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
- }
-
- new SettingsUI()
-
-
-class SettingsUI
- constructor: ->
- @menu = $$ "#general-menu li"
- @menu.append $$ "#plugin-menu li"
-
- @name = $ "tabsback"
- @general = $ "general_form_content"
- @plugin = $ "plugin_form_content"
-
- el.addEvent 'click', @menuClick.bind(this) for el in @menu
-
- $("general|submit").addEvent "click", @configSubmit.bind(this)
- $("plugin|submit").addEvent "click", @configSubmit.bind(this)
-
- $("account_add").addEvent "click", (e) ->
- root.accountDialog.open()
- e.stop()
-
- $("account_reset").addEvent "click", (e) ->
- root.accountDialog.close()
-
- $("account_add_button").addEvent "click", @addAccount.bind(this)
- $("account_submit").addEvent "click", @submitAccounts.bind(this)
-
-
- menuClick: (e) ->
- [category, section] = e.target.get("id").split("|")
- name = e.target.get "text"
-
-
- target = if category is "general" then @general else @plugin
- target.dissolve()
-
- new Request({
- "method" : "get"
- "url" : "/json/load_config/#{category}/#{section}"
- "onSuccess": (data) =>
- target.set "html", data
- target.reveal()
- this.name.set "text", name
- }).send()
-
-
- configSubmit: (e) ->
- category = e.target.get("id").split("|")[0];
- form = $("#{category}_form");
-
- form.set "send", {
- "method": "post"
- "url": "/json/save_config/#{category}"
- "onSuccess" : ->
- root.notify.alert '{{ _("Settings saved.")}}', {
- 'className': 'success'
- }
- "onFailure": ->
- root.notify.alert '{{ _("Error occured.")}}', {
- 'className': 'error'
- }
- }
- form.send()
- e.stop()
-
- addAccount: (e) ->
- form = $ "add_account_form"
- form.set "send", {
- "method": "post"
- "onSuccess" : -> window.location.reload()
- "onFailure": ->
- root.notify.alert '{{_("Error occured.")}}', {
- 'className': 'error'
- }
- }
-
- form.send()
- e.stop()
-
- submitAccounts: (e) ->
- form = $ "account_form"
- form.set "send", {
- "method": "post",
- "onSuccess" : -> window.location.reload()
- "onFailure": ->
- root.notify.alert('{{ _("Error occured.") }}', {
- 'className': 'error'
- });
- }
-
- form.send()
- e.stop() \ No newline at end of file
diff --git a/module/web/media/js/settings.js b/module/web/media/js/settings.js
deleted file mode 100644
index 9191fac72..000000000
--- a/module/web/media/js/settings.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/tinytab_static.js b/module/web/media/js/tinytab_static.js
deleted file mode 100644
index 6c38292f5..000000000
--- a/module/web/media/js/tinytab_static.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
----
-description: TinyTab - Tiny and simple tab handler for Mootools.
-
-license: MIT-style
-
-authors:
-- Danillo César de O. Melo
-
-requires:
-- core/1.2.4: '*'
-
-provides: TinyTab
-
-...
-*/
-(function($) {
- this.TinyTab = new Class({
- Implements: Events,
- initialize: function(tabs, contents, opt) {
- this.tabs = tabs;
- this.contents = contents;
- this.header = $("tabsback");
- this.headers = [];
- for(var i =0; i < this.tabs.length; i++){
- this.headers.push("");
- }
- if(!opt) opt = {};
- this.css = opt.selectedClass || 'selected';
- this.select(this.tabs[0]);
- tabs.each(function(el){
- el.addEvent('click',function(e){
- this.select(el);
- e.stop();
- }.bind(this));
- }.bind(this));
- },
-
- select: function(el) {
- this.tabs.removeClass(this.css);
- el.addClass(this.css);
- this.contents.setStyle('display','none');
- var index = this.tabs.indexOf(el);
- this.header.set("text", this.headers[index]);
- var content = this.contents[index];
- content.setStyle('display','block');
- this.fireEvent('change',[content,el]);
- }
- });
-})(document.id); \ No newline at end of file
diff --git a/module/web/middlewares.py b/module/web/middlewares.py
index e0e6c3102..3cf49a8fc 100644
--- a/module/web/middlewares.py
+++ b/module/web/middlewares.py
@@ -31,9 +31,6 @@ class PrefixMiddleware(object):
# (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.
@@ -90,14 +87,15 @@ class GzipResponse(object):
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:
+ elif ct and (ct.startswith('text/') or ct.startswith('application/')) \
+ and 'zip' not in ct and 200 < cl < 1024*1024:
+ self.compressible = True
headers.append(('content-encoding', 'gzip'))
+ headers.append(('vary', 'Accept-Encoding'))
+
remove_header(headers, 'content-length')
self.headers = headers
self.status = status
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
index df4a4b3d4..8401e1778 100644
--- a/module/web/pyload_app.py
+++ b/module/web/pyload_app.py
@@ -16,74 +16,51 @@
@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 os.path import join
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 webinterface import PYLOAD, PROJECT_DIR, SETUP, env
-from filters import relpath, unquotepath
+from utils import render_to_response, login_required, set_session, get_user_api, is_mobile
-from module.utils import formatSize, save_join, fs_encode, fs_decode
+##########
# Helper
+##########
+# TODO: useful but needs a rewrite, too
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
+ api = get_user_api(s)
+ user = None
+ status = None
+ if api is not None:
+ user = api.user
+ status = api.statusServer()
return {"user": user,
- 'status': status,
- 'captcha': captcha,
- 'perms': perms,
- 'url': request.url,
- 'update': update,
- 'plugins': plugins}
+ 'server': status,
+ 'url': request.url }
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."
+ print "An error occurred while processing the request."
if error.traceback:
print error.traceback
- return base(["An Error occured, please enable debug mode to get more details.", error,
+ return base(["An error occurred while processing the request.", error,
error.traceback.replace("\n", "<br>") if error.traceback else "No Traceback"])
-# render js
-@route("/media/js/<path:re:.+\.js>")
+# TODO: not working
+# @route("/static/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))
@@ -92,442 +69,80 @@ def js_dynamic(path):
try:
# static files are not rendered
- if "static" not in path and "mootools" not in path:
+ if "static" not in path:
t = env.get_template("js/%s" % path)
return t.render()
else:
- return static_file(path, root=join(PROJECT_DIR, "media", "js"))
+ return static_file(path, root=join(PROJECT_DIR, "static", "js"))
except:
return HTTPError(404, "Not Found")
-@route('/media/<path:path>')
+@route('/static/<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"))
+ return static_file(path, root=join(PROJECT_DIR, "static"))
+
+@route('/templates/<path:path>')
+def serve_template(path):
+ """ Serve backbone templates """
+ args = path.split("/")
+ args.insert(1, "backbone")
+ return static_file("/".join(args), root=join(PROJECT_DIR, "templates"))
@route('/favicon.ico')
def favicon():
- return static_file("favicon.ico", root=join(PROJECT_DIR, "media", "img"))
+ return static_file("favicon.ico", root=join(PROJECT_DIR, "static", "img"))
+
+
+##########
+# Views
+##########
@route('/login', method="GET")
def login():
+ # set mobilecookie to reduce is_mobile check-time
+ response.set_cookie("mobile", str(is_mobile()))
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.")])
+ return base([_("You don't have permission to access this page.")])
@route("/login", method="POST")
def login_post():
- user = request.forms.get("username")
+ username = request.forms.get("username")
password = request.forms.get("password")
-
- info = PYLOAD.checkAuth(user, password)
-
- if not info:
+ user = PYLOAD.checkAuth(username, password)
+ if not user:
return render_to_response("login.html", {"errors": True}, [pre_processor])
-
- set_session(request, info)
+ set_session(request, user)
return redirect("/")
+@route("/toggle_mobile")
+def toggle_mobile():
+ response.set_cookie("mobile", str(not is_mobile()))
+ return redirect("/")
@route("/logout")
def logout():
s = request.environ.get('beaker.session')
s.delete()
- return render_to_response("logout.html", proc=[pre_processor])
-
+ return render_to_response("login.html", {"logout": True}, 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.")
-
-
+@login_required()
+def index(api):
+ return render_to_response("dashboard.html", proc=[pre_processor])
@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])
-
+@login_required()
+def settings(api):
+ return render_to_response("settings.html", proc=[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/static/css/bootstrap.css b/module/web/static/css/bootstrap.css
new file mode 100644
index 000000000..9fa6f766f
--- /dev/null
+++ b/module/web/static/css/bootstrap.css
@@ -0,0 +1,5774 @@
+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+audio:not([controls]) {
+ display: none;
+}
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+ outline: 0;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+img {
+ width: auto\9;
+ height: auto;
+ max-width: 100%;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img {
+ max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+
+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-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+a {
+ color: #0088cc;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+
+.img-rounded {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+ -webkit-border-radius: 500px;
+ -moz-border-radius: 500px;
+ border-radius: 500px;
+}
+
+.row {
+ margin-left: -20px;
+ *zoom: 1;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row:after {
+ clear: both;
+}
+
+[class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.span12 {
+ width: 940px;
+}
+
+.span11 {
+ width: 860px;
+}
+
+.span10 {
+ width: 780px;
+}
+
+.span9 {
+ width: 700px;
+}
+
+.span8 {
+ width: 620px;
+}
+
+.span7 {
+ width: 540px;
+}
+
+.span6 {
+ width: 460px;
+}
+
+.span5 {
+ width: 380px;
+}
+
+.span4 {
+ width: 300px;
+}
+
+.span3 {
+ width: 220px;
+}
+
+.span2 {
+ width: 140px;
+}
+
+.span1 {
+ width: 60px;
+}
+
+.offset12 {
+ margin-left: 980px;
+}
+
+.offset11 {
+ margin-left: 900px;
+}
+
+.offset10 {
+ margin-left: 820px;
+}
+
+.offset9 {
+ margin-left: 740px;
+}
+
+.offset8 {
+ margin-left: 660px;
+}
+
+.offset7 {
+ margin-left: 580px;
+}
+
+.offset6 {
+ margin-left: 500px;
+}
+
+.offset5 {
+ margin-left: 420px;
+}
+
+.offset4 {
+ margin-left: 340px;
+}
+
+.offset3 {
+ margin-left: 260px;
+}
+
+.offset2 {
+ margin-left: 180px;
+}
+
+.offset1 {
+ margin-left: 100px;
+}
+
+.row-fluid {
+ width: 100%;
+ *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row-fluid:after {
+ clear: both;
+}
+
+.row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.127659574468085%;
+ *margin-left: 2.074468085106383%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+}
+
+.row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+ width: 91.48936170212765%;
+ *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+ width: 82.97872340425532%;
+ *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+ width: 74.46808510638297%;
+ *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+ width: 65.95744680851064%;
+ *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+ width: 57.44680851063829%;
+ *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+ width: 48.93617021276595%;
+ *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+ width: 40.42553191489362%;
+ *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+ width: 31.914893617021278%;
+ *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+ width: 23.404255319148934%;
+ *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+ width: 14.893617021276595%;
+ *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+ width: 6.382978723404255%;
+ *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+ margin-left: 104.25531914893617%;
+ *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+ margin-left: 102.12765957446808%;
+ *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+ margin-left: 95.74468085106382%;
+ *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+ margin-left: 93.61702127659574%;
+ *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+ margin-left: 87.23404255319149%;
+ *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+ margin-left: 85.1063829787234%;
+ *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+ margin-left: 78.72340425531914%;
+ *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+ margin-left: 76.59574468085106%;
+ *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+ margin-left: 70.2127659574468%;
+ *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+ margin-left: 68.08510638297872%;
+ *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+ margin-left: 61.70212765957446%;
+ *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+ margin-left: 59.574468085106375%;
+ *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+ margin-left: 53.191489361702125%;
+ *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+ margin-left: 51.063829787234035%;
+ *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+ margin-left: 44.68085106382979%;
+ *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+ margin-left: 42.5531914893617%;
+ *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+ margin-left: 36.170212765957444%;
+ *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+ margin-left: 34.04255319148936%;
+ *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+ margin-left: 27.659574468085104%;
+ *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+ margin-left: 25.53191489361702%;
+ *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+ margin-left: 19.148936170212764%;
+ *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+ margin-left: 17.02127659574468%;
+ *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+ margin-left: 10.638297872340425%;
+ *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+ margin-left: 8.51063829787234%;
+ *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
+
+.container {
+ margin-right: auto;
+ margin-left: auto;
+ *zoom: 1;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container:after {
+ clear: both;
+}
+
+.container-fluid {
+ padding-right: 20px;
+ padding-left: 20px;
+ *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container-fluid:after {
+ clear: both;
+}
+
+p {
+ margin: 0 0 10px;
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 30px;
+}
+
+small {
+ font-size: 85%;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+cite {
+ font-style: normal;
+}
+
+.muted {
+ color: #999999;
+}
+
+.text-warning {
+ color: #c09853;
+}
+
+.text-error {
+ color: #b94a48;
+}
+
+.text-info {
+ color: #3a87ad;
+}
+
+.text-success {
+ color: #468847;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 10px 0;
+ font-family: inherit;
+ font-weight: bold;
+ line-height: 1;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999999;
+}
+
+h1 {
+ font-size: 36px;
+ line-height: 40px;
+}
+
+h2 {
+ font-size: 30px;
+ line-height: 40px;
+}
+
+h3 {
+ font-size: 24px;
+ line-height: 40px;
+}
+
+h4 {
+ font-size: 18px;
+ line-height: 20px;
+}
+
+h5 {
+ font-size: 14px;
+ line-height: 20px;
+}
+
+h6 {
+ font-size: 12px;
+ line-height: 20px;
+}
+
+h1 small {
+ font-size: 24px;
+}
+
+h2 small {
+ font-size: 18px;
+}
+
+h3 small {
+ font-size: 14px;
+}
+
+h4 small {
+ font-size: 14px;
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 20px 0 30px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+
+li {
+ line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+dl {
+ margin-bottom: 20px;
+}
+
+dt,
+dd {
+ line-height: 20px;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 10px;
+}
+
+.dl-horizontal {
+ *zoom: 1;
+}
+
+.dl-horizontal:before,
+.dl-horizontal:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.dl-horizontal:after {
+ clear: both;
+}
+
+.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;
+}
+
+hr {
+ margin: 20px 0;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+ border-bottom: 1px solid #ffffff;
+}
+
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+ margin-bottom: 0;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 25px;
+}
+
+blockquote small {
+ display: block;
+ line-height: 20px;
+ color: #999999;
+}
+
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eeeeee;
+ border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+
+blockquote.pull-right small:before {
+ content: '';
+}
+
+blockquote.pull-right small:after {
+ content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+address {
+ display: block;
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 20px;
+}
+
+code,
+pre {
+ padding: 0 3px 2px;
+ font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+ font-size: 12px;
+ color: #333333;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 20px;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+pre.prettyprint {
+ margin-bottom: 20px;
+}
+
+pre code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+}
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+
+form {
+ margin: 0 0 20px;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: 40px;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+ font-size: 15px;
+ color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ display: inline-block;
+ height: 20px;
+ padding: 4px 6px;
+ margin-bottom: 9px;
+ font-size: 14px;
+ line-height: 20px;
+ color: #555555;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+input,
+textarea,
+.uneditable-input {
+ width: 206px;
+}
+
+textarea {
+ height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \9;
+ /* IE6-9 */
+
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ *margin-top: 0;
+ line-height: normal;
+ cursor: pointer;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto;
+}
+
+select,
+input[type="file"] {
+ height: 30px;
+ /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+ *margin-top: 4px;
+ /* For IE7, add top margin to align select with labels */
+
+ line-height: 30px;
+}
+
+select {
+ width: 220px;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+}
+
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #fcfcfc;
+ border-color: #cccccc;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+ color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+ color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.radio,
+.checkbox {
+ min-height: 18px;
+ padding-left: 18px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -18px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px;
+}
+
+.input-mini {
+ width: 60px;
+}
+
+.input-small {
+ width: 90px;
+}
+
+.input-medium {
+ width: 150px;
+}
+
+.input-large {
+ width: 210px;
+}
+
+.input-xlarge {
+ width: 270px;
+}
+
+.input-xxlarge {
+ width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+ float: none;
+ margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+ display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+ margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+ width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+ width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+ width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+ width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+ width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+ width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+ width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+ width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+ width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+ width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+ width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+ width: 46px;
+}
+
+.controls-row {
+ *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.controls-row:after {
+ clear: both;
+}
+
+.controls-row [class*="span"] {
+ float: left;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+.control-group.warning > label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+ color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ color: #c09853;
+}
+
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ border-color: #c09853;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+
+.control-group.error > label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+ color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ color: #b94a48;
+}
+
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ border-color: #b94a48;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+
+.control-group.success > label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+ color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ color: #468847;
+}
+
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ border-color: #468847;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+ border-color: #356635;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+
+.control-group.info > label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+ color: #3a87ad;
+}
+
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ color: #3a87ad;
+}
+
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ border-color: #3a87ad;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+ border-color: #2d6987;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #3a87ad;
+}
+
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+}
+
+input:focus:required:invalid:focus,
+textarea:focus:required:invalid:focus,
+select:focus:required:invalid:focus {
+ border-color: #e9322d;
+ -webkit-box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+ padding: 19px 20px 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #e5e5e5;
+ *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-actions:after {
+ clear: both;
+}
+
+.help-block,
+.help-inline {
+ color: #595959;
+}
+
+.help-block {
+ display: block;
+ margin-bottom: 10px;
+}
+
+.help-inline {
+ display: inline-block;
+ *display: inline;
+ padding-left: 5px;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+ margin-bottom: 5px;
+ font-size: 0;
+ white-space: nowrap;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+ position: relative;
+ margin-bottom: 0;
+ *margin-left: 0;
+ font-size: 14px;
+ vertical-align: top;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+ z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+ display: inline-block;
+ width: auto;
+ height: 20px;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #eeeeee;
+ border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn {
+ vertical-align: top;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+ margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append .add-on,
+.input-append .btn {
+ margin-left: -1px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+ margin-right: -1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9;
+ /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+ margin-bottom: 0;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+ display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-right: 3px;
+ margin-left: 0;
+}
+
+.control-group {
+ margin-bottom: 10px;
+}
+
+legend + .control-group {
+ margin-top: 20px;
+ -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+ margin-bottom: 20px;
+ *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-horizontal .control-group:after {
+ clear: both;
+}
+
+.form-horizontal .control-label {
+ float: left;
+ width: 160px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+.form-horizontal .controls {
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: 180px;
+ *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+ *padding-left: 180px;
+}
+
+.form-horizontal .help-block {
+ margin-bottom: 0;
+}
+
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block {
+ margin-top: 10px;
+}
+
+.form-horizontal .form-actions {
+ padding-left: 180px;
+}
+
+table {
+ max-width: 100%;
+ background-color: transparent;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+ padding: 8px;
+ line-height: 20px;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+
+.table th {
+ font-weight: bold;
+}
+
+.table thead th {
+ vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+
+.table-condensed th,
+.table-condensed td {
+ padding: 4px 5px;
+}
+
+.table-bordered {
+ border: 1px solid #dddddd;
+ border-collapse: separate;
+ *border-collapse: collapse;
+ border-left: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+ border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child th:first-child,
+.table-bordered tbody:first-child tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child th:last-child,
+.table-bordered tbody:first-child tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:first-child,
+.table-bordered tbody:last-child tr:last-child td:first-child,
+.table-bordered tfoot:last-child tr:last-child td:first-child {
+ -webkit-border-radius: 0 0 0 4px;
+ -moz-border-radius: 0 0 0 4px;
+ border-radius: 0 0 0 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:last-child,
+.table-bordered tbody:last-child tr:last-child td:last-child,
+.table-bordered tfoot:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-striped tbody tr:nth-child(odd) td,
+.table-striped tbody tr:nth-child(odd) th {
+ background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover td,
+.table-hover tbody tr:hover th {
+ background-color: #f5f5f5;
+}
+
+table [class*=span],
+.row-fluid table [class*=span] {
+ display: table-cell;
+ float: none;
+ margin-left: 0;
+}
+
+.table .span1 {
+ float: none;
+ width: 44px;
+ margin-left: 0;
+}
+
+.table .span2 {
+ float: none;
+ width: 124px;
+ margin-left: 0;
+}
+
+.table .span3 {
+ float: none;
+ width: 204px;
+ margin-left: 0;
+}
+
+.table .span4 {
+ float: none;
+ width: 284px;
+ margin-left: 0;
+}
+
+.table .span5 {
+ float: none;
+ width: 364px;
+ margin-left: 0;
+}
+
+.table .span6 {
+ float: none;
+ width: 444px;
+ margin-left: 0;
+}
+
+.table .span7 {
+ float: none;
+ width: 524px;
+ margin-left: 0;
+}
+
+.table .span8 {
+ float: none;
+ width: 604px;
+ margin-left: 0;
+}
+
+.table .span9 {
+ float: none;
+ width: 684px;
+ margin-left: 0;
+}
+
+.table .span10 {
+ float: none;
+ width: 764px;
+ margin-left: 0;
+}
+
+.table .span11 {
+ float: none;
+ width: 844px;
+ margin-left: 0;
+}
+
+.table .span12 {
+ float: none;
+ width: 924px;
+ margin-left: 0;
+}
+
+.table .span13 {
+ float: none;
+ width: 1004px;
+ margin-left: 0;
+}
+
+.table .span14 {
+ float: none;
+ width: 1084px;
+ margin-left: 0;
+}
+
+.table .span15 {
+ float: none;
+ width: 1164px;
+ margin-left: 0;
+}
+
+.table .span16 {
+ float: none;
+ width: 1244px;
+ margin-left: 0;
+}
+
+.table .span17 {
+ float: none;
+ width: 1324px;
+ margin-left: 0;
+}
+
+.table .span18 {
+ float: none;
+ width: 1404px;
+ margin-left: 0;
+}
+
+.table .span19 {
+ float: none;
+ width: 1484px;
+ margin-left: 0;
+}
+
+.table .span20 {
+ float: none;
+ width: 1564px;
+ margin-left: 0;
+}
+
+.table .span21 {
+ float: none;
+ width: 1644px;
+ margin-left: 0;
+}
+
+.table .span22 {
+ float: none;
+ width: 1724px;
+ margin-left: 0;
+}
+
+.table .span23 {
+ float: none;
+ width: 1804px;
+ margin-left: 0;
+}
+
+.table .span24 {
+ float: none;
+ width: 1884px;
+ margin-left: 0;
+}
+
+.table tbody tr.success td {
+ background-color: #dff0d8;
+}
+
+.table tbody tr.error td {
+ background-color: #f2dede;
+}
+
+.table tbody tr.warning td {
+ background-color: #fcf8e3;
+}
+
+.table tbody tr.info td {
+ background-color: #d9edf7;
+}
+
+.table-hover tbody tr.success:hover td {
+ background-color: #d0e9c6;
+}
+
+.table-hover tbody tr.error:hover td {
+ background-color: #ebcccc;
+}
+
+.table-hover tbody tr.warning:hover td {
+ background-color: #faf2cc;
+}
+
+.table-hover tbody tr.info:hover td {
+ background-color: #c4e3f3;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin-top: 1px;
+ *margin-right: .3em;
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("../img/glyphicons-halflings.png");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+
+.icon-white,
+.nav-tabs > .active > a > [class^="icon-"],
+.nav-tabs > .active > a > [class*=" icon-"],
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"] {
+ background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+ background-position: 0 0;
+}
+
+.icon-music {
+ background-position: -24px 0;
+}
+
+.icon-search {
+ background-position: -48px 0;
+}
+
+.icon-envelope {
+ background-position: -72px 0;
+}
+
+.icon-heart {
+ background-position: -96px 0;
+}
+
+.icon-star {
+ background-position: -120px 0;
+}
+
+.icon-star-empty {
+ background-position: -144px 0;
+}
+
+.icon-user {
+ background-position: -168px 0;
+}
+
+.icon-film {
+ background-position: -192px 0;
+}
+
+.icon-th-large {
+ background-position: -216px 0;
+}
+
+.icon-th {
+ background-position: -240px 0;
+}
+
+.icon-th-list {
+ background-position: -264px 0;
+}
+
+.icon-ok {
+ background-position: -288px 0;
+}
+
+.icon-remove {
+ background-position: -312px 0;
+}
+
+.icon-zoom-in {
+ background-position: -336px 0;
+}
+
+.icon-zoom-out {
+ background-position: -360px 0;
+}
+
+.icon-off {
+ background-position: -384px 0;
+}
+
+.icon-signal {
+ background-position: -408px 0;
+}
+
+.icon-cog {
+ background-position: -432px 0;
+}
+
+.icon-trash {
+ background-position: -456px 0;
+}
+
+.icon-home {
+ background-position: 0 -24px;
+}
+
+.icon-file {
+ background-position: -24px -24px;
+}
+
+.icon-time {
+ background-position: -48px -24px;
+}
+
+.icon-road {
+ background-position: -72px -24px;
+}
+
+.icon-download-alt {
+ background-position: -96px -24px;
+}
+
+.icon-download {
+ background-position: -120px -24px;
+}
+
+.icon-upload {
+ background-position: -144px -24px;
+}
+
+.icon-inbox {
+ background-position: -168px -24px;
+}
+
+.icon-play-circle {
+ background-position: -192px -24px;
+}
+
+.icon-repeat {
+ background-position: -216px -24px;
+}
+
+.icon-refresh {
+ background-position: -240px -24px;
+}
+
+.icon-list-alt {
+ background-position: -264px -24px;
+}
+
+.icon-lock {
+ background-position: -287px -24px;
+}
+
+.icon-flag {
+ background-position: -312px -24px;
+}
+
+.icon-headphones {
+ background-position: -336px -24px;
+}
+
+.icon-volume-off {
+ background-position: -360px -24px;
+}
+
+.icon-volume-down {
+ background-position: -384px -24px;
+}
+
+.icon-volume-up {
+ background-position: -408px -24px;
+}
+
+.icon-qrcode {
+ background-position: -432px -24px;
+}
+
+.icon-barcode {
+ background-position: -456px -24px;
+}
+
+.icon-tag {
+ background-position: 0 -48px;
+}
+
+.icon-tags {
+ background-position: -25px -48px;
+}
+
+.icon-book {
+ background-position: -48px -48px;
+}
+
+.icon-bookmark {
+ background-position: -72px -48px;
+}
+
+.icon-print {
+ background-position: -96px -48px;
+}
+
+.icon-camera {
+ background-position: -120px -48px;
+}
+
+.icon-font {
+ background-position: -144px -48px;
+}
+
+.icon-bold {
+ background-position: -167px -48px;
+}
+
+.icon-italic {
+ background-position: -192px -48px;
+}
+
+.icon-text-height {
+ background-position: -216px -48px;
+}
+
+.icon-text-width {
+ background-position: -240px -48px;
+}
+
+.icon-align-left {
+ background-position: -264px -48px;
+}
+
+.icon-align-center {
+ background-position: -288px -48px;
+}
+
+.icon-align-right {
+ background-position: -312px -48px;
+}
+
+.icon-align-justify {
+ background-position: -336px -48px;
+}
+
+.icon-list {
+ background-position: -360px -48px;
+}
+
+.icon-indent-left {
+ background-position: -384px -48px;
+}
+
+.icon-indent-right {
+ background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+ background-position: -432px -48px;
+}
+
+.icon-picture {
+ background-position: -456px -48px;
+}
+
+.icon-pencil {
+ background-position: 0 -72px;
+}
+
+.icon-map-marker {
+ background-position: -24px -72px;
+}
+
+.icon-adjust {
+ background-position: -48px -72px;
+}
+
+.icon-tint {
+ background-position: -72px -72px;
+}
+
+.icon-edit {
+ background-position: -96px -72px;
+}
+
+.icon-share {
+ background-position: -120px -72px;
+}
+
+.icon-check {
+ background-position: -144px -72px;
+}
+
+.icon-move {
+ background-position: -168px -72px;
+}
+
+.icon-step-backward {
+ background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+ background-position: -216px -72px;
+}
+
+.icon-backward {
+ background-position: -240px -72px;
+}
+
+.icon-play {
+ background-position: -264px -72px;
+}
+
+.icon-pause {
+ background-position: -288px -72px;
+}
+
+.icon-stop {
+ background-position: -312px -72px;
+}
+
+.icon-forward {
+ background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+ background-position: -360px -72px;
+}
+
+.icon-step-forward {
+ background-position: -384px -72px;
+}
+
+.icon-eject {
+ background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+ background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+ background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+ background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+ background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+ background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+ background-position: -72px -96px;
+}
+
+.icon-question-sign {
+ background-position: -96px -96px;
+}
+
+.icon-info-sign {
+ background-position: -120px -96px;
+}
+
+.icon-screenshot {
+ background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+ background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+ background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+ background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+ background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+ background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+ background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+ background-position: -312px -96px;
+}
+
+.icon-share-alt {
+ background-position: -336px -96px;
+}
+
+.icon-resize-full {
+ background-position: -360px -96px;
+}
+
+.icon-resize-small {
+ background-position: -384px -96px;
+}
+
+.icon-plus {
+ background-position: -408px -96px;
+}
+
+.icon-minus {
+ background-position: -433px -96px;
+}
+
+.icon-asterisk {
+ background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+ background-position: 0 -120px;
+}
+
+.icon-gift {
+ background-position: -24px -120px;
+}
+
+.icon-leaf {
+ background-position: -48px -120px;
+}
+
+.icon-fire {
+ background-position: -72px -120px;
+}
+
+.icon-eye-open {
+ background-position: -96px -120px;
+}
+
+.icon-eye-close {
+ background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+ background-position: -144px -120px;
+}
+
+.icon-plane {
+ background-position: -168px -120px;
+}
+
+.icon-calendar {
+ background-position: -192px -120px;
+}
+
+.icon-random {
+ width: 16px;
+ background-position: -216px -120px;
+}
+
+.icon-comment {
+ background-position: -240px -120px;
+}
+
+.icon-magnet {
+ background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+ background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+ background-position: -313px -119px;
+}
+
+.icon-retweet {
+ background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+ background-position: -360px -120px;
+}
+
+.icon-folder-close {
+ background-position: -384px -120px;
+}
+
+.icon-folder-open {
+ width: 16px;
+ background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+ background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+ background-position: -456px -118px;
+}
+
+.icon-hdd {
+ background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+ background-position: -24px -144px;
+}
+
+.icon-bell {
+ background-position: -48px -144px;
+}
+
+.icon-certificate {
+ background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+ background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+ background-position: -120px -144px;
+}
+
+.icon-hand-right {
+ background-position: -144px -144px;
+}
+
+.icon-hand-left {
+ background-position: -168px -144px;
+}
+
+.icon-hand-up {
+ background-position: -192px -144px;
+}
+
+.icon-hand-down {
+ background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+ background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+ background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+ background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+ background-position: -312px -144px;
+}
+
+.icon-globe {
+ background-position: -336px -144px;
+}
+
+.icon-wrench {
+ background-position: -360px -144px;
+}
+
+.icon-tasks {
+ background-position: -384px -144px;
+}
+
+.icon-filter {
+ background-position: -408px -144px;
+}
+
+.icon-briefcase {
+ background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+ background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+ position: relative;
+}
+
+.dropdown-toggle {
+ *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid #000000;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+.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;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.dropdown-menu .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 20px;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ outline: 0;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+ color: #999999;
+}
+
+.dropdown-menu .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.open {
+ *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+ display: block;
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid #000000;
+ content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+ position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+ display: block;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ float: right;
+ width: 0;
+ height: 0;
+ margin-top: 5px;
+ margin-right: -10px;
+ border-color: transparent;
+ border-left-color: #cccccc;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+ border-left-color: #ffffff;
+}
+
+.dropdown .dropdown-menu .nav-header {
+ padding-right: 20px;
+ padding-left: 20px;
+}
+
+.typeahead {
+ margin-top: 2px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+ padding: 24px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.well-small {
+ padding: 9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+}
+
+.fade.in {
+ opacity: 1;
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+}
+
+.collapse.in {
+ height: auto;
+}
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+
+.close:hover {
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+}
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
+
+.btn {
+ display: inline-block;
+ *display: inline;
+ padding: 4px 14px;
+ margin-bottom: 0;
+ *margin-left: .3em;
+ font-size: 14px;
+ line-height: 20px;
+ *line-height: 20px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: #f5f5f5;
+ *background-color: #e6e6e6;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border: 1px solid #bbbbbb;
+ *border: 0;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-bottom-color: #a2a2a2;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+ color: #333333;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+ background-color: #cccccc \9;
+}
+
+.btn:first-child {
+ *margin-left: 0;
+}
+
+.btn:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+ /* Buttons in IE7 don't get borders, so darken on hover */
+
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+ cursor: default;
+ background-color: #e6e6e6;
+ background-image: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-large {
+ padding: 9px 14px;
+ font-size: 16px;
+ line-height: normal;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.btn-large [class^="icon-"] {
+ margin-top: 2px;
+}
+
+.btn-small {
+ padding: 3px 9px;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.btn-small [class^="icon-"] {
+ margin-top: 0;
+}
+
+.btn-mini {
+ padding: 2px 6px;
+ font-size: 11px;
+ line-height: 17px;
+}
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.btn {
+ border-color: #c5c5c5;
+ border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
+}
+
+.btn-primary {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #006dcc;
+ *background-color: #0044cc;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #003399 \9;
+}
+
+.btn-warning {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #faa732;
+ *background-color: #f89406;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ border-color: #f89406 #f89406 #ad6704;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+ color: #ffffff;
+ background-color: #f89406;
+ *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #c67605 \9;
+}
+
+.btn-danger {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #da4f49;
+ *background-color: #bd362f;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+ background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+ background-repeat: repeat-x;
+ border-color: #bd362f #bd362f #802420;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+ color: #ffffff;
+ background-color: #bd362f;
+ *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #942a25 \9;
+}
+
+.btn-success {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #5bb75b;
+ *background-color: #51a351;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(to bottom, #62c462, #51a351);
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-repeat: repeat-x;
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+ color: #ffffff;
+ background-color: #51a351;
+ *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+ background-color: #408140 \9;
+}
+
+.btn-info {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #49afcd;
+ *background-color: #2f96b4;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+ background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+ background-repeat: repeat-x;
+ border-color: #2f96b4 #2f96b4 #1f6377;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+ color: #ffffff;
+ background-color: #2f96b4;
+ *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+ background-color: #24748c \9;
+}
+
+.btn-inverse {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #363636;
+ *background-color: #222222;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+ background-image: -webkit-linear-gradient(top, #444444, #222222);
+ background-image: -o-linear-gradient(top, #444444, #222222);
+ background-image: linear-gradient(to bottom, #444444, #222222);
+ background-image: -moz-linear-gradient(top, #444444, #222222);
+ background-repeat: repeat-x;
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+ color: #ffffff;
+ background-color: #222222;
+ *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+ background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+ background-color: transparent;
+ background-image: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-link {
+ color: #0088cc;
+ cursor: pointer;
+ border-color: transparent;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-link:hover {
+ color: #005580;
+ text-decoration: underline;
+ background-color: transparent;
+}
+
+.btn-link[disabled]:hover {
+ color: #333333;
+ text-decoration: none;
+}
+
+.btn-group {
+ position: relative;
+ *margin-left: .3em;
+ font-size: 0;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.btn-group:first-child {
+ *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-toolbar {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 0;
+}
+
+.btn-toolbar .btn-group {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-toolbar .btn + .btn,
+.btn-toolbar .btn-group + .btn,
+.btn-toolbar .btn + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-group > .btn {
+ position: relative;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu {
+ font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+ font-size: 11px;
+}
+
+.btn-group > .btn-small {
+ font-size: 12px;
+}
+
+.btn-group > .btn-large {
+ font-size: 16px;
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ -webkit-border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ *padding-top: 5px;
+ padding-right: 8px;
+ *padding-bottom: 5px;
+ padding-left: 8px;
+ -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+ *padding-top: 2px;
+ padding-right: 5px;
+ *padding-bottom: 2px;
+ padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+ *padding-top: 7px;
+ padding-right: 12px;
+ *padding-bottom: 7px;
+ padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+ background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+ background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+ background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+ background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+ background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+ background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+ background-color: #222222;
+}
+
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+ margin-top: 6px;
+}
+
+.btn-large .caret {
+ border-top-width: 5px;
+ border-right-width: 5px;
+ border-left-width: 5px;
+}
+
+.dropup .btn-large .caret {
+ border-top: 0;
+ border-bottom: 5px solid #000000;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-group-vertical .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group-vertical .btn + .btn {
+ margin-top: -1px;
+ margin-left: 0;
+}
+
+.btn-group-vertical .btn:first-child {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical .btn:last-child {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical .btn-large:first-child {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical .btn-large:last-child {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 20px;
+ color: #c09853;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #fcf8e3;
+ border: 1px solid #fbeed5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.alert h4 {
+ margin: 0;
+}
+
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: 20px;
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.alert-danger,
+.alert-error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.alert-info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+
+.alert-block p + p {
+ margin-top: 5px;
+}
+
+.nav {
+ margin-bottom: 20px;
+ margin-left: 0;
+ list-style: none;
+}
+
+.nav > li > a {
+ display: block;
+}
+
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.nav > .pull-right {
+ float: right;
+}
+
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #999999;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+.nav-list {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-right: -15px;
+ margin-left: -15px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"] {
+ margin-right: 2px;
+}
+
+.nav-list .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+ *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+ clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 20px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: #555555;
+ cursor: default;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: #ffffff;
+ background-color: #0088cc;
+}
+
+.nav-stacked > li {
+ float: none;
+}
+
+.nav-stacked > li > a {
+ margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover {
+ z-index: 2;
+ border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+ margin-top: 6px;
+ border-top-color: #0088cc;
+ border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret {
+ border-top-color: #005580;
+ border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover {
+ cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+ color: #ffffff;
+ background-color: #999999;
+ border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover {
+ border-color: #999999;
+}
+
+.tabbable {
+ *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tab-content {
+ overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+.tabs-below > .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover {
+ border-top-color: #ddd;
+ border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+ float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+ color: #999999;
+}
+
+.nav > .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.navbar {
+ *position: relative;
+ *z-index: 2;
+ margin-bottom: 20px;
+ overflow: visible;
+ color: #777777;
+}
+
+.navbar-inner {
+ min-height: 40px;
+ padding-right: 20px;
+ padding-left: 20px;
+ background-color: #fafafa;
+ background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+ background-repeat: repeat-x;
+ border: 1px solid #d4d4d4;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar-inner:before,
+.navbar-inner:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-inner:after {
+ clear: both;
+}
+
+.navbar .container {
+ width: auto;
+}
+
+.nav-collapse.collapse {
+ height: auto;
+}
+
+.navbar .brand {
+ display: block;
+ float: left;
+ padding: 10px 20px 10px;
+ margin-left: -20px;
+ font-size: 20px;
+ font-weight: 200;
+ color: #777777;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover {
+ text-decoration: none;
+}
+
+.navbar-text {
+ margin-bottom: 0;
+ line-height: 40px;
+}
+
+.navbar-link {
+ color: #777777;
+}
+
+.navbar-link:hover {
+ color: #333333;
+}
+
+.navbar .divider-vertical {
+ height: 40px;
+ margin: 0 9px;
+ border-right: 1px solid #ffffff;
+ border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+ margin-top: 5px;
+}
+
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn {
+ margin-top: 0;
+}
+
+.navbar-form {
+ margin-bottom: 0;
+ *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-form:after {
+ clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+ margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+ display: inline-block;
+ margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+ margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+ margin-top: 0;
+}
+
+.navbar-search {
+ position: relative;
+ float: left;
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+ padding: 4px 14px;
+ margin-bottom: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.navbar-static-top {
+ position: static;
+ width: 100%;
+ margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ border-width: 1px 0 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.navbar-fixed-top {
+ top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+ float: right;
+ margin-right: 0;
+}
+
+.navbar .nav > li {
+ float: left;
+}
+
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 15px 10px;
+ color: #777777;
+ text-decoration: none;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: #555555;
+ text-decoration: none;
+ background-color: #e5e5e5;
+ -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-right: 5px;
+ margin-left: 5px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #ededed;
+ *background-color: #e5e5e5;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+ background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+ background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-repeat: repeat-x;
+ border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #e5e5e5;
+ *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+ background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+ top: auto;
+ bottom: -7px;
+ border-top: 7px solid #ccc;
+ border-bottom: 0;
+ border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+ top: auto;
+ bottom: -6px;
+ border-top: 6px solid #ffffff;
+ border-bottom: 0;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #555555;
+ background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #777777;
+ border-bottom-color: #777777;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+ right: 12px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+ right: 13px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+ right: 100%;
+ left: auto;
+ margin-right: -1px;
+ margin-left: 0;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-inner {
+ background-color: #1b1b1b;
+ background-image: -moz-linear-gradient(top, #222222, #111111);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+ background-image: -webkit-linear-gradient(top, #222222, #111111);
+ background-image: -o-linear-gradient(top, #222222, #111111);
+ background-image: linear-gradient(to bottom, #222222, #111111);
+ background-repeat: repeat-x;
+ border-color: #252525;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+ color: #999999;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+ border-right-color: #222222;
+ border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #999999;
+ border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+ color: #ffffff;
+ background-color: #515151;
+ border-color: #111111;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+ padding: 5px 15px;
+ color: #333333;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #ffffff;
+ border: 0;
+ outline: 0;
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e0e0e;
+ *background-color: #040404;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+ background-image: -webkit-linear-gradient(top, #151515, #040404);
+ background-image: -o-linear-gradient(top, #151515, #040404);
+ background-image: linear-gradient(to bottom, #151515, #040404);
+ background-image: -moz-linear-gradient(top, #151515, #040404);
+ background-repeat: repeat-x;
+ border-color: #040404 #040404 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #040404;
+ *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+ background-color: #000000 \9;
+}
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.breadcrumb li {
+ display: inline-block;
+ *display: inline;
+ text-shadow: 0 1px 0 #ffffff;
+ *zoom: 1;
+}
+
+.breadcrumb .divider {
+ padding: 0 5px;
+ color: #ccc;
+}
+
+.breadcrumb .active {
+ color: #999999;
+}
+
+.pagination {
+ height: 40px;
+ margin: 20px 0;
+}
+
+.pagination ul {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ margin-left: 0;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination ul > li {
+ display: inline;
+}
+
+.pagination ul > li > a,
+.pagination ul > li > span {
+ float: left;
+ padding: 0 14px;
+ line-height: 38px;
+ text-decoration: none;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-left-width: 0;
+}
+
+.pagination ul > li > a:hover,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ background-color: #f5f5f5;
+}
+
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ color: #999999;
+ cursor: default;
+}
+
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover {
+ color: #999999;
+ cursor: default;
+ background-color: transparent;
+}
+
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+ border-left-width: 1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.pagination-centered {
+ text-align: center;
+}
+
+.pagination-right {
+ text-align: right;
+}
+
+.pager {
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+ *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager li {
+ display: inline;
+}
+
+.pager a,
+.pager span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.pager a:hover {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
+.pager .next a,
+.pager .next span {
+ float: right;
+}
+
+.pager .previous a {
+ float: left;
+}
+
+.pager .disabled a,
+.pager .disabled a:hover,
+.pager .disabled span {
+ color: #999999;
+ cursor: default;
+ background-color: #fff;
+}
+
+.modal-open .modal .dropdown-menu {
+ z-index: 2050;
+}
+
+.modal-open .modal .dropdown.open {
+ *z-index: 2050;
+}
+
+.modal-open .modal .popover {
+ z-index: 2060;
+}
+
+.modal-open .modal .tooltip {
+ z-index: 2080;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000000;
+}
+
+.modal-backdrop.fade {
+ opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: 1050;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ overflow: auto;
+ background-color: #ffffff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ *border: 1px solid #999;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+
+.modal.fade {
+ top: -25%;
+ -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+ transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+ top: 50%;
+}
+
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+ margin-top: 2px;
+}
+
+.modal-header h3 {
+ margin: 0;
+ line-height: 30px;
+}
+
+.modal-body {
+ max-height: 400px;
+ padding: 15px;
+ overflow-y: auto;
+}
+
+.modal-form {
+ margin-bottom: 0;
+}
+
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1030;
+ display: block;
+ padding: 5px;
+ font-size: 11px;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ visibility: visible;
+}
+
+.tooltip.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+ margin-top: -3px;
+}
+
+.tooltip.right {
+ margin-left: 3px;
+}
+
+.tooltip.bottom {
+ margin-top: 3px;
+}
+
+.tooltip.left {
+ margin-left: -3px;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ 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-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-right-color: #000000;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-left-color: #000000;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ width: 236px;
+ padding: 1px;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.popover.top {
+ margin-bottom: 10px;
+}
+
+.popover.right {
+ margin-left: 10px;
+}
+
+.popover.bottom {
+ margin-top: 10px;
+}
+
+.popover.left {
+ margin-right: 10px;
+}
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+ padding: 9px 14px;
+}
+
+.popover-content p,
+.popover-content ul,
+.popover-content ol {
+ margin-bottom: 0;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.popover .arrow:after {
+ z-index: -1;
+ content: "";
+}
+
+.popover.top .arrow {
+ bottom: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-top-color: #ffffff;
+ border-width: 10px 10px 0;
+}
+
+.popover.top .arrow:after {
+ bottom: -1px;
+ left: -11px;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 0;
+}
+
+.popover.right .arrow {
+ top: 50%;
+ left: -10px;
+ margin-top: -10px;
+ border-right-color: #ffffff;
+ border-width: 10px 10px 10px 0;
+}
+
+.popover.right .arrow:after {
+ bottom: -11px;
+ left: -1px;
+ border-right-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 11px 0;
+}
+
+.popover.bottom .arrow {
+ top: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-bottom-color: #ffffff;
+ border-width: 0 10px 10px;
+}
+
+.popover.bottom .arrow:after {
+ top: -1px;
+ left: -11px;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ border-width: 0 11px 11px;
+}
+
+.popover.left .arrow {
+ top: 50%;
+ right: -10px;
+ margin-top: -10px;
+ border-left-color: #ffffff;
+ border-width: 10px 0 10px 10px;
+}
+
+.popover.left .arrow:after {
+ right: -1px;
+ bottom: -11px;
+ border-left-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 0 11px 11px;
+}
+
+.thumbnails {
+ margin-left: -20px;
+ list-style: none;
+ *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.thumbnails:after {
+ clear: both;
+}
+
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+.thumbnails > li {
+ float: left;
+ margin-bottom: 20px;
+ margin-left: 20px;
+}
+
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: 20px;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover {
+ border-color: #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #555555;
+}
+
+.label,
+.badge {
+ font-size: 11.844px;
+ font-weight: bold;
+ line-height: 14px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+}
+
+.label {
+ padding: 1px 4px 2px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.badge {
+ padding: 1px 9px 2px;
+ -webkit-border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
+}
+
+a.label:hover,
+a.badge:hover {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+ background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+ background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+ background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+ background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+ background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+ background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+ background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+ background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+ background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+ background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+ top: 0;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 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: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(to bottom, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+ -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+ background-color: #149bdf;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ 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);
+ background-image: -moz-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);
+ 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);
+ 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);
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+ background-color: #dd514c;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+ background-color: #ee5f5b;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ 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);
+ background-image: -moz-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);
+ 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);
+ 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);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+ background-color: #5eb95e;
+ background-image: -moz-linear-gradient(top, #62c462, #57a957);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+ background-image: -o-linear-gradient(top, #62c462, #57a957);
+ background-image: linear-gradient(to bottom, #62c462, #57a957);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+ background-color: #62c462;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ 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);
+ background-image: -moz-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);
+ 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);
+ 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);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+ background-color: #4bb1cf;
+ background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+ background-color: #5bc0de;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ 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);
+ background-image: -moz-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);
+ 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);
+ 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);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+ background-color: #fbb450;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ 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);
+ background-image: -moz-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);
+ 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);
+ 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);
+}
+
+.accordion {
+ margin-bottom: 20px;
+}
+
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.accordion-heading {
+ border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+.accordion-toggle {
+ cursor: pointer;
+}
+
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+ position: relative;
+ margin-bottom: 20px;
+ line-height: 1;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+
+.carousel .item {
+ position: relative;
+ display: none;
+ -webkit-transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+
+.carousel .item > img {
+ display: block;
+ line-height: 1;
+}
+
+.carousel .active,
+.carousel .next,
+.carousel .prev {
+ display: block;
+}
+
+.carousel .active {
+ left: 0;
+}
+
+.carousel .next,
+.carousel .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.carousel .next {
+ left: 100%;
+}
+
+.carousel .prev {
+ left: -100%;
+}
+
+.carousel .next.left,
+.carousel .prev.right {
+ left: 0;
+}
+
+.carousel .active.left {
+ left: -100%;
+}
+
+.carousel .active.right {
+ left: 100%;
+}
+
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: #ffffff;
+ text-align: center;
+ background: #222222;
+ border: 3px solid #ffffff;
+ -webkit-border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+ right: 15px;
+ left: auto;
+}
+
+.carousel-control:hover {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 15px;
+ background: #333333;
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+ line-height: 20px;
+ color: #ffffff;
+}
+
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+
+.carousel-caption p {
+ margin-bottom: 0;
+}
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ background-color: #eeeeee;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.hero-unit h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+ color: inherit;
+}
+
+.hero-unit p {
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ color: inherit;
+}
+
+.pull-right {
+ float: right;
+}
+
+.pull-left {
+ float: left;
+}
+
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.affix {
+ position: fixed;
+}
diff --git a/module/web/static/css/default/style.less b/module/web/static/css/default/style.less
new file mode 100644
index 000000000..203135e97
--- /dev/null
+++ b/module/web/static/css/default/style.less
@@ -0,0 +1,318 @@
+
+/*
+ General
+ */
+
+@min-width: 1000px;
+@header-height: 70px;
+@footer-height: 100px;
+@margin-side: 150px;
+
+@dark: #333333;
+@grey: #757575;
+@yellow: #fee247;
+@blue: #3a79aa;
+@emph: #FF7637;
+
+
+* {
+ margin: 0;
+}
+
+html, body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: 'Abel', sans-serif;
+ font-size: 16px;
+ background: url("../../img/default/bgpattern.png") repeat scroll 0 0 transparent;
+ min-width: @min-width;
+}
+
+h1, h2, h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+}
+
+a {
+ text-decoration: none;
+ color: @blue;
+}
+
+a:hover {
+ text-decoration: none;
+ color: @emph;
+}
+
+#wrap {
+ min-height: 100%;
+}
+
+#content {
+ margin-left: @margin-side;
+ margin-right: @margin-side ;
+ padding-bottom: @footer-height;
+}
+
+#content:before {
+ display: block;
+ content: " ";
+ height: @header-height;
+}
+
+/*
+ Header
+*/
+
+header {
+ background: url("../../img/default/main-wrapper-bg.png") repeat-x;
+ height: @header-height;
+ position: fixed;
+ top: 0;
+ vertical-align: top;
+ width: 100%;
+ z-index: 10;
+ min-width: @min-width;
+ color: #ffffff;
+}
+
+header a {
+ color: #ffffff;
+}
+
+header:before {
+ position: absolute;
+ content: ' ';
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: transparent;
+ box-shadow: 0 0 5px black;
+ z-index: -1;
+}
+
+header div.center {
+ position: relative;
+ padding-left: 20px;
+ padding-right: 20px;
+}
+header div.center span.title {
+ color: white;
+ float: left;
+ font-family: SansationRegular, sans-serif;
+ font-size: 40px;
+ cursor: default;
+ margin-top: 24px;
+}
+header .logo {
+ float: left;
+ margin-right: 10px;
+ margin-top: 6px;
+ width: 120px;
+ height: 120px;
+ background: url("../../img/default/logo.png")no-repeat;
+}
+
+.header_block {
+ float: right;
+ margin: 12px 12px 0;
+ font-family: SansationRegular, sans-serif;
+}
+.header_icon {
+ padding-top: 2px;
+ padding-bottom: 5px;
+ padding-left: 25px;
+ height: 20px;
+}
+
+.header_text {
+ text-align: center;
+}
+
+.icon_info img {
+ margin-bottom: -4px;
+ padding-right: 5px;
+}
+
+#notification_div {
+ position: absolute;
+ left: 50%;
+ width: 28%;
+ height: 45px;
+ margin-left: -14%;
+ margin-top: 12px;
+ text-align: center;
+}
+
+#globalprogress {
+ height: 8px;
+ margin: 8px 5px 0;
+}
+
+#globalprogress .bar {
+ background-color: @yellow;
+}
+
+#speedgraph {
+ float: right;
+ height: 45px;
+ width: 14%;
+ margin-top: 12px;
+ font-family: sans-serif
+}
+
+#header_user {
+ background: url("../../img/default/icon_user_small_white.png")no-repeat;
+}
+
+#header_speed {
+ background: url("../../img/default/icon_speed_small_white.png")no-repeat;
+}
+
+#header_qeuue {
+ background: url("../../img/default/icon_clock_small_white.png")no-repeat;
+}
+
+/*
+ Login
+*/
+.login {
+ vertical-align: middle;
+ border: 2px solid @dark;
+ padding: 15px 50px;
+ font-size: 17px;
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+}
+/*
+ Footer
+*/
+footer {
+ background: url("../../img/default/main-wrapper-bg.png") repeat-x;
+ color: @grey;
+ height: @footer-height;
+ margin-top: -@footer-height;
+ position: relative;
+ width: 100%;
+ line-height: 16px;
+ z-index: 10;
+}
+
+footer .logo {
+ background: url(../../img/default/logo_grey.png) no-repeat;
+ float: left;
+ width: 60px;
+ height: 60px;
+ margin-top: 12px;
+ margin-right: 12px;
+}
+
+footer div.center {
+ padding-top: 8px;
+ width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+footer:before {
+ position: absolute;
+ content: ' ';
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: transparent;
+ box-shadow: 0 0 5px black;
+ z-index: -1;
+}
+
+footer .block {
+ font-size: 12px;
+ float: left;
+ margin: 0;
+ width: 150px;
+ padding-top: 6px;
+ padding-right: 30px;
+}
+
+footer .copyright {
+ text-align: center;
+ width: auto;
+ padding-top: 22px;
+}
+
+footer h2 {
+ background: url("../../img/default/double-line.gif") repeat-x scroll center bottom transparent !important;
+ color: #FFFFFF;
+ font-family: SansationLight, sans-serif;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 16px;
+ margin: 0;
+ padding-bottom: 6px;
+}
+
+/*
+ Modal Overlay
+*/
+#modal-overlay {
+ content: " ";
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: -moz-radial-gradient(center, ellipse cover, rgba(236,208,66,0) 0%, rgba(40,119,171,0.9) 100%);
+ background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(236,208,66,0)), color-stop(100%,rgba(40,119,171,0.9)));
+ background: -webkit-radial-gradient(center, ellipse cover, rgba(236,208,66,0) 0%,rgba(40,119,171,0.9) 100%);
+ background: -o-radial-gradient(center, ellipse cover, rgba(236,208,66,0) 0%,rgba(40,119,171,0.9) 100%);
+ background: -ms-radial-gradient(center, ellipse cover, rgba(236,208,66,0) 0%,rgba(40,119,171,0.9) 100%);
+ background: radial-gradient(center, ellipse cover, rgba(236,208,66,0) 0%,rgba(40,119,171,0.9) 100%);
+ z-index: 50;
+ opacity: 0;
+}
+
+/*
+ Dashboard
+*/
+
+.nav > li > a:hover {
+ color: @blue;
+}
+
+#dash-nav {
+ border-bottom: 1px dashed @grey;
+ padding-bottom: 2px;
+ margin-bottom: 5px;
+}
+
+#dash-nav li > a {
+ margin-top: 5px;
+}
+
+#dash-nav .breadcrumb {
+ margin: 0;
+ padding-top: 10px;
+ padding-bottom: 0;
+
+ .active {
+ color: @grey;
+ }
+
+}
+
+#dash-nav form {
+ margin-top: 8px;
+ margin-bottom: 0;
+}
+
+#dash-nav input, #dash-nav button {
+ padding-top: 2px;
+ padding-bottom: 2px;
+} \ No newline at end of file
diff --git a/module/web/static/css/font.css b/module/web/static/css/font.css
new file mode 100644
index 000000000..ee117d43b
--- /dev/null
+++ b/module/web/static/css/font.css
@@ -0,0 +1,37 @@
+/**
+ * @file
+ * Font styling
+ */
+
+@font-face {
+ font-family: 'SansationRegular';
+ src: url('../fonts/Sansation_Regular-webfont.eot');
+ src: url('../fonts/Sansation_Regular-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/Sansation_Regular-webfont.woff') format('woff'),
+ url('../fonts/Sansation_Regular-webfont.ttf') format('truetype'),
+ url('../fonts/Sansation_Regular-webfont.svg#SansationRegular') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'SansationLight';
+ src: url('../fonts/Sansation_Light-webfont.eot');
+ src: url('../fonts/Sansation_Light-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/Sansation_Light-webfont.woff') format('woff'),
+ url('../fonts/Sansation_Light-webfont.ttf') format('truetype'),
+ url('../fonts/Sansation_Light-webfont.svg#SansationLight') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'SansationBold';
+ src: url('../fonts/Sansation_Bold-webfont.eot');
+ src: url('../fonts/Sansation_Bold-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/Sansation_Bold-webfont.woff') format('woff'),
+ url('../fonts/Sansation_Bold-webfont.ttf') format('truetype'),
+ url('../fonts/Sansation_Bold-webfont.svg#SansationBold') format('svg');
+ font-weight: normal;
+ font-style: normal;
+} \ No newline at end of file
diff --git a/module/web/static/css/mobile/images/ajax-loader.gif b/module/web/static/css/mobile/images/ajax-loader.gif
new file mode 100644
index 000000000..fd1a189c2
--- /dev/null
+++ b/module/web/static/css/mobile/images/ajax-loader.gif
Binary files differ
diff --git a/module/web/static/css/mobile/images/ajax-loader.png b/module/web/static/css/mobile/images/ajax-loader.png
new file mode 100644
index 000000000..13b208ddd
--- /dev/null
+++ b/module/web/static/css/mobile/images/ajax-loader.png
Binary files differ
diff --git a/module/web/static/css/mobile/images/icons-18-black.png b/module/web/static/css/mobile/images/icons-18-black.png
new file mode 100644
index 000000000..ce1b758ad
--- /dev/null
+++ b/module/web/static/css/mobile/images/icons-18-black.png
Binary files differ
diff --git a/module/web/static/css/mobile/images/icons-18-white.png b/module/web/static/css/mobile/images/icons-18-white.png
new file mode 100644
index 000000000..1ab012723
--- /dev/null
+++ b/module/web/static/css/mobile/images/icons-18-white.png
Binary files differ
diff --git a/module/web/static/css/mobile/images/icons-36-black.png b/module/web/static/css/mobile/images/icons-36-black.png
new file mode 100644
index 000000000..1a59d7c37
--- /dev/null
+++ b/module/web/static/css/mobile/images/icons-36-black.png
Binary files differ
diff --git a/module/web/static/css/mobile/images/icons-36-white.png b/module/web/static/css/mobile/images/icons-36-white.png
new file mode 100644
index 000000000..5647bdc94
--- /dev/null
+++ b/module/web/static/css/mobile/images/icons-36-white.png
Binary files differ
diff --git a/module/web/static/css/mobile/jquery.mobile-1.1.1.min.css b/module/web/static/css/mobile/jquery.mobile-1.1.1.min.css
new file mode 100644
index 000000000..3bbf55c66
--- /dev/null
+++ b/module/web/static/css/mobile/jquery.mobile-1.1.1.min.css
@@ -0,0 +1,2 @@
+/*! jQuery Mobile v1.1.1 1981b3f5ec22675ae47df8f0bdf9622e7780e90e jquerymobile.com | jquery.org/license */
+.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a a.ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a a.ui-link:visited{color:#2489ce}.ui-bar-a a.ui-link:hover{color:#2489ce}.ui-bar-a a.ui-link:active{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:visited{color:#2489ce}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a:visited,.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a:visited,.ui-btn-hover-a:hover,.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a:visited,.ui-btn-down-a:hover,.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b a.ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b a.ui-link:visited{color:#ddf0f8}.ui-bar-b a.ui-link:hover{color:#ddf0f8}.ui-bar-b a.ui-link:active{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:visited{color:#2489ce}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b:visited,.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b:visited,.ui-btn-hover-a:hover,.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b:visited,.ui-btn-down-b:hover,.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c a.ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c a.ui-link:visited{color:#2489ce}.ui-bar-c a.ui-link:hover{color:#2489ce}.ui-bar-c a.ui-link:active{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:visited{color:#2489ce}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c:visited,.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f6f6f6,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c:visited,.ui-btn-hover-c:hover,.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c:visited,.ui-btn-down-c:hover,.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d a.ui-link{color:#2489ce;font-weight:bold}.ui-bar-d a.ui-link:visited{color:#2489ce}.ui-bar-d a.ui-link:hover{color:#2489ce}.ui-bar-d a.ui-link:active{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:visited{color:#2489ce}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d:visited,.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d:visited,.ui-btn-hover-d:hover,.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d:visited,.ui-btn-down-d:hover,.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e a.ui-link{color:#2489ce;font-weight:bold}.ui-bar-e a.ui-link:visited{color:#2489ce}.ui-bar-e a.ui-link:hover{color:#2489ce}.ui-bar-e a.ui-link:active{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:visited{color:#2489ce}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e:visited,.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e:visited,.ui-btn-hover-e:hover,.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e:visited,.ui-btn-down-e:hover,.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active:visited,.ui-btn-active:hover,.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus,.ui-link-inherit:focus{outline:0}.ui-btn.ui-focus{z-index:1}.ui-focus,.ui-btn:focus{-moz-box-shadow:inset 0 0 3px #387bbe,0px 0 9px #387bbe;-webkit-box-shadow:inset 0 0 3px #387bbe,0px 0 9px #387bbe;box-shadow:inset 0 0 3px #387bbe,0px 0 9px #387bbe}.ui-input-text.ui-focus,.ui-input-search.ui-focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus,.ui-mobile-nosupport-boxshadow .ui-link-inherit:focus{outline-width:1px;outline-style:auto}.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;box-shadow:0 1px 1px -1px #fff;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0;zoom:1}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label.ui-input-text,.ui-hide-label label.ui-select,.ui-hide-label label.ui-slider,.ui-hide-label label.ui-submit,.ui-hide-label .ui-controlgroup-label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.ui-grid-solo .ui-block-a{display:block;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:49.95%}.ui-grid-a>:nth-child(n){width:50%;margin-right:-.5px}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.25%}.ui-grid-b>:nth-child(n){width:33.333%;margin-right:-.5px}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:24.925%}.ui-grid-c>:nth-child(n){width:25%;margin-right:-.5px}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:19.925%}.ui-grid-d>:nth-child(n){width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-page-pre-in{opacity:0}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.6875em}.ui-page-footer-fixed{padding-bottom:2.6875em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{max-width:100%}.ui-navbar ul{list-style:none;margin:0;padding:0;position:relative;display:block;border:0;max-width:100%;overflow:hidden}.ui-navbar li .ui-btn{display:block;text-align:center;margin:0 -1px 0 0;border-right-width:0}.ui-navbar li .ui-btn-icon-right .ui-icon{right:6px}.ui-navbar li:last-child .ui-btn,.ui-navbar .ui-grid-duo .ui-block-b .ui-btn{margin-right:0;border-right-width:1px}.ui-header .ui-navbar li:last-child .ui-btn,.ui-footer .ui-navbar li:last-child .ui-btn,.ui-header .ui-navbar .ui-grid-duo .ui-block-b .ui-btn,.ui-footer .ui-navbar .ui-grid-duo .ui-block-b .ui-btn{margin-right:-1px;border-right-width:0}.ui-navbar .ui-grid-duo li.ui-block-a:last-child .ui-btn{margin-right:-1px;border-right-width:1px}.ui-header .ui-navbar li .ui-btn,.ui-footer .ui-navbar li .ui-btn{border-top-width:0;border-bottom-width:0}.ui-header .ui-navbar .ui-grid-b li.ui-block-c .ui-btn,.ui-footer .ui-navbar .ui-grid-b li.ui-block-c .ui-btn{margin-right:-5px}.ui-header .ui-navbar .ui-grid-c li.ui-block-d .ui-btn,.ui-footer .ui-navbar .ui-grid-c li.ui-block-d .ui-btn,.ui-header .ui-navbar .ui-grid-d li.ui-block-e .ui-btn,.ui-footer .ui-navbar .ui-grid-d li.ui-block-e .ui-btn{margin-right:-4px}.ui-header .ui-navbar .ui-grid-b li.ui-block-c .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-b li.ui-block-c .ui-btn-icon-right .ui-icon,.ui-header .ui-navbar .ui-grid-c li.ui-block-d .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-c li.ui-block-d .ui-btn-icon-right .ui-icon,.ui-header .ui-navbar .ui-grid-d li.ui-block-e .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-d li.ui-block-e .ui-btn-icon-right .ui-icon{right:8px}.ui-navbar li .ui-btn .ui-btn-inner{padding-top:.7em;padding-bottom:.8em}.ui-navbar li .ui-btn-icon-top .ui-btn-inner{padding-top:30px}.ui-navbar li .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:30px}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 0;padding:0}.ui-btn.ui-mini{margin-top:.25em;margin-bottom:.25em}.ui-btn-left,.ui-btn-right,.ui-input-clear,.ui-btn-inline,.ui-grid-a .ui-btn,.ui-grid-b .ui-btn,.ui-grid-c .ui-btn,.ui-grid-d .ui-btn,.ui-grid-e .ui-btn,.ui-grid-solo .ui-btn{margin-right:5px;margin-left:5px}.ui-btn-inner{font-size:16px;padding:.6em 20px;min-width:.75em;display:block;position:relative;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block;vertical-align:middle}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-block,.ui-footer .ui-btn-block{display:block}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px;float:left}.ui-btn-text{position:relative;z-index:1;width:100%;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-mini.ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding-top:30px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-mini.ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:30px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left>.ui-btn-inner>.ui-icon,.ui-btn-icon-right>.ui-btn-inner>.ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:none;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=0);font-size:1px;border:0;text-indent:-9999px}.ui-field-contain .ui-btn.ui-submit{margin:0}label.ui-submit{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}@media all and (min-width:450px){.ui-field-contain label.ui-submit{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-btn.ui-submit{width:60%;display:inline-block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.ui-hide-label .ui-btn.ui-submit{width:auto}}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading .ui-btn{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading .ui-btn span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading .ui-btn span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading .ui-btn span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:.5em 0;zoom:1}.ui-controlgroup.ui-mini,fieldset.ui-controlgroup.ui-mini{margin:.25em 0}.ui-field-contain .ui-controlgroup,.ui-field-contain fieldset.ui-controlgroup{margin:0}.ui-bar .ui-controlgroup{margin:0 5px}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup .ui-btn-icon-notext{width:24px;height:24px}.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner .ui-icon{position:absolute;top:50%;right:50%;margin:-9px -9px 0 0}.ui-controlgroup-horizontal .ui-controlgroup-controls:before,.ui-controlgroup-horizontal .ui-controlgroup-controls:after{content:"";display:table}.ui-controlgroup-horizontal .ui-controlgroup-controls:after{clear:both}.ui-controlgroup-horizontal .ui-controlgroup-controls{zoom:1}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select,.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-select .ui-btn,.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%;display:block}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}.ui-hide-label .ui-controlgroup-controls{width:100%}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:0;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:.5em 0;text-align:left;z-index:2}.ui-checkbox .ui-btn.ui-mini,.ui-radio .ui-btn.ui-mini{margin:.25em 0}.ui-controlgroup .ui-checkbox .ui-btn,.ui-controlgroup .ui-radio .ui-btn{margin:0}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:last-child{border-bottom-width:0}.ui-field-contain{max-width:100%}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1}.ui-field-contain .ui-select .ui-btn{margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:none;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn.ui-select-nativeonly{border-radius:0}.ui-select .ui-btn.ui-select-nativeonly select{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner,.ui-select .ui-li-has-count .ui-btn-inner{padding-right:45px}.ui-select .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:32px}.ui-select .ui-btn-icon-right.ui-li-has-count .ui-btn-inner{padding-right:80px}.ui-select .ui-mini.ui-btn-icon-right.ui-li-has-count .ui-btn-inner{padding-right:67px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}.ui-select .ui-btn-icon-right.ui-li-has-count .ui-li-count{right:45px}.ui-select .ui-mini.ui-btn-icon-right.ui-li-has-count .ui-li-count{right:32px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-99999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}.ui-hide-label .ui-select{width:100%}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;margin:.5em 0;line-height:1.4;font-size:16px;display:block;width:100%;outline:0}input.ui-input-text.ui-mini,textarea.ui-input-text.ui-mini{margin:.25em 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text{margin:0}input.ui-input-text,textarea.ui-input-text,.ui-input-search{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;margin:.5em 0;background-image:none;position:relative}.ui-input-search.ui-mini{margin:.25em 0}.ui-field-contain .ui-input-search{margin:0}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{width:100%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider .ui-btn-text,.ui-li-static .ui-btn-text{font-size:16px}.ui-li-divider .ui-mini .ui-btn-text,.ui-li-static .ui-mini .ui-btn-text{font-size:inherit}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count,.ui-li-divider.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:40px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:16px;max-width:16px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:53px}.ui-li-has-alt.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt.ui-li-has-count{padding-right:88px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:10px}.ui-li-has-count.ui-li-divider .ui-li-count,.ui-li-has-count .ui-link-inherit .ui-li-count{margin-top:-.95em}.ui-li-has-arrow.ui-li-has-count .ui-li-count{right:40px}.ui-li-has-alt.ui-li-has-count .ui-li-count{right:53px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-13px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-li-link-alt .ui-btn-icon-notext .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px;background-image:none;padding:.4em;margin:.5em 0;line-height:1.4;font-size:16px;outline:0}input.ui-slider-input.ui-mini,.ui-field-contain input.ui-slider-input.ui-mini{width:45px;margin:.25em 0;font-size:14px}.ui-field-contain input.ui-slider-input{margin:0}input.ui-slider-input,.ui-field-contain input.ui-slider-input{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;-ms-box-sizing:content-box;box-sizing:content-box}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px;top:2px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px;border-top:0}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file
diff --git a/module/web/static/css/mobile/my.css b/module/web/static/css/mobile/my.css
new file mode 100644
index 000000000..2e73c1f35
--- /dev/null
+++ b/module/web/static/css/mobile/my.css
@@ -0,0 +1,93 @@
+.text-align-center {
+ text-align: center;
+}
+.text-align-right {
+ text-align: right;
+}
+
+/** CSS for non-standard jQuery Mobile styles or Codiqa components **/
+.split-wrapper {
+ width: 100%;
+ min-height: 200px;
+ clear: both;
+}
+@media all and (min-width: 650px) {
+ .content-secondary {
+ text-align: left;
+ float: left;
+ width: 45%;
+ background: none;
+ padding: 1.5em 6% 3em 0;
+ margin: 0;
+ }
+ .content-secondary {
+ background: none;
+ border-top: none;
+ }
+ .content-primary {
+ width: 45%;
+ float: right;
+ margin-right: 1%;
+ padding-right: 1%;
+ }
+ .content-primary ul:first-child {
+ margin-top: 0;
+ }
+ .content-secondary ul.ui-listview, .content-secondary ul.ui-listview-inset {
+ margin: 0;
+ }
+ .content-secondary ul.ui-listview .ui-li-divider, .content-secondary ul.ui-listview .ui-li {
+ border-radius: 0px;
+ }
+ .content-secondary ul.ui-listview .ui-li {
+ border-left: 0;
+ border-right: 0;
+ }
+ .content-secondary h2 {
+ position: absolute;
+ left: -9999px;
+ }
+ .content-secondary .ui-li-divider {
+ padding-top: 1em;
+ padding-bottom: 1em;
+ }
+ .content-secondary {
+ margin: 0;
+ padding: 0;
+ }
+
+}
+@media all and (min-width: 750px){
+ .content-secondary {
+ width: 34%;
+ }
+ .content-primary {
+ width: 60%;
+ padding-right: 1%;
+ }
+ .content-secondary ul.ui-listview-inset {
+}
+
+@media all and (min-width: 1200px){
+ .content-secondary {
+ width: 30%;
+ padding-right:6%;
+ margin: 0px 0 20px 5%;
+ }
+ .content-secondary ul {
+ margin: 0;
+ }
+ .content-secondary {
+ margin: 0;
+ padding: 0;
+ }
+ .content-primary {
+ width: 50%;
+ margin-right: 5%;
+ padding-right: 3%;
+ }
+ .content-primary {
+ width: 60%;
+ }
+}
+
diff --git a/module/web/static/css/mobile/style.css b/module/web/static/css/mobile/style.css
new file mode 100644
index 000000000..349b54bc0
--- /dev/null
+++ b/module/web/static/css/mobile/style.css
@@ -0,0 +1,214 @@
+
+/*
+ General
+ */
+
+* {
+ margin: 0;
+}
+
+html, body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: 'Abel', sans-serif;
+ font-size: 16px;
+ color: #757575;
+ background: url("../../img/default/fancy_deboss.png") repeat scroll 0 0 transparent;
+ min-width: 320px;
+}
+
+h1, h2, h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+ color: #221D1D;
+}
+
+h1 {
+ font-size: 2em;
+}
+
+h2 {
+ font-size: 2.4em;
+}
+
+h3 {
+ font-size: 1.6em;
+}
+
+p, ul, ol {
+ margin-top: 0;
+ line-height: 180%;
+}
+
+ul, ol {
+}
+
+a {
+ text-decoration: none;
+ color: #FF7637;
+}
+
+a:hover {
+}
+
+#wrap {
+ min-height: 100%;
+}
+
+#content {
+ padding-left: 10px;
+ padding-bottom: 30px; /* Height of footer */
+}
+
+#content:before {
+ display: block;
+ content: " ";
+ height: 30px;
+}
+
+/*
+ Header
+*/
+
+header {
+ background: url("../../img/default/main-wrapper-bg.png") repeat-x;
+ height: 30px;
+ position: fixed;
+ top: 0;
+ vertical-align: top;
+ width: 100%;
+ z-index: 10;
+ min-width: 1000px;
+ color: #ffffff;
+}
+
+header a {
+ color: #ffffff;
+}
+
+header:before {
+ position: absolute;
+ content: ' ';
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: transparent;
+ box-shadow: 0 0 5px black;
+ z-index: -1;
+}
+
+header div.center {
+ position: relative;
+ padding-left: 20px;
+ padding-right: 20px;
+}
+header span.title {
+ color: white;
+ float: left;
+ font-family: SansationRegular, sans-serif;
+ font-size: 18px;
+ cursor: default;
+ margin-top: 4px;
+ padding-left: 10px;
+}
+
+/*
+ Login
+*/
+.login {
+ vertical-align: middle;
+ text-align: center;
+ border: 2px solid #000000;
+ padding: 15px;
+ font-size: 17px;
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+}
+
+.login input, .login div{
+ padding: 3px;
+}
+.login_submit input {
+ padding: 5px 15px;
+}
+.login_user , .login_password {
+ text-align: right;
+ width: 320px;
+ margin-left: auto;
+ margin-right: auto;
+}
+.login_user span, .login_password span{
+ margin-right: 5px;
+}
+/*
+ Footer
+*/
+footer {
+ background: url("../../img/default/main-wrapper-bg.png") repeat-x;
+ height: 30px;
+ margin-top: -30px;
+ position: relative;
+ width: 100%;
+ z-index: 10;
+}
+
+footer .logo {
+ background: url(../../img/default/logo_grey.png) no-repeat;
+ float: left;
+ width: 60px;
+ height: 60px;
+ margin-top: 12px;
+ margin-right: 12px;
+}
+
+footer div.center {
+ padding-top: 10px;
+ width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+footer:before {
+ position: absolute;
+ content: ' ';
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: transparent;
+ box-shadow: 0 0 5px black;
+ z-index: -1;
+}
+
+footer .block {
+ font-size: 12px;
+ float: left;
+ margin: 0;
+ width: 150px;
+ padding-top: 6px;
+ padding-right: 30px;
+}
+
+footer .copyright {
+ text-align: center;
+ width: auto;
+ padding-top: 22px;
+}
+
+footer h2 {
+ background: url("../../img/default/double-line.gif") repeat-x scroll center bottom transparent !important;
+ color: #FFFFFF;
+ font-family: SansationLight, sans-serif;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 16px;
+ margin: 0;
+ padding-bottom: 6px;
+} \ No newline at end of file
diff --git a/module/web/static/fonts/Sansation_Bold-webfont.eot b/module/web/static/fonts/Sansation_Bold-webfont.eot
new file mode 100644
index 000000000..43ed2ee31
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Bold-webfont.eot
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Bold-webfont.ttf b/module/web/static/fonts/Sansation_Bold-webfont.ttf
new file mode 100644
index 000000000..d2e7c4c2a
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Bold-webfont.ttf
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Bold-webfont.woff b/module/web/static/fonts/Sansation_Bold-webfont.woff
new file mode 100644
index 000000000..9ee938d55
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Bold-webfont.woff
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Light-webfont.eot b/module/web/static/fonts/Sansation_Light-webfont.eot
new file mode 100644
index 000000000..d83fa9cf6
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Light-webfont.eot
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Light-webfont.ttf b/module/web/static/fonts/Sansation_Light-webfont.ttf
new file mode 100644
index 000000000..64d734bec
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Light-webfont.ttf
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Light-webfont.woff b/module/web/static/fonts/Sansation_Light-webfont.woff
new file mode 100644
index 000000000..5f3dce493
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Light-webfont.woff
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Regular-webfont.eot b/module/web/static/fonts/Sansation_Regular-webfont.eot
new file mode 100644
index 000000000..46219c9ff
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Regular-webfont.eot
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Regular-webfont.ttf b/module/web/static/fonts/Sansation_Regular-webfont.ttf
new file mode 100644
index 000000000..92f686359
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Regular-webfont.ttf
Binary files differ
diff --git a/module/web/static/fonts/Sansation_Regular-webfont.woff b/module/web/static/fonts/Sansation_Regular-webfont.woff
new file mode 100644
index 000000000..524b67992
--- /dev/null
+++ b/module/web/static/fonts/Sansation_Regular-webfont.woff
Binary files differ
diff --git a/module/web/media/default/img/arrow_refresh.png b/module/web/static/img/default/arrow_refresh.png
index 0de26566d..0de26566d 100644
--- a/module/web/media/default/img/arrow_refresh.png
+++ b/module/web/static/img/default/arrow_refresh.png
Binary files differ
diff --git a/module/web/static/img/default/bgpattern.png b/module/web/static/img/default/bgpattern.png
new file mode 100644
index 000000000..5111e6bdf
--- /dev/null
+++ b/module/web/static/img/default/bgpattern.png
Binary files differ
diff --git a/module/web/media/default/img/delete.png b/module/web/static/img/default/delete.png
index 08f249365..08f249365 100644
--- a/module/web/media/default/img/delete.png
+++ b/module/web/static/img/default/delete.png
Binary files differ
diff --git a/module/web/static/img/default/double-line.gif b/module/web/static/img/default/double-line.gif
new file mode 100644
index 000000000..e9f4cf971
--- /dev/null
+++ b/module/web/static/img/default/double-line.gif
Binary files differ
diff --git a/module/web/static/img/default/fancy_deboss.png b/module/web/static/img/default/fancy_deboss.png
new file mode 100644
index 000000000..926a762db
--- /dev/null
+++ b/module/web/static/img/default/fancy_deboss.png
Binary files differ
diff --git a/module/web/media/default/img/folder.png b/module/web/static/img/default/folder.png
index 784e8fa48..784e8fa48 100644
--- a/module/web/media/default/img/folder.png
+++ b/module/web/static/img/default/folder.png
Binary files differ
diff --git a/module/web/static/img/default/icon_blank_file_black.png b/module/web/static/img/default/icon_blank_file_black.png
new file mode 100644
index 000000000..d054a2af7
--- /dev/null
+++ b/module/web/static/img/default/icon_blank_file_black.png
Binary files differ
diff --git a/module/web/static/img/default/icon_clock_small_white.png b/module/web/static/img/default/icon_clock_small_white.png
new file mode 100644
index 000000000..9e6c9bdd0
--- /dev/null
+++ b/module/web/static/img/default/icon_clock_small_white.png
Binary files differ
diff --git a/module/web/static/img/default/icon_folder.png b/module/web/static/img/default/icon_folder.png
new file mode 100644
index 000000000..31773520a
--- /dev/null
+++ b/module/web/static/img/default/icon_folder.png
Binary files differ
diff --git a/module/web/static/img/default/icon_speed_small_white.png b/module/web/static/img/default/icon_speed_small_white.png
new file mode 100644
index 000000000..ac86514ca
--- /dev/null
+++ b/module/web/static/img/default/icon_speed_small_white.png
Binary files differ
diff --git a/module/web/static/img/default/icon_user_small_white.png b/module/web/static/img/default/icon_user_small_white.png
new file mode 100644
index 000000000..6434734fa
--- /dev/null
+++ b/module/web/static/img/default/icon_user_small_white.png
Binary files differ
diff --git a/module/web/static/img/default/logo.png b/module/web/static/img/default/logo.png
new file mode 100644
index 000000000..7cb924d60
--- /dev/null
+++ b/module/web/static/img/default/logo.png
Binary files differ
diff --git a/module/web/static/img/default/logo_grey.png b/module/web/static/img/default/logo_grey.png
new file mode 100644
index 000000000..7061372aa
--- /dev/null
+++ b/module/web/static/img/default/logo_grey.png
Binary files differ
diff --git a/module/web/static/img/default/main-wrapper-bg.png b/module/web/static/img/default/main-wrapper-bg.png
new file mode 100644
index 000000000..68febb6a2
--- /dev/null
+++ b/module/web/static/img/default/main-wrapper-bg.png
Binary files differ
diff --git a/module/web/media/default/img/pencil.png b/module/web/static/img/default/pencil.png
index 0bfecd50e..0bfecd50e 100644
--- a/module/web/media/default/img/pencil.png
+++ b/module/web/static/img/default/pencil.png
Binary files differ
diff --git a/module/web/media/img/favicon.ico b/module/web/static/img/favicon.ico
index 58b1f4b89..58b1f4b89 100644
--- a/module/web/media/img/favicon.ico
+++ b/module/web/static/img/favicon.ico
Binary files differ
diff --git a/module/web/static/img/glyphicons-halflings-white.png b/module/web/static/img/glyphicons-halflings-white.png
new file mode 100644
index 000000000..3bf6484a2
--- /dev/null
+++ b/module/web/static/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/module/web/static/img/glyphicons-halflings.png b/module/web/static/img/glyphicons-halflings.png
new file mode 100644
index 000000000..a99699932
--- /dev/null
+++ b/module/web/static/img/glyphicons-halflings.png
Binary files differ
diff --git a/module/web/static/js/app.build.js b/module/web/static/js/app.build.js
new file mode 100644
index 000000000..88b96cb89
--- /dev/null
+++ b/module/web/static/js/app.build.js
@@ -0,0 +1,22 @@
+//Install node.js, navigate to the js folder, and then run this command: "node r.js -o app.build.js"
+({
+
+ // Creates a js-optimized folder at the same folder level as your "js" folder and places the optimized project there
+ dir: "../js-optimized",
+
+ // Tells Require.js to look at desktop.js for all shim and path configurations
+ mainConfigFile: 'default.js',
+
+ // Modules to be optimized:
+ modules: [
+
+ {
+ name: "mobile"
+ },
+
+ {
+ name: "default"
+ }
+ ]
+
+}) \ No newline at end of file
diff --git a/module/web/static/js/collections/FileList.js b/module/web/static/js/collections/FileList.js
new file mode 100644
index 000000000..e91088867
--- /dev/null
+++ b/module/web/static/js/collections/FileList.js
@@ -0,0 +1,17 @@
+define(['jquery', 'backbone', 'underscore', 'models/File'], function($, Backbone, _, File) {
+
+ return Backbone.Collection.extend({
+
+ model: File,
+
+ comparator: function(file) {
+ return file.get('fileorder');
+ },
+
+ initialize: function() {
+
+ }
+
+ });
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/collections/PackageList.js b/module/web/static/js/collections/PackageList.js
new file mode 100644
index 000000000..a36f8bcdc
--- /dev/null
+++ b/module/web/static/js/collections/PackageList.js
@@ -0,0 +1,15 @@
+define(['jquery', 'backbone', 'underscore', 'models/Package'], function($, Backbone, _, Package) {
+
+ return Backbone.Collection.extend({
+
+ model: Package,
+
+ comparator: function(pack) {
+ return pack.get('packageorder');
+ },
+
+ initialize: function() {
+ }
+
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/default.js b/module/web/static/js/default.js
new file mode 100644
index 000000000..e200f470a
--- /dev/null
+++ b/module/web/static/js/default.js
@@ -0,0 +1,57 @@
+// Sets the require.js configuration for your application.
+// Note: Config needs to be duplicated for mobile.js
+require.config({
+
+ // XXX: To many dots in file breaks dependencies
+ paths:{
+
+ jquery:"libs/jquery-1.8.0",
+ jqueryui:"libs/jqueryui",
+ flot:"libs/jquery.flot.min",
+ transit:"libs/jquery.transit-0.1.3",
+ omniwindow: "libs/jquery.omniwindow",
+ bootstrap: "libs/bootstrap-2.1.1",
+
+ underscore:"libs/lodash-0.5.2",
+ backbone:"libs/backbone-0.9.2",
+
+ // Require.js Plugins
+ text:"plugins/text-2.0.3",
+ tpl: "../../templates"
+
+ },
+
+ // Sets the configuration for your third party scripts that are not AMD compatible
+ shim:{
+
+ "backbone":{
+ deps:["underscore", "jquery"],
+ exports:"Backbone" //attaches "Backbone" to the window object
+ },
+ "flot" : ["jquery"],
+ "transit" : ["jquery"],
+ "omniwindow" : ["jquery"],
+ "bootstrap" : ["jquery"]
+ } // end Shim Configuration
+
+});
+
+define('default', ['jquery', 'backbone', 'routers/defaultRouter', 'views/headerView', 'views/packageTreeView',
+ 'utils/animations', 'bootstrap'],
+ function ($, Backbone, DefaultRouter, HeaderView, TreeView) {
+
+
+ var init = function(){
+ var view = new HeaderView();
+ view.render();
+ };
+
+ var initPackageTree = function() {
+ $(function() {
+ var view = new TreeView();
+ view.init();
+ });
+ };
+
+ return {"init":init, "initPackageTree": initPackageTree};
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/backbone-0.9.2.js b/module/web/static/js/libs/backbone-0.9.2.js
new file mode 100644
index 000000000..d0410b5c8
--- /dev/null
+++ b/module/web/static/js/libs/backbone-0.9.2.js
@@ -0,0 +1,1431 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `global`
+ // on the server).
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create a local reference to slice/splice.
+ var slice = Array.prototype.slice;
+ var splice = Array.prototype.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.2';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ var $ = root.jQuery || root.Zepto || root.ender;
+
+ // Set the JavaScript library that will be used for DOM manipulation and
+ // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+ // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+ // alternate JavaScript library (or a mock library for testing your views
+ // outside of a browser).
+ Backbone.setDomLibrary = function(lib) {
+ $ = lib;
+ };
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+
+ // Regular expression used to split event strings
+ var eventSplitter = /\s+/;
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may bind with `on` or remove with `off` callback functions
+ // to an event; trigger`-ing an event fires all callbacks in succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.on('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ var Events = Backbone.Events = {
+
+ // Bind one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) return this;
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ while (event = events.shift()) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ }
+
+ return this;
+ },
+
+ // Remove one or many callbacks. If `context` is null, removes all callbacks
+ // with that function. If `callback` is null, removes all callbacks for the
+ // event. If `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ while (event = events.shift()) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) continue;
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // passed the same arguments as `trigger` is, apart from the event name
+ // (unless you're listening on `"all"`, which will cause your callback to
+ // receive the true name of the event as the first argument).
+ trigger: function(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ if (node = calls[event]) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ if (node = all) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) this.collection = options.collection;
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ (options || (options = {})).unset = true;
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ (options || (options = {})).unset = true;
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save: function(key, value, options) {
+ var attrs, current;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) {
+ delete options.wait;
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+ if (options.wait) this.set(current, silentOptions);
+ return xhr;
+ },
+
+ // Destroy this model on the server if it was already persisted.
+ // Optimistically removes the model from its collection, if it has one.
+ // If `wait: true` is passed, waits for the server to respond before removal.
+ destroy: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+
+ var triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (this.isNew()) {
+ triggerDestroy();
+ return false;
+ }
+
+ options.success = function(resp) {
+ if (options.wait) triggerDestroy();
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ if (!options.wait) triggerDestroy();
+ return xhr;
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url: function() {
+ var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew: function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (!arguments.length) return !_.isEmpty(this.changed);
+ return _.has(this.changed, attr);
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+ var val, changed = false, old = this._previousAttributes;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (!arguments.length || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. If a `comparator` is specified, the Collection will maintain
+ // its models in sort order, as they're added and removed.
+ var Collection = Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.model) this.model = options.model;
+ if (options.comparator) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Collection.prototype, Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model: Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON: function(options) {
+ return this.map(function(model){ return model.toJSON(options); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ models.splice(dups[i], 1);
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of `filter`.
+ where: function(attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return true;
+ });
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
+ // normal circumstances, as the set will maintain sort order as each item
+ // is added.
+ sort: function(options) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. Add the model to the
+ // collection immediately, unless `wait: true` is passed, in which case we
+ // wait for the server to agree.
+ create: function(model, options) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) coll.add(nextModel, options);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', model, resp, options);
+ }
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(model, options) {
+ options || (options = {});
+ if (!(model instanceof Model)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) model = false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ if (this == model.collection) {
+ delete model.collection;
+ }
+ model.off('all', this._onModelEvent, this);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that originate
+ // in other collections are ignored.
+ _onModelEvent: function(event, model, collection, options) {
+ if ((event == 'add' || event == 'remove') && collection != this) return;
+ if (event == 'destroy') {
+ this.remove(model, options);
+ }
+ if (model && event === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+ });
+
+ // Backbone.Router
+ // -------------------
+
+ // Routers map faux-URLs to actions, and fire events when routes are
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
+ var Router = Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var namedParam = /:\w+/g;
+ var splatParam = /\*\w+/g;
+ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ // Set up all inheritable **Backbone.Router** properties and methods.
+ _.extend(Router.prototype, Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query, num) {
+ // ...
+ // });
+ //
+ route: function(route, name, callback) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) return;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Has the history handling already been started?
+ History.started = false;
+
+ // Set up all inheritable **Backbone.History** properties and methods.
+ _.extend(History.prototype, Events, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+ return fragment.replace(routeStripper, '');
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start: function(options) {
+ if (History.started) throw new Error("Backbone.history has already been started");
+ History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname == this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+ this.fragment = frag;
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+
+ // Backbone.View
+ // -------------
+
+ // Creating a Backbone.View creates its initial element outside of the DOM,
+ // if an existing element is not provided...
+ var View = Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+ // Set up all inheritable **Backbone.View** properties and methods.
+ _.extend(View.prototype, Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ // jQuery delegate for element lookup, scoped to DOM elements within the
+ // current view. This should be prefered to global lookups where possible.
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render: function() {
+ return this;
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // Change the view's element (`this.el` property), including event
+ // re-delegation.
+ setElement: function(element, delegate) {
+ if (this.$el) this.undelegateEvents();
+ this.$el = (element instanceof $) ? element : $(element);
+ this.el = this.$el[0];
+ if (delegate !== false) this.delegateEvents();
+ return this;
+ },
+
+ // Set callbacks, where `this.events` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // 'click .open': function(e) { ... }
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents: function(events) {
+ if (!(events || (events = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
+ // You usually don't need to use this, but may wish to if you have multiple
+ // Backbone views attached to the same DOM element.
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` properties.
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = getValue(this, 'attributes') || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // Override this function to change the manner in which Backbone persists
+ // models to the server. You will be passed the type of request, and the
+ // model in question. By default, makes a RESTful Ajax request
+ // to the model's `url()`. Some possible customizations could be:
+ //
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
+ // * Send up the models as XML instead of JSON.
+ // * Persist models via WebSockets instead of Ajax.
+ //
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
+ // instead of `application/json` with the model in a param named `model`.
+ // Useful when interfacing with server-side languages like **PHP** that make
+ // it difficult to read the body of `PUT` requests.
+ Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default options, unless specified.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.data = params.data ? {model: params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return $.ajax(_.extend(params, options));
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this); \ No newline at end of file
diff --git a/module/web/static/js/libs/bootstrap-2.1.1.js b/module/web/static/js/libs/bootstrap-2.1.1.js
new file mode 100644
index 000000000..f73fcb8e7
--- /dev/null
+++ b/module/web/static/js/libs/bootstrap-2.1.1.js
@@ -0,0 +1,2027 @@
+/* ===================================================
+ * bootstrap-transition.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ $(function () {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(function () {
+ $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(function () {
+ $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(function () {
+ $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ getParent($(toggle))
+ .removeClass('open')
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html')
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ $('body')
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+ })
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ $('body').addClass('modal-open')
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+ .focus()
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
+ that.$element.trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ $('body').removeClass('modal-open')
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(document.body)
+
+ if (this.options.backdrop != 'static') {
+ this.$backdrop.click($.proxy(this.hide, this))
+ }
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+ })
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-tooltip.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ 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()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+
+ return this
+ }
+
+ , 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') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: true
+ }
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-popover.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active a').last()[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ 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')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(function () {
+ $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '<strong>' + match + '</strong>'
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if ($.browser.chrome || $.browser.webkit || $.browser.msie) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: '<ul class="typeahead dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(function () {
+ $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+ })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery); \ No newline at end of file
diff --git a/module/web/static/js/libs/jquery-1.8.0.js b/module/web/static/js/libs/jquery-1.8.0.js
new file mode 100644
index 000000000..43991b385
--- /dev/null
+++ b/module/web/static/js/libs/jquery-1.8.0.js
@@ -0,0 +1,9227 @@
+/*!
+ * jQuery JavaScript Library v1.8.0
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Thu Aug 09 2012 16:24:48 GMT-0400 (Eastern Daylight Time)
+ */
+(function( window, undefined ) {
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // IE doesn't match non-breaking spaces with \s
+ rtrim = core_rnotwhite.test("\xA0") ? (/^[\s\xA0]+|[\s\xA0]+$/g) : /^\s+|\s+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.0",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" || ( document.readyState !== "loading" && document.addEventListener ) ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) {
+ list.push( arg );
+ } else if ( arg && arg.length ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Preliminary tests
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, window.getComputedStyle was used here
+ // instead of getComputedStyle because it gave a better gzip size.
+ // The difference between window.getComputedStyle and getComputedStyle is
+ // 7 bytes
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div></div>";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+})();
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+ if ( !queue.length && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = jqcur.is( sel );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8 –
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "_submit_attached", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "_change_attached", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+ dirruns,
+ sortOrder,
+ siblingCheck,
+ assertGetIdNotName,
+
+ document = window.document,
+ docElem = document.documentElement,
+
+ strundefined = "undefined",
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ done = 0,
+ slice = [].slice,
+ push = [].push,
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",
+ pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",
+ combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*",
+ groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 7 ) + "|[^\\\\(),])+",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcombinators = new RegExp( "^" + combinators ),
+
+ // All simple (non-comma) selectors, excluding insignifant trailing whitespace
+ rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ),
+
+ // A selector, or everything after leading whitespace
+ // Optionally followed in either case by a ")" for terminating sub-selectors
+ rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ),
+
+ // All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive
+ rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "[-", "[-\\*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "POS": new RegExp( pos, "ig" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ classCache = {},
+ cachedClasses = [],
+ compilerCache = {},
+ cachedSelectors = [],
+
+ // Mark a function for use in filtering
+ markFunction = function( fn ) {
+ fn.sizzleFilter = true;
+ return fn;
+ },
+
+ // Returns a function to use in pseudos for input types
+ createInputFunction = function( type ) {
+ return function( elem ) {
+ // Check the input's nodeName and type
+ return elem.nodeName.toLowerCase() === "input" && elem.type === type;
+ };
+ },
+
+ // Returns a function to use in pseudos for buttons
+ createButtonFunction = function( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+ },
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var pass = false,
+ div = document.createElement("div");
+ try {
+ pass = fn( div );
+ } catch (e) {}
+ // release memory in IE
+ div = null;
+ return pass;
+ },
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = "<select></select>";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length ===
+ // buggy browsers will return more than the correct 0
+ 2 + document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ }),
+
+ // Check if the browser returns only elements
+ // when doing getElementsByTagName("*")
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return div.getElementsByTagName("*").length === 0;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = "<a href='#'></a>";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return false;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length !== 1;
+ });
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector, context, results, seed, xml );
+};
+
+var Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ match: matchExpr,
+
+ order: [ "ID", "TAG" ],
+
+ attrHandle: {},
+
+ createPseudo: markFunction,
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr.CHILD
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var argument,
+ unquoted = match[4];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Relinquish our claim on characters in `unquoted` from a closing parenthesis on
+ if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) {
+
+ match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 );
+ unquoted = argument[0].slice( 0, -1 );
+ }
+
+ // Quoted or unquoted, we have the full argument
+ // Return only captures needed by the pseudo filter method (type and argument)
+ match.splice( 2, 3, unquoted || match[3] );
+ return match;
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className ];
+ if ( !pattern ) {
+ pattern = classCache[ className ] = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" );
+ cachedClasses.push( className );
+ // Avoid too large of a cache
+ if ( cachedClasses.length > Expr.cacheLength ) {
+ delete classCache[ cachedClasses.shift() ];
+ }
+ }
+ return function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ };
+ },
+
+ "ATTR": function( name, operator, check ) {
+ if ( !operator ) {
+ return function( elem ) {
+ return Sizzle.attr( elem, name ) != null;
+ };
+ }
+
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name ),
+ value = result + "";
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+
+ switch ( operator ) {
+ case "=":
+ return value === check;
+ case "!=":
+ return value !== check;
+ case "^=":
+ return check && value.indexOf( check ) === 0;
+ case "*=":
+ return check && value.indexOf( check ) > -1;
+ case "$=":
+ return check && value.substr( value.length - check.length ) === check;
+ case "~=":
+ return ( " " + value + " " ).indexOf( check ) > -1;
+ case "|=":
+ return value === check || value.substr( 0, check.length + 1 ) === check + "-";
+ }
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ var doneName = done++;
+
+ return function( elem ) {
+ var parent, diff,
+ count = 0,
+ node = elem;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) {
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.sizset = ++count;
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.sizset - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ }
+
+ return function( elem ) {
+ var node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument, context, xml ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ var fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ];
+
+ if ( !fn ) {
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+ }
+
+ // The user may set fn.sizzleFilter to indicate
+ // that arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( !fn.sizzleFilter ) {
+ return fn;
+ }
+
+ return fn( argument, context, xml );
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector, context, xml ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var matcher = compile( selector.replace( rtrim, "$1" ), context, xml );
+ return function( elem ) {
+ return !matcher( elem );
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputFunction("radio"),
+ "checkbox": createInputFunction("checkbox"),
+ "file": createInputFunction("file"),
+ "password": createInputFunction("password"),
+ "image": createInputFunction("image"),
+
+ "submit": createButtonFunction("submit"),
+ "reset": createButtonFunction("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+
+ setFilters: {
+ "first": function( elements, argument, not ) {
+ return not ? elements.slice( 1 ) : [ elements[0] ];
+ },
+
+ "last": function( elements, argument, not ) {
+ var elem = elements.pop();
+ return not ? elements : [ elem ];
+ },
+
+ "even": function( elements, argument, not ) {
+ var results = [],
+ i = not ? 1 : 0,
+ len = elements.length;
+ for ( ; i < len; i = i + 2 ) {
+ results.push( elements[i] );
+ }
+ return results;
+ },
+
+ "odd": function( elements, argument, not ) {
+ var results = [],
+ i = not ? 0 : 1,
+ len = elements.length;
+ for ( ; i < len; i = i + 2 ) {
+ results.push( elements[i] );
+ }
+ return results;
+ },
+
+ "lt": function( elements, argument, not ) {
+ return not ? elements.slice( +argument ) : elements.slice( 0, +argument );
+ },
+
+ "gt": function( elements, argument, not ) {
+ return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 );
+ },
+
+ "eq": function( elements, argument, not ) {
+ var elem = elements.splice( +argument, 1 );
+ return not ? elements : elem;
+ }
+ }
+};
+
+// Deprecated
+Expr.setFilters["nth"] = Expr.setFilters["eq"];
+
+// Back-compat
+Expr.filters = Expr.pseudos;
+
+// IE6/7 return a modified href
+if ( !assertHrefNotNormalized ) {
+ Expr.attrHandle = {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ };
+}
+
+// Add getElementsByName if usable
+if ( assertUsableName ) {
+ Expr.order.push("NAME");
+ Expr.find["NAME"] = function( name, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ };
+}
+
+// Add getElementsByClassName if usable
+if ( assertUsableClassName ) {
+ Expr.order.splice( 1, 0, "CLASS" );
+ Expr.find["CLASS"] = function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+}
+
+// If slice is not available, provide a backup
+try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+ slice = function( i ) {
+ var elem, results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+}
+
+var isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+var contains = Sizzle.contains = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+};
+
+Sizzle.attr = function( elem, name ) {
+ var attr,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( Expr.attrHandle[ name ] ) {
+ return Expr.attrHandle[ name ]( elem );
+ }
+ if ( assertAttributes || xml ) {
+ return elem.getAttribute( name );
+ }
+ attr = elem.getAttributeNode( name );
+ return attr ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ attr.specified ? attr.value : null :
+ null;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+// Check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ return (baseHasDuplicate = 0);
+});
+
+
+if ( docElem.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ i = 1;
+
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+function multipleContexts( selector, contexts, results, seed ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results, seed );
+ }
+}
+
+function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) {
+ var results,
+ fn = Expr.setFilters[ posfilter.toLowerCase() ];
+
+ if ( !fn ) {
+ Sizzle.error( posfilter );
+ }
+
+ if ( selector || !(results = seed) ) {
+ multipleContexts( selector || "*", contexts, (results = []), seed );
+ }
+
+ return results.length > 0 ? fn( results, argument, not ) : [];
+}
+
+function handlePOS( selector, context, results, seed, groups ) {
+ var match, not, anchor, ret, elements, currentContexts, part, lastIndex,
+ i = 0,
+ len = groups.length,
+ rpos = matchExpr["POS"],
+ // This is generated here in case matchExpr["POS"] is extended
+ rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ),
+ // This is for making sure non-participating
+ // matching groups are represented cross-browser (IE6-8)
+ setUndefined = function() {
+ var i = 1,
+ len = arguments.length - 2;
+ for ( ; i < len; i++ ) {
+ if ( arguments[i] === undefined ) {
+ match[i] = undefined;
+ }
+ }
+ };
+
+ for ( ; i < len; i++ ) {
+ // Reset regex index to 0
+ rpos.exec("");
+ selector = groups[i];
+ ret = [];
+ anchor = 0;
+ elements = seed;
+ while ( (match = rpos.exec( selector )) ) {
+ lastIndex = rpos.lastIndex = match.index + match[0].length;
+ if ( lastIndex > anchor ) {
+ part = selector.slice( anchor, match.index );
+ anchor = lastIndex;
+ currentContexts = [ context ];
+
+ if ( rcombinators.test(part) ) {
+ if ( elements ) {
+ currentContexts = elements;
+ }
+ elements = seed;
+ }
+
+ if ( (not = rendsWithNot.test( part )) ) {
+ part = part.slice( 0, -5 ).replace( rcombinators, "$&*" );
+ }
+
+ if ( match.length > 1 ) {
+ match[0].replace( rposgroups, setUndefined );
+ }
+ elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not );
+ }
+ }
+
+ if ( elements ) {
+ ret = ret.concat( elements );
+
+ if ( (part = selector.slice( anchor )) && part !== ")" ) {
+ if ( rcombinators.test(part) ) {
+ multipleContexts( part, ret, results, seed );
+ } else {
+ Sizzle( part, context, results, seed ? seed.concat(elements) : elements );
+ }
+ } else {
+ push.apply( results, ret );
+ }
+ } else {
+ Sizzle( selector, context, results, seed );
+ }
+ }
+
+ // Do not sort if this is a single filter
+ return len === 1 ? results : Sizzle.uniqueSort( results );
+}
+
+function tokenize( selector, context, xml ) {
+ var tokens, soFar, type,
+ groups = [],
+ i = 0,
+
+ // Catch obvious selector issues: terminal ")"; nonempty fallback match
+ // rselector never fails to match *something*
+ match = rselector.exec( selector ),
+ matched = !match.pop() && !match.pop(),
+ selectorGroups = matched && selector.match( rgroups ) || [""],
+
+ preFilters = Expr.preFilter,
+ filters = Expr.filter,
+ checkContext = !xml && context !== document;
+
+ for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) {
+ groups.push( tokens = [] );
+
+ // Need to make sure we're within a narrower context if necessary
+ // Adding a descendant combinator will generate what is needed
+ if ( checkContext ) {
+ soFar = " " + soFar;
+ }
+
+ while ( soFar ) {
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ soFar = soFar.slice( match[0].length );
+
+ // Cast descendant combinators to space
+ matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match });
+ }
+
+ // Filters
+ for ( type in filters ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match, context, xml )) ) ) {
+
+ soFar = soFar.slice( match.shift().length );
+ matched = tokens.push({ part: type, captures: match });
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+ }
+
+ if ( !matched ) {
+ Sizzle.error( selector );
+ }
+
+ return groups;
+}
+
+function addCombinator( matcher, combinator, context ) {
+ var dir = combinator.dir,
+ doneName = done++;
+
+ if ( !matcher ) {
+ // If there is no matcher to check, check against the context
+ matcher = function( elem ) {
+ return elem === context;
+ };
+ }
+ return combinator.first ?
+ function( elem, context ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 ) {
+ return matcher( elem, context ) && elem;
+ }
+ }
+ } :
+ function( elem, context ) {
+ var cache,
+ dirkey = doneName + "." + dirruns,
+ cachedkey = dirkey + "." + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ };
+}
+
+function addMatcher( higher, deeper ) {
+ return higher ?
+ function( elem, context ) {
+ var result = deeper( elem, context );
+ return result && higher( result === true ? elem : result, context );
+ } :
+ deeper;
+}
+
+// ["TAG", ">", "ID", " ", "CLASS"]
+function matcherFromTokens( tokens, context, xml ) {
+ var token, matcher,
+ i = 0;
+
+ for ( ; (token = tokens[i]); i++ ) {
+ if ( Expr.relative[ token.part ] ) {
+ matcher = addCombinator( matcher, Expr.relative[ token.part ], context );
+ } else {
+ token.captures.push( context, xml );
+ matcher = addMatcher( matcher, Expr.filter[ token.part ].apply( null, token.captures ) );
+ }
+ }
+
+ return matcher;
+}
+
+function matcherFromGroupMatchers( matchers ) {
+ return function( elem, context ) {
+ var matcher,
+ j = 0;
+ for ( ; (matcher = matchers[j]); j++ ) {
+ if ( matcher(elem, context) ) {
+ return true;
+ }
+ }
+ return false;
+ };
+}
+
+var compile = Sizzle.compile = function( selector, context, xml ) {
+ var tokens, group, i,
+ cached = compilerCache[ selector ];
+
+ // Return a cached group function if already generated (context dependent)
+ if ( cached && cached.context === context ) {
+ return cached;
+ }
+
+ // Generate a function of recursive functions that can be used to check each element
+ group = tokenize( selector, context, xml );
+ for ( i = 0; (tokens = group[i]); i++ ) {
+ group[i] = matcherFromTokens( tokens, context, xml );
+ }
+
+ // Cache the compiled function
+ cached = compilerCache[ selector ] = matcherFromGroupMatchers( group );
+ cached.context = context;
+ cached.runs = cached.dirruns = 0;
+ cachedSelectors.push( selector );
+ // Ensure only the most recent are cached
+ if ( cachedSelectors.length > Expr.cacheLength ) {
+ delete compilerCache[ cachedSelectors.shift() ];
+ }
+ return cached;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+var select = function( selector, context, results, seed, xml ) {
+ // Remove excessive whitespace
+ selector = selector.replace( rtrim, "$1" );
+ var elements, matcher, i, len, elem, token,
+ type, findContext, notTokens,
+ match = selector.match( rgroups ),
+ tokens = selector.match( rtokens ),
+ contextNodeType = context.nodeType;
+
+ // POS handling
+ if ( matchExpr["POS"].test(selector) ) {
+ return handlePOS( selector, context, results, seed, match );
+ }
+
+ if ( seed ) {
+ elements = slice.call( seed, 0 );
+
+ // To maintain document order, only narrow the
+ // set if there is one group
+ } else if ( match && match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ if ( tokens.length > 1 && contextNodeType === 9 && !xml &&
+ (match = matchExpr["ID"].exec( tokens[0] )) ) {
+
+ context = Expr.find["ID"]( match[1], context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ findContext = ( (match = rsibling.exec( tokens[0] )) && !match.index && context.parentNode ) || context;
+
+ // Get the last token, excluding :not
+ notTokens = tokens.pop();
+ token = notTokens.split(":not")[0];
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = matchExpr[ type ].exec( token )) ) {
+ elements = Expr.find[ type ]( (match[1] || "").replace( rbackslash, "" ), findContext, xml );
+
+ if ( elements == null ) {
+ continue;
+ }
+
+ if ( token === notTokens ) {
+ selector = selector.slice( 0, selector.length - notTokens.length ) +
+ token.replace( matchExpr[ type ], "" );
+
+ if ( !selector ) {
+ push.apply( results, slice.call(elements, 0) );
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // Only loop over the given elements once
+ // If selector is empty, we're already done
+ if ( selector ) {
+ matcher = compile( selector, context, xml );
+ dirruns = matcher.dirruns++;
+
+ if ( elements == null ) {
+ elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context );
+ }
+ for ( i = 0; (elem = elements[i]); i++ ) {
+ cachedruns = matcher.runs++;
+ if ( matcher(elem, context) ) {
+ results.push( elem );
+ }
+ }
+ }
+
+ return results;
+};
+
+if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+ rbuggyQSA = [],
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [":active"],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ div.innerHTML = "<select><option selected></option></select>";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "<p test=''></p>";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = "<input type='hidden'>";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ if ( context.nodeType === 9 ) {
+ try {
+ push.apply( results, slice.call(context.querySelectorAll( selector ), 0) );
+ return results;
+ } catch(qsaError) {}
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var old = context.getAttribute("id"),
+ nid = old || expando,
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+
+ if ( old ) {
+ nid = nid.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ selector.replace( rgroups, "[id='" + nid + "'] $&" )
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( Expr.match.PSEUDO );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+}
+
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i, l, length, n, r, ret,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ ret = this.pushStack( "", "find", selector );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ rneedsContext.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ ret = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( this.length > 1 && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ rcheckableType = /^(?:checkbox|radio)$/,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "X<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+ }
+ },
+
+ after: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( !isDisconnected( this[0] ) ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ }
+
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+
+ // Flatten any nested arrays
+ args = [].concat.apply( [], args );
+
+ var results, first, fragment, iNoClone,
+ i = 0,
+ value = args[0],
+ scripts = [],
+ l = this.length;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call( this, i, table ? self.html() : undefined );
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ results = jQuery.buildFragment( args, this, scripts );
+ fragment = results.fragment;
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ // Fragments from the fragment cache must always be cloned and never used in place.
+ for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+ callback.call(
+ table && jQuery.nodeName( this[i], "table" ) ?
+ findOrAppend( this[i], "tbody" ) :
+ this[i],
+ i === iNoClone ?
+ fragment :
+ jQuery.clone( fragment, true, true )
+ );
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ if ( jQuery.ajax ) {
+ jQuery.ajax({
+ url: elem.src,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ } else {
+ jQuery.error("no ajax");
+ }
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function findOrAppend( elem, tag ) {
+ return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ if ( nodeName === "object" ) {
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, context, scripts ) {
+ var fragment, cacheable, cachehit,
+ first = args[ 0 ];
+
+ // Set context from what may come in as undefined or a jQuery collection or a node
+ context = context || document;
+ context = (context[0] || context).ownerDocument || context[0] || context;
+
+ // Ensure that an attr object doesn't incorrectly stand in as a document object
+ // Chrome and Firefox seem to allow this to occur and will throw exception
+ // Fixes #8950
+ if ( typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ // Mark cacheable and look for a hit
+ cacheable = true;
+ fragment = jQuery.fragments[ first ];
+ cachehit = fragment !== undefined;
+ }
+
+ if ( !fragment ) {
+ fragment = context.createDocumentFragment();
+ jQuery.clean( args, context, fragment, scripts );
+
+ // Update the cache, but only store false
+ // unless this is a second parsing of the same content
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cachehit && fragment;
+ }
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ l = insert.length,
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+ } else {
+ for ( ; i < l; i++ ) {
+ elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ clone;
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var j, safe, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+ i = 0,
+ ret = [];
+
+ // Ensure that context is a document
+ if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Use the already-created safe fragment if context permits
+ for ( safe = context === document && safeFragment; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Ensure a safe container in which to render the html
+ safe = safe || createSafeFragment( context );
+ div = div || safe.appendChild( context.createElement("div") );
+
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Go to html and back, then peel off extra wrappers
+ tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ depth = wrap[0];
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ hasBody = rtbody.test(elem);
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Remember the top-level container for proper cleanup
+ div = safe.lastChild;
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ // Fix #11356: Clear elements from safeFragment
+ if ( div ) {
+ safe.removeChild( div );
+ elem = div = safe = null;
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+ }
+ }
+
+ // Append elements to a provided document fragment
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var data, id, elem, type,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = jQuery.support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ jQuery.deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+});
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+// Maintained for back-compat only
+if ( browser.webkit ) {
+ browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+};
+
+})();
+var curCSS, iframe, iframeDoc,
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rposition = /^(top|right|bottom|left)$/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+ elemdisplay = {},
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400,
+ lineHeight: 1
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+ eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function isHidden( elem, el ) {
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+ var elem, display,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && elem.style.display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ display = curCSS( elem, "display" );
+
+ if ( !values[ index ] && display !== "none" ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state, fn2 ) {
+ var bool = typeof state === "boolean";
+
+ if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+ return eventsToggle.apply( this, arguments );
+ }
+
+ return this.each(function() {
+ if ( bool ? state : isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, numeric, extra ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( numeric || extra !== undefined ) {
+ num = parseFloat( val );
+ return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// NOTE: To any future maintainer, we've used both window.getComputedStyle
+// and getComputedStyle here to produce a better gzip size
+if ( window.getComputedStyle ) {
+ curCSS = function( elem, name ) {
+ var ret, width, minWidth, maxWidth,
+ computed = getComputedStyle( elem, null ),
+ style = elem.style;
+
+ if ( computed ) {
+
+ ret = computed[ name ];
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+} else if ( document.documentElement.currentStyle ) {
+ curCSS = function( elem, name ) {
+ var left, rsLeft,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ // we use jQuery.css instead of curCSS here
+ // because of the reliableMarginRight CSS hook!
+ val += jQuery.css( elem, extra + cssExpand[ i ], true );
+ }
+
+ // From this point on we use curCSS for maximum performance (relevant in animations)
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ valueIsBorderBox = true,
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+ if ( val <= 0 ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox
+ )
+ ) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ if ( elemdisplay[ nodeName ] ) {
+ return elemdisplay[ nodeName ];
+ }
+
+ var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+ display = elem.css("display");
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // Use the already-created iframe if possible
+ iframe = document.body.appendChild(
+ iframe || jQuery.extend( document.createElement("iframe"), {
+ frameBorder: 0,
+ width: 0,
+ height: 0
+ })
+ );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write("<!doctype html><html><body>");
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+ display = curCSS( elem, "display" );
+ document.body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+
+ return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 || curCSS( elem, "display" ) !== "none" ) {
+ return getWidthOrHeight( elem, name, extra );
+ } else {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ }
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+ ) : 0
+ );
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "marginRight" );
+ }
+ });
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ var ret = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+var // Document location
+ ajaxLocation,
+ // Document location segments
+ ajaxLocParts,
+
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType, list, placeBefore,
+ dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
+ i = 0,
+ length = dataTypes.length;
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var selection,
+ list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters );
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ // Don't do a request if no elements are being requested
+ if ( !this.length ) {
+ return this;
+ }
+
+ var selector, type, response,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function( jqXHR, status ) {
+ if ( callback ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ }
+ }
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ // See if a selector was specified
+ self.html( selector ?
+
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append( responseText.replace( rscript, "" ) )
+
+ // Locate the specified elements
+ .find( selector ) :
+
+ // If not, just inject the full result
+ responseText );
+
+ });
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // ifModified key
+ ifModifiedKey,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ ifModifiedKey ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("Etag");
+ if ( modified ) {
+ jQuery.etag[ ifModifiedKey ] = modified;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ isSuccess = ajaxConvert( s, response );
+ statusText = isSuccess.state;
+ success = isSuccess.data;
+ error = isSuccess.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.always( tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ var conv, conv2, current, tmp,
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice(),
+ prev = dataTypes[ 0 ],
+ converters = {},
+ i = 0;
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ // Convert to each sequential dataType, tolerating list modification
+ for ( ; (current = dataTypes[++i]); ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current !== "*" ) {
+
+ // Convert response if prev dataType is non-auto and differs from current
+ if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split(" ");
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.splice( i--, 0, current );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s["throws"] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+
+ // Update prev for next iteration
+ prev = current;
+ }
+ }
+
+ return { state: "success", data: response };
+}
+var oldCallbacks = [],
+ rquestion = /\?/,
+ rjsonp = /(=)\?(?=&|$)|\?\?/,
+ nonce = jQuery.now();
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ data = s.data,
+ url = s.url,
+ hasCallback = s.jsonp !== false,
+ replaceInUrl = hasCallback && rjsonp.test( url ),
+ replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
+ !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
+ rjsonp.test( data );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+ overwritten = window[ callbackName ];
+
+ // Insert callback into url or form data
+ if ( replaceInUrl ) {
+ s.url = url.replace( rjsonp, "$1" + callbackName );
+ } else if ( replaceInData ) {
+ s.data = data.replace( rjsonp, "$1" + callbackName );
+ } else if ( hasCallback ) {
+ s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+var xhrCallbacks,
+ // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occurred
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( _ ) {
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ if ( !s.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback, 0 );
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var end, unit, prevScale,
+ tween = this.createTween( prop, value ),
+ parts = rfxnum.exec( value ),
+ target = tween.cur(),
+ start = +target || 0,
+ scale = 1;
+
+ if ( parts ) {
+ end = +parts[2];
+ unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" && start ) {
+ // Iteratively approximate from a nonzero starting point
+ // Prefer the current property, because this process will be trivial if it uses the same units
+ // Fallback to end or a simple constant
+ start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ prevScale = scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zeroes from tween.cur()
+ scale = tween.cur() / target;
+
+ // Stop looping if we've hit the mark or scale is unchanged
+ } while ( scale !== 1 && scale !== prevScale );
+ }
+
+ tween.unit = unit;
+ tween.start = start;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+ }
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ }, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+ jQuery.each( props, function( prop, value ) {
+ var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( collection[ index ].call( animation, prop, value ) ) {
+
+ // we're done with this property
+ return;
+ }
+ }
+ });
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ index = 0,
+ tweenerIndex = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ percent = 1 - ( remaining / animation.duration || 0 ),
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end, easing ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ anim: animation,
+ queue: animation.opts.queue,
+ elem: elem
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ var index, prop, value, length, dataShow, tween, hooks, oldfire,
+ anim = this,
+ style = elem.style,
+ orig = {},
+ handled = [],
+ hidden = elem.nodeType && isHidden( elem );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !jQuery.support.shrinkWrapBlocks ) {
+ anim.done(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+
+ // show/hide pass
+ for ( index in props ) {
+ value = props[ index ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ index ];
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+ continue;
+ }
+ handled.push( index );
+ }
+ }
+
+ length = handled.length;
+ if ( length ) {
+ dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery.removeData( elem, "fxshow", true );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( index = 0 ; index < length ; index++ ) {
+ prop = handled[ index ];
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration );
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing any value as a 4th parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, false, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ||
+ // special check for .toggle( handler, handler, ... )
+ ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations resolve immediately
+ if ( empty ) {
+ anim.stop( true );
+ }
+ };
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+var rroot = /^(?:body|html)$/i;
+
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left,
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ if ( (body = doc.body) === elem ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return { top: 0, left: 0 };
+ }
+
+ box = elem.getBoundingClientRect();
+ win = getWindow( doc );
+ clientTop = docElem.clientTop || body.clientTop || 0;
+ clientLeft = docElem.clientLeft || body.clientLeft || 0;
+ scrollTop = win.pageYOffset || docElem.scrollTop;
+ scrollLeft = win.pageXOffset || docElem.scrollLeft;
+ top = box.top + scrollTop - clientTop;
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+};
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || document.body;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, value, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable );
+ };
+ });
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window ); \ No newline at end of file
diff --git a/module/web/static/js/libs/jquery.fastClick-0.2.js b/module/web/static/js/libs/jquery.fastClick-0.2.js
new file mode 100644
index 000000000..49eb75d2a
--- /dev/null
+++ b/module/web/static/js/libs/jquery.fastClick-0.2.js
@@ -0,0 +1,96 @@
+/**
+ * jQuery.fastClick.js
+ *
+ * Work around the 300ms delay for the click event in some mobile browsers.
+ *
+ * Code based on <http://code.google.com/mobile/articles/fast_buttons.html>
+ *
+ * @usage
+ * $('button').fastClick(function() {alert('clicked!');});
+ *
+ * @license Under Creative Commons Attribution 3.0 License
+ * @author Dave Hulbert (dave1010)
+ * @version 0.2 2011-09-20
+ */
+
+/*global document, window, jQuery, Math */
+
+(function($) {
+
+$.fn.fastClick = function(handler) {
+ return $(this).each(function(){
+ $.FastButton($(this)[0], handler);
+ });
+};
+
+$.FastButton = function(element, handler) {
+ var startX, startY;
+
+ var reset = function() {
+ $(element).unbind('touchend');
+ $("body").unbind('touchmove.fastClick');
+ };
+
+ var onClick = function(event) {
+ event.stopPropagation();
+ reset();
+ handler.call(this, event);
+
+ if (event.type === 'touchend') {
+ $.clickbuster.preventGhostClick(startX, startY);
+ }
+ };
+
+ var onTouchMove = function(event) {
+ if (Math.abs(event.originalEvent.touches[0].clientX - startX) > 10 ||
+ Math.abs(event.originalEvent.touches[0].clientY - startY) > 10) {
+ reset();
+ }
+ };
+
+ var onTouchStart = function(event) {
+ event.stopPropagation();
+
+ $(element).bind('touchend', onClick);
+ $("body").bind('touchmove.fastClick', onTouchMove);
+
+ startX = event.originalEvent.touches[0].clientX;
+ startY = event.originalEvent.touches[0].clientY;
+ };
+
+ $(element).bind({
+ touchstart: onTouchStart,
+ click: onClick
+ });
+};
+
+$.clickbuster = {
+ coordinates: [],
+
+ preventGhostClick: function(x, y) {
+ $.clickbuster.coordinates.push(x, y);
+ window.setTimeout($.clickbuster.pop, 2500);
+ },
+
+ pop: function() {
+ $.clickbuster.coordinates.splice(0, 2);
+ },
+
+ onClick: function(event) {
+ var x, y, i;
+ for (i = 0; i < $.clickbuster.coordinates.length; i += 2) {
+ x = $.clickbuster.coordinates[i];
+ y = $.clickbuster.coordinates[i + 1];
+ if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+ }
+};
+
+$(function(){
+ document.addEventListener('click', $.clickbuster.onClick, true);
+});
+
+}(jQuery));
diff --git a/module/web/static/js/libs/jquery.flot.min.js b/module/web/static/js/libs/jquery.flot.min.js
new file mode 100644
index 000000000..4467fc5d8
--- /dev/null
+++ b/module/web/static/js/libs/jquery.flot.min.js
@@ -0,0 +1,6 @@
+/* Javascript plotting library for jQuery, v. 0.7.
+ *
+ * Released under the MIT license by IOLA, December 2007.
+ *
+ */
+(function(b){b.color={};b.color.make=function(d,e,g,f){var c={};c.r=d||0;c.g=e||0;c.b=g||0;c.a=f!=null?f:1;c.add=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]+=j}return c.normalize()};c.scale=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]*=j}return c.normalize()};c.toString=function(){if(c.a>=1){return"rgb("+[c.r,c.g,c.b].join(",")+")"}else{return"rgba("+[c.r,c.g,c.b,c.a].join(",")+")"}};c.normalize=function(){function h(k,j,l){return j<k?k:(j>l?l:j)}c.r=h(0,parseInt(c.r),255);c.g=h(0,parseInt(c.g),255);c.b=h(0,parseInt(c.b),255);c.a=h(0,c.a,1);return c};c.clone=function(){return b.color.make(c.r,c.b,c.g,c.a)};return c.normalize()};b.color.extract=function(d,e){var c;do{c=d.css(e).toLowerCase();if(c!=""&&c!="transparent"){break}d=d.parent()}while(!b.nodeName(d.get(0),"body"));if(c=="rgba(0, 0, 0, 0)"){c="transparent"}return b.color.parse(c)};b.color.parse=function(c){var d,f=b.color.make;if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10))}if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]))}if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55)}if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]))}if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c)){return f(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16))}if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c)){return f(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16))}var e=b.trim(c).toLowerCase();if(e=="transparent"){return f(255,255,255,0)}else{d=a[e]||[0,0,0];return f(d[0],d[1],d[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function(c){function b(av,ai,J,af){var Q=[],O={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{show:null,position:"bottom",mode:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},az=null,ad=null,y=null,H=null,A=null,p=[],aw=[],q={left:0,right:0,top:0,bottom:0},G=0,I=0,h=0,w=0,ak={processOptions:[],processRawData:[],processDatapoints:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},aq=this;aq.setData=aj;aq.setupGrid=t;aq.draw=W;aq.getPlaceholder=function(){return av};aq.getCanvas=function(){return az};aq.getPlotOffset=function(){return q};aq.width=function(){return h};aq.height=function(){return w};aq.offset=function(){var aB=y.offset();aB.left+=q.left;aB.top+=q.top;return aB};aq.getData=function(){return Q};aq.getAxes=function(){var aC={},aB;c.each(p.concat(aw),function(aD,aE){if(aE){aC[aE.direction+(aE.n!=1?aE.n:"")+"axis"]=aE}});return aC};aq.getXAxes=function(){return p};aq.getYAxes=function(){return aw};aq.c2p=C;aq.p2c=ar;aq.getOptions=function(){return O};aq.highlight=x;aq.unhighlight=T;aq.triggerRedrawOverlay=f;aq.pointOffset=function(aB){return{left:parseInt(p[aA(aB,"x")-1].p2c(+aB.x)+q.left),top:parseInt(aw[aA(aB,"y")-1].p2c(+aB.y)+q.top)}};aq.shutdown=ag;aq.resize=function(){B();g(az);g(ad)};aq.hooks=ak;F(aq);Z(J);X();aj(ai);t();W();ah();function an(aD,aB){aB=[aq].concat(aB);for(var aC=0;aC<aD.length;++aC){aD[aC].apply(this,aB)}}function F(){for(var aB=0;aB<af.length;++aB){var aC=af[aB];aC.init(aq);if(aC.options){c.extend(true,O,aC.options)}}}function Z(aC){var aB;c.extend(true,O,aC);if(O.xaxis.color==null){O.xaxis.color=O.grid.color}if(O.yaxis.color==null){O.yaxis.color=O.grid.color}if(O.xaxis.tickColor==null){O.xaxis.tickColor=O.grid.tickColor}if(O.yaxis.tickColor==null){O.yaxis.tickColor=O.grid.tickColor}if(O.grid.borderColor==null){O.grid.borderColor=O.grid.color}if(O.grid.tickColor==null){O.grid.tickColor=c.color.parse(O.grid.color).scale("a",0.22).toString()}for(aB=0;aB<Math.max(1,O.xaxes.length);++aB){O.xaxes[aB]=c.extend(true,{},O.xaxis,O.xaxes[aB])}for(aB=0;aB<Math.max(1,O.yaxes.length);++aB){O.yaxes[aB]=c.extend(true,{},O.yaxis,O.yaxes[aB])}if(O.xaxis.noTicks&&O.xaxis.ticks==null){O.xaxis.ticks=O.xaxis.noTicks}if(O.yaxis.noTicks&&O.yaxis.ticks==null){O.yaxis.ticks=O.yaxis.noTicks}if(O.x2axis){O.xaxes[1]=c.extend(true,{},O.xaxis,O.x2axis);O.xaxes[1].position="top"}if(O.y2axis){O.yaxes[1]=c.extend(true,{},O.yaxis,O.y2axis);O.yaxes[1].position="right"}if(O.grid.coloredAreas){O.grid.markings=O.grid.coloredAreas}if(O.grid.coloredAreasColor){O.grid.markingsColor=O.grid.coloredAreasColor}if(O.lines){c.extend(true,O.series.lines,O.lines)}if(O.points){c.extend(true,O.series.points,O.points)}if(O.bars){c.extend(true,O.series.bars,O.bars)}if(O.shadowSize!=null){O.series.shadowSize=O.shadowSize}for(aB=0;aB<O.xaxes.length;++aB){V(p,aB+1).options=O.xaxes[aB]}for(aB=0;aB<O.yaxes.length;++aB){V(aw,aB+1).options=O.yaxes[aB]}for(var aD in ak){if(O.hooks[aD]&&O.hooks[aD].length){ak[aD]=ak[aD].concat(O.hooks[aD])}}an(ak.processOptions,[O])}function aj(aB){Q=Y(aB);ax();z()}function Y(aE){var aC=[];for(var aB=0;aB<aE.length;++aB){var aD=c.extend(true,{},O.series);if(aE[aB].data!=null){aD.data=aE[aB].data;delete aE[aB].data;c.extend(true,aD,aE[aB]);aE[aB].data=aD.data}else{aD.data=aE[aB]}aC.push(aD)}return aC}function aA(aC,aD){var aB=aC[aD+"axis"];if(typeof aB=="object"){aB=aB.n}if(typeof aB!="number"){aB=1}return aB}function m(){return c.grep(p.concat(aw),function(aB){return aB})}function C(aE){var aC={},aB,aD;for(aB=0;aB<p.length;++aB){aD=p[aB];if(aD&&aD.used){aC["x"+aD.n]=aD.c2p(aE.left)}}for(aB=0;aB<aw.length;++aB){aD=aw[aB];if(aD&&aD.used){aC["y"+aD.n]=aD.c2p(aE.top)}}if(aC.x1!==undefined){aC.x=aC.x1}if(aC.y1!==undefined){aC.y=aC.y1}return aC}function ar(aF){var aD={},aC,aE,aB;for(aC=0;aC<p.length;++aC){aE=p[aC];if(aE&&aE.used){aB="x"+aE.n;if(aF[aB]==null&&aE.n==1){aB="x"}if(aF[aB]!=null){aD.left=aE.p2c(aF[aB]);break}}}for(aC=0;aC<aw.length;++aC){aE=aw[aC];if(aE&&aE.used){aB="y"+aE.n;if(aF[aB]==null&&aE.n==1){aB="y"}if(aF[aB]!=null){aD.top=aE.p2c(aF[aB]);break}}}return aD}function V(aC,aB){if(!aC[aB-1]){aC[aB-1]={n:aB,direction:aC==p?"x":"y",options:c.extend(true,{},aC==p?O.xaxis:O.yaxis)}}return aC[aB-1]}function ax(){var aG;var aM=Q.length,aB=[],aE=[];for(aG=0;aG<Q.length;++aG){var aJ=Q[aG].color;if(aJ!=null){--aM;if(typeof aJ=="number"){aE.push(aJ)}else{aB.push(c.color.parse(Q[aG].color))}}}for(aG=0;aG<aE.length;++aG){aM=Math.max(aM,aE[aG]+1)}var aC=[],aF=0;aG=0;while(aC.length<aM){var aI;if(O.colors.length==aG){aI=c.color.make(100,100,100)}else{aI=c.color.parse(O.colors[aG])}var aD=aF%2==1?-1:1;aI.scale("rgb",1+aD*Math.ceil(aF/2)*0.2);aC.push(aI);++aG;if(aG>=O.colors.length){aG=0;++aF}}var aH=0,aN;for(aG=0;aG<Q.length;++aG){aN=Q[aG];if(aN.color==null){aN.color=aC[aH].toString();++aH}else{if(typeof aN.color=="number"){aN.color=aC[aN.color].toString()}}if(aN.lines.show==null){var aL,aK=true;for(aL in aN){if(aN[aL]&&aN[aL].show){aK=false;break}}if(aK){aN.lines.show=true}}aN.xaxis=V(p,aA(aN,"x"));aN.yaxis=V(aw,aA(aN,"y"))}}function z(){var aO=Number.POSITIVE_INFINITY,aI=Number.NEGATIVE_INFINITY,aB=Number.MAX_VALUE,aU,aS,aR,aN,aD,aJ,aT,aP,aH,aG,aC,a0,aX,aL;function aF(a3,a2,a1){if(a2<a3.datamin&&a2!=-aB){a3.datamin=a2}if(a1>a3.datamax&&a1!=aB){a3.datamax=a1}}c.each(m(),function(a1,a2){a2.datamin=aO;a2.datamax=aI;a2.used=false});for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aJ.datapoints={points:[]};an(ak.processRawData,[aJ,aJ.data,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];var aZ=aJ.data,aW=aJ.datapoints.format;if(!aW){aW=[];aW.push({x:true,number:true,required:true});aW.push({y:true,number:true,required:true});if(aJ.bars.show||(aJ.lines.show&&aJ.lines.fill)){aW.push({y:true,number:true,required:false,defaultValue:0});if(aJ.bars.horizontal){delete aW[aW.length-1].y;aW[aW.length-1].x=true}}aJ.datapoints.format=aW}if(aJ.datapoints.pointsize!=null){continue}aJ.datapoints.pointsize=aW.length;aP=aJ.datapoints.pointsize;aT=aJ.datapoints.points;insertSteps=aJ.lines.show&&aJ.lines.steps;aJ.xaxis.used=aJ.yaxis.used=true;for(aS=aR=0;aS<aZ.length;++aS,aR+=aP){aL=aZ[aS];var aE=aL==null;if(!aE){for(aN=0;aN<aP;++aN){a0=aL[aN];aX=aW[aN];if(aX){if(aX.number&&a0!=null){a0=+a0;if(isNaN(a0)){a0=null}else{if(a0==Infinity){a0=aB}else{if(a0==-Infinity){a0=-aB}}}}if(a0==null){if(aX.required){aE=true}if(aX.defaultValue!=null){a0=aX.defaultValue}}}aT[aR+aN]=a0}}if(aE){for(aN=0;aN<aP;++aN){a0=aT[aR+aN];if(a0!=null){aX=aW[aN];if(aX.x){aF(aJ.xaxis,a0,a0)}if(aX.y){aF(aJ.yaxis,a0,a0)}}aT[aR+aN]=null}}else{if(insertSteps&&aR>0&&aT[aR-aP]!=null&&aT[aR-aP]!=aT[aR]&&aT[aR-aP+1]!=aT[aR+1]){for(aN=0;aN<aP;++aN){aT[aR+aP+aN]=aT[aR+aN]}aT[aR+1]=aT[aR-aP+1];aR+=aP}}}}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];an(ak.processDatapoints,[aJ,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aT=aJ.datapoints.points,aP=aJ.datapoints.pointsize;var aK=aO,aQ=aO,aM=aI,aV=aI;for(aS=0;aS<aT.length;aS+=aP){if(aT[aS]==null){continue}for(aN=0;aN<aP;++aN){a0=aT[aS+aN];aX=aW[aN];if(!aX||a0==aB||a0==-aB){continue}if(aX.x){if(a0<aK){aK=a0}if(a0>aM){aM=a0}}if(aX.y){if(a0<aQ){aQ=a0}if(a0>aV){aV=a0}}}}if(aJ.bars.show){var aY=aJ.bars.align=="left"?0:-aJ.bars.barWidth/2;if(aJ.bars.horizontal){aQ+=aY;aV+=aY+aJ.bars.barWidth}else{aK+=aY;aM+=aY+aJ.bars.barWidth}}aF(aJ.xaxis,aK,aM);aF(aJ.yaxis,aQ,aV)}c.each(m(),function(a1,a2){if(a2.datamin==aO){a2.datamin=null}if(a2.datamax==aI){a2.datamax=null}})}function j(aB,aC){var aD=document.createElement("canvas");aD.className=aC;aD.width=G;aD.height=I;if(!aB){c(aD).css({position:"absolute",left:0,top:0})}c(aD).appendTo(av);if(!aD.getContext){aD=window.G_vmlCanvasManager.initElement(aD)}aD.getContext("2d").save();return aD}function B(){G=av.width();I=av.height();if(G<=0||I<=0){throw"Invalid dimensions for plot, width = "+G+", height = "+I}}function g(aC){if(aC.width!=G){aC.width=G}if(aC.height!=I){aC.height=I}var aB=aC.getContext("2d");aB.restore();aB.save()}function X(){var aC,aB=av.children("canvas.base"),aD=av.children("canvas.overlay");if(aB.length==0||aD==0){av.html("");av.css({padding:0});if(av.css("position")=="static"){av.css("position","relative")}B();az=j(true,"base");ad=j(false,"overlay");aC=false}else{az=aB.get(0);ad=aD.get(0);aC=true}H=az.getContext("2d");A=ad.getContext("2d");y=c([ad,az]);if(aC){av.data("plot").shutdown();aq.resize();A.clearRect(0,0,G,I);y.unbind();av.children().not([az,ad]).remove()}av.data("plot",aq)}function ah(){if(O.grid.hoverable){y.mousemove(aa);y.mouseleave(l)}if(O.grid.clickable){y.click(R)}an(ak.bindEvents,[y])}function ag(){if(M){clearTimeout(M)}y.unbind("mousemove",aa);y.unbind("mouseleave",l);y.unbind("click",R);an(ak.shutdown,[y])}function r(aG){function aC(aH){return aH}var aF,aB,aD=aG.options.transform||aC,aE=aG.options.inverseTransform;if(aG.direction=="x"){aF=aG.scale=h/Math.abs(aD(aG.max)-aD(aG.min));aB=Math.min(aD(aG.max),aD(aG.min))}else{aF=aG.scale=w/Math.abs(aD(aG.max)-aD(aG.min));aF=-aF;aB=Math.max(aD(aG.max),aD(aG.min))}if(aD==aC){aG.p2c=function(aH){return(aH-aB)*aF}}else{aG.p2c=function(aH){return(aD(aH)-aB)*aF}}if(!aE){aG.c2p=function(aH){return aB+aH/aF}}else{aG.c2p=function(aH){return aE(aB+aH/aF)}}}function L(aD){var aB=aD.options,aF,aJ=aD.ticks||[],aI=[],aE,aK=aB.labelWidth,aG=aB.labelHeight,aC;function aH(aM,aL){return c('<div style="position:absolute;top:-10000px;'+aL+'font-size:smaller"><div class="'+aD.direction+"Axis "+aD.direction+aD.n+'Axis">'+aM.join("")+"</div></div>").appendTo(av)}if(aD.direction=="x"){if(aK==null){aK=Math.floor(G/(aJ.length>0?aJ.length:1))}if(aG==null){aI=[];for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel" style="float:left;width:'+aK+'px">'+aE+"</div>")}}if(aI.length>0){aI.push('<div style="clear:left"></div>');aC=aH(aI,"width:10000px;");aG=aC.height();aC.remove()}}}else{if(aK==null||aG==null){for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel">'+aE+"</div>")}}if(aI.length>0){aC=aH(aI,"");if(aK==null){aK=aC.children().width()}if(aG==null){aG=aC.find("div.tickLabel").height()}aC.remove()}}}if(aK==null){aK=0}if(aG==null){aG=0}aD.labelWidth=aK;aD.labelHeight=aG}function au(aD){var aC=aD.labelWidth,aL=aD.labelHeight,aH=aD.options.position,aF=aD.options.tickLength,aG=O.grid.axisMargin,aJ=O.grid.labelMargin,aK=aD.direction=="x"?p:aw,aE;var aB=c.grep(aK,function(aN){return aN&&aN.options.position==aH&&aN.reserveSpace});if(c.inArray(aD,aB)==aB.length-1){aG=0}if(aF==null){aF="full"}var aI=c.grep(aK,function(aN){return aN&&aN.reserveSpace});var aM=c.inArray(aD,aI)==0;if(!aM&&aF=="full"){aF=5}if(!isNaN(+aF)){aJ+=+aF}if(aD.direction=="x"){aL+=aJ;if(aH=="bottom"){q.bottom+=aL+aG;aD.box={top:I-q.bottom,height:aL}}else{aD.box={top:q.top+aG,height:aL};q.top+=aL+aG}}else{aC+=aJ;if(aH=="left"){aD.box={left:q.left+aG,width:aC};q.left+=aC+aG}else{q.right+=aC+aG;aD.box={left:G-q.right,width:aC}}}aD.position=aH;aD.tickLength=aF;aD.box.padding=aJ;aD.innermost=aM}function U(aB){if(aB.direction=="x"){aB.box.left=q.left;aB.box.width=h}else{aB.box.top=q.top;aB.box.height=w}}function t(){var aC,aE=m();c.each(aE,function(aF,aG){aG.show=aG.options.show;if(aG.show==null){aG.show=aG.used}aG.reserveSpace=aG.show||aG.options.reserveSpace;n(aG)});allocatedAxes=c.grep(aE,function(aF){return aF.reserveSpace});q.left=q.right=q.top=q.bottom=0;if(O.grid.show){c.each(allocatedAxes,function(aF,aG){S(aG);P(aG);ap(aG,aG.ticks);L(aG)});for(aC=allocatedAxes.length-1;aC>=0;--aC){au(allocatedAxes[aC])}var aD=O.grid.minBorderMargin;if(aD==null){aD=0;for(aC=0;aC<Q.length;++aC){aD=Math.max(aD,Q[aC].points.radius+Q[aC].points.lineWidth/2)}}for(var aB in q){q[aB]+=O.grid.borderWidth;q[aB]=Math.max(aD,q[aB])}}h=G-q.left-q.right;w=I-q.bottom-q.top;c.each(aE,function(aF,aG){r(aG)});if(O.grid.show){c.each(allocatedAxes,function(aF,aG){U(aG)});k()}o()}function n(aE){var aF=aE.options,aD=+(aF.min!=null?aF.min:aE.datamin),aB=+(aF.max!=null?aF.max:aE.datamax),aH=aB-aD;if(aH==0){var aC=aB==0?1:0.01;if(aF.min==null){aD-=aC}if(aF.max==null||aF.min!=null){aB+=aC}}else{var aG=aF.autoscaleMargin;if(aG!=null){if(aF.min==null){aD-=aH*aG;if(aD<0&&aE.datamin!=null&&aE.datamin>=0){aD=0}}if(aF.max==null){aB+=aH*aG;if(aB>0&&aE.datamax!=null&&aE.datamax<=0){aB=0}}}}aE.min=aD;aE.max=aB}function S(aG){var aM=aG.options;var aH;if(typeof aM.ticks=="number"&&aM.ticks>0){aH=aM.ticks}else{aH=0.3*Math.sqrt(aG.direction=="x"?G:I)}var aT=(aG.max-aG.min)/aH,aO,aB,aN,aR,aS,aQ,aI;if(aM.mode=="time"){var aJ={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var aK=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var aC=0;if(aM.minTickSize!=null){if(typeof aM.tickSize=="number"){aC=aM.tickSize}else{aC=aM.minTickSize[0]*aJ[aM.minTickSize[1]]}}for(var aS=0;aS<aK.length-1;++aS){if(aT<(aK[aS][0]*aJ[aK[aS][1]]+aK[aS+1][0]*aJ[aK[aS+1][1]])/2&&aK[aS][0]*aJ[aK[aS][1]]>=aC){break}}aO=aK[aS][0];aN=aK[aS][1];if(aN=="year"){aQ=Math.pow(10,Math.floor(Math.log(aT/aJ.year)/Math.LN10));aI=(aT/aJ.year)/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ}aG.tickSize=aM.tickSize||[aO,aN];aB=function(aX){var a2=[],a0=aX.tickSize[0],a3=aX.tickSize[1],a1=new Date(aX.min);var aW=a0*aJ[a3];if(a3=="second"){a1.setUTCSeconds(a(a1.getUTCSeconds(),a0))}if(a3=="minute"){a1.setUTCMinutes(a(a1.getUTCMinutes(),a0))}if(a3=="hour"){a1.setUTCHours(a(a1.getUTCHours(),a0))}if(a3=="month"){a1.setUTCMonth(a(a1.getUTCMonth(),a0))}if(a3=="year"){a1.setUTCFullYear(a(a1.getUTCFullYear(),a0))}a1.setUTCMilliseconds(0);if(aW>=aJ.minute){a1.setUTCSeconds(0)}if(aW>=aJ.hour){a1.setUTCMinutes(0)}if(aW>=aJ.day){a1.setUTCHours(0)}if(aW>=aJ.day*4){a1.setUTCDate(1)}if(aW>=aJ.year){a1.setUTCMonth(0)}var a5=0,a4=Number.NaN,aY;do{aY=a4;a4=a1.getTime();a2.push(a4);if(a3=="month"){if(a0<1){a1.setUTCDate(1);var aV=a1.getTime();a1.setUTCMonth(a1.getUTCMonth()+1);var aZ=a1.getTime();a1.setTime(a4+a5*aJ.hour+(aZ-aV)*a0);a5=a1.getUTCHours();a1.setUTCHours(0)}else{a1.setUTCMonth(a1.getUTCMonth()+a0)}}else{if(a3=="year"){a1.setUTCFullYear(a1.getUTCFullYear()+a0)}else{a1.setTime(a4+aW)}}}while(a4<aX.max&&a4!=aY);return a2};aR=function(aV,aY){var a0=new Date(aV);if(aM.timeformat!=null){return c.plot.formatDate(a0,aM.timeformat,aM.monthNames)}var aW=aY.tickSize[0]*aJ[aY.tickSize[1]];var aX=aY.max-aY.min;var aZ=(aM.twelveHourClock)?" %p":"";if(aW<aJ.minute){fmt="%h:%M:%S"+aZ}else{if(aW<aJ.day){if(aX<2*aJ.day){fmt="%h:%M"+aZ}else{fmt="%b %d %h:%M"+aZ}}else{if(aW<aJ.month){fmt="%b %d"}else{if(aW<aJ.year){if(aX<aJ.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return c.plot.formatDate(a0,fmt,aM.monthNames)}}else{var aU=aM.tickDecimals;var aP=-Math.floor(Math.log(aT)/Math.LN10);if(aU!=null&&aP>aU){aP=aU}aQ=Math.pow(10,-aP);aI=aT/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2;if(aI>2.25&&(aU==null||aP+1<=aU)){aO=2.5;++aP}}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ;if(aM.minTickSize!=null&&aO<aM.minTickSize){aO=aM.minTickSize}aG.tickDecimals=Math.max(0,aU!=null?aU:aP);aG.tickSize=aM.tickSize||aO;aB=function(aX){var aZ=[];var a0=a(aX.min,aX.tickSize),aW=0,aV=Number.NaN,aY;do{aY=aV;aV=a0+aW*aX.tickSize;aZ.push(aV);++aW}while(aV<aX.max&&aV!=aY);return aZ};aR=function(aV,aW){return aV.toFixed(aW.tickDecimals)}}if(aM.alignTicksWithAxis!=null){var aF=(aG.direction=="x"?p:aw)[aM.alignTicksWithAxis-1];if(aF&&aF.used&&aF!=aG){var aL=aB(aG);if(aL.length>0){if(aM.min==null){aG.min=Math.min(aG.min,aL[0])}if(aM.max==null&&aL.length>1){aG.max=Math.max(aG.max,aL[aL.length-1])}}aB=function(aX){var aY=[],aV,aW;for(aW=0;aW<aF.ticks.length;++aW){aV=(aF.ticks[aW].v-aF.min)/(aF.max-aF.min);aV=aX.min+aV*(aX.max-aX.min);aY.push(aV)}return aY};if(aG.mode!="time"&&aM.tickDecimals==null){var aE=Math.max(0,-Math.floor(Math.log(aT)/Math.LN10)+1),aD=aB(aG);if(!(aD.length>1&&/\..*0$/.test((aD[1]-aD[0]).toFixed(aE)))){aG.tickDecimals=aE}}}}aG.tickGenerator=aB;if(c.isFunction(aM.tickFormatter)){aG.tickFormatter=function(aV,aW){return""+aM.tickFormatter(aV,aW)}}else{aG.tickFormatter=aR}}function P(aF){var aH=aF.options.ticks,aG=[];if(aH==null||(typeof aH=="number"&&aH>0)){aG=aF.tickGenerator(aF)}else{if(aH){if(c.isFunction(aH)){aG=aH({min:aF.min,max:aF.max})}else{aG=aH}}}var aE,aB;aF.ticks=[];for(aE=0;aE<aG.length;++aE){var aC=null;var aD=aG[aE];if(typeof aD=="object"){aB=+aD[0];if(aD.length>1){aC=aD[1]}}else{aB=+aD}if(aC==null){aC=aF.tickFormatter(aB,aF)}if(!isNaN(aB)){aF.ticks.push({v:aB,label:aC})}}}function ap(aB,aC){if(aB.options.autoscaleMargin&&aC.length>0){if(aB.options.min==null){aB.min=Math.min(aB.min,aC[0].v)}if(aB.options.max==null&&aC.length>1){aB.max=Math.max(aB.max,aC[aC.length-1].v)}}}function W(){H.clearRect(0,0,G,I);var aC=O.grid;if(aC.show&&aC.backgroundColor){N()}if(aC.show&&!aC.aboveData){ac()}for(var aB=0;aB<Q.length;++aB){an(ak.drawSeries,[H,Q[aB]]);d(Q[aB])}an(ak.draw,[H]);if(aC.show&&aC.aboveData){ac()}}function D(aB,aI){var aE,aH,aG,aD,aF=m();for(i=0;i<aF.length;++i){aE=aF[i];if(aE.direction==aI){aD=aI+aE.n+"axis";if(!aB[aD]&&aE.n==1){aD=aI+"axis"}if(aB[aD]){aH=aB[aD].from;aG=aB[aD].to;break}}}if(!aB[aD]){aE=aI=="x"?p[0]:aw[0];aH=aB[aI+"1"];aG=aB[aI+"2"]}if(aH!=null&&aG!=null&&aH>aG){var aC=aH;aH=aG;aG=aC}return{from:aH,to:aG,axis:aE}}function N(){H.save();H.translate(q.left,q.top);H.fillStyle=am(O.grid.backgroundColor,w,0,"rgba(255, 255, 255, 0)");H.fillRect(0,0,h,w);H.restore()}function ac(){var aF;H.save();H.translate(q.left,q.top);var aH=O.grid.markings;if(aH){if(c.isFunction(aH)){var aK=aq.getAxes();aK.xmin=aK.xaxis.min;aK.xmax=aK.xaxis.max;aK.ymin=aK.yaxis.min;aK.ymax=aK.yaxis.max;aH=aH(aK)}for(aF=0;aF<aH.length;++aF){var aD=aH[aF],aC=D(aD,"x"),aI=D(aD,"y");if(aC.from==null){aC.from=aC.axis.min}if(aC.to==null){aC.to=aC.axis.max}if(aI.from==null){aI.from=aI.axis.min}if(aI.to==null){aI.to=aI.axis.max}if(aC.to<aC.axis.min||aC.from>aC.axis.max||aI.to<aI.axis.min||aI.from>aI.axis.max){continue}aC.from=Math.max(aC.from,aC.axis.min);aC.to=Math.min(aC.to,aC.axis.max);aI.from=Math.max(aI.from,aI.axis.min);aI.to=Math.min(aI.to,aI.axis.max);if(aC.from==aC.to&&aI.from==aI.to){continue}aC.from=aC.axis.p2c(aC.from);aC.to=aC.axis.p2c(aC.to);aI.from=aI.axis.p2c(aI.from);aI.to=aI.axis.p2c(aI.to);if(aC.from==aC.to||aI.from==aI.to){H.beginPath();H.strokeStyle=aD.color||O.grid.markingsColor;H.lineWidth=aD.lineWidth||O.grid.markingsLineWidth;H.moveTo(aC.from,aI.from);H.lineTo(aC.to,aI.to);H.stroke()}else{H.fillStyle=aD.color||O.grid.markingsColor;H.fillRect(aC.from,aI.to,aC.to-aC.from,aI.from-aI.to)}}}var aK=m(),aM=O.grid.borderWidth;for(var aE=0;aE<aK.length;++aE){var aB=aK[aE],aG=aB.box,aQ=aB.tickLength,aN,aL,aP,aJ;if(!aB.show||aB.ticks.length==0){continue}H.strokeStyle=aB.options.tickColor||c.color.parse(aB.options.color).scale("a",0.22).toString();H.lineWidth=1;if(aB.direction=="x"){aN=0;if(aQ=="full"){aL=(aB.position=="top"?0:w)}else{aL=aG.top-q.top+(aB.position=="top"?aG.height:0)}}else{aL=0;if(aQ=="full"){aN=(aB.position=="left"?0:h)}else{aN=aG.left-q.left+(aB.position=="left"?aG.width:0)}}if(!aB.innermost){H.beginPath();aP=aJ=0;if(aB.direction=="x"){aP=h}else{aJ=w}if(H.lineWidth==1){aN=Math.floor(aN)+0.5;aL=Math.floor(aL)+0.5}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ);H.stroke()}H.beginPath();for(aF=0;aF<aB.ticks.length;++aF){var aO=aB.ticks[aF].v;aP=aJ=0;if(aO<aB.min||aO>aB.max||(aQ=="full"&&aM>0&&(aO==aB.min||aO==aB.max))){continue}if(aB.direction=="x"){aN=aB.p2c(aO);aJ=aQ=="full"?-w:aQ;if(aB.position=="top"){aJ=-aJ}}else{aL=aB.p2c(aO);aP=aQ=="full"?-h:aQ;if(aB.position=="left"){aP=-aP}}if(H.lineWidth==1){if(aB.direction=="x"){aN=Math.floor(aN)+0.5}else{aL=Math.floor(aL)+0.5}}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ)}H.stroke()}if(aM){H.lineWidth=aM;H.strokeStyle=O.grid.borderColor;H.strokeRect(-aM/2,-aM/2,h+aM,w+aM)}H.restore()}function k(){av.find(".tickLabels").remove();var aG=['<div class="tickLabels" style="font-size:smaller">'];var aJ=m();for(var aD=0;aD<aJ.length;++aD){var aC=aJ[aD],aF=aC.box;if(!aC.show){continue}aG.push('<div class="'+aC.direction+"Axis "+aC.direction+aC.n+'Axis" style="color:'+aC.options.color+'">');for(var aE=0;aE<aC.ticks.length;++aE){var aH=aC.ticks[aE];if(!aH.label||aH.v<aC.min||aH.v>aC.max){continue}var aK={},aI;if(aC.direction=="x"){aI="center";aK.left=Math.round(q.left+aC.p2c(aH.v)-aC.labelWidth/2);if(aC.position=="bottom"){aK.top=aF.top+aF.padding}else{aK.bottom=I-(aF.top+aF.height-aF.padding)}}else{aK.top=Math.round(q.top+aC.p2c(aH.v)-aC.labelHeight/2);if(aC.position=="left"){aK.right=G-(aF.left+aF.width-aF.padding);aI="right"}else{aK.left=aF.left+aF.padding;aI="left"}}aK.width=aC.labelWidth;var aB=["position:absolute","text-align:"+aI];for(var aL in aK){aB.push(aL+":"+aK[aL]+"px")}aG.push('<div class="tickLabel" style="'+aB.join(";")+'">'+aH.label+"</div>")}aG.push("</div>")}aG.push("</div>");av.append(aG.join(""))}function d(aB){if(aB.lines.show){at(aB)}if(aB.bars.show){e(aB)}if(aB.points.show){ao(aB)}}function at(aE){function aD(aP,aQ,aI,aU,aT){var aV=aP.points,aJ=aP.pointsize,aN=null,aM=null;H.beginPath();for(var aO=aJ;aO<aV.length;aO+=aJ){var aL=aV[aO-aJ],aS=aV[aO-aJ+1],aK=aV[aO],aR=aV[aO+1];if(aL==null||aK==null){continue}if(aS<=aR&&aS<aT.min){if(aR<aT.min){continue}aL=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.min}else{if(aR<=aS&&aR<aT.min){if(aS<aT.min){continue}aK=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.min}}if(aS>=aR&&aS>aT.max){if(aR>aT.max){continue}aL=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.max}else{if(aR>=aS&&aR>aT.max){if(aS>aT.max){continue}aK=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.max}}if(aL<=aK&&aL<aU.min){if(aK<aU.min){continue}aS=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.min}else{if(aK<=aL&&aK<aU.min){if(aL<aU.min){continue}aR=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.min}}if(aL>=aK&&aL>aU.max){if(aK>aU.max){continue}aS=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.max}else{if(aK>=aL&&aK>aU.max){if(aL>aU.max){continue}aR=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.max}}if(aL!=aN||aS!=aM){H.moveTo(aU.p2c(aL)+aQ,aT.p2c(aS)+aI)}aN=aK;aM=aR;H.lineTo(aU.p2c(aK)+aQ,aT.p2c(aR)+aI)}H.stroke()}function aF(aI,aQ,aP){var aW=aI.points,aV=aI.pointsize,aN=Math.min(Math.max(0,aP.min),aP.max),aX=0,aU,aT=false,aM=1,aL=0,aR=0;while(true){if(aV>0&&aX>aW.length+aV){break}aX+=aV;var aZ=aW[aX-aV],aK=aW[aX-aV+aM],aY=aW[aX],aJ=aW[aX+aM];if(aT){if(aV>0&&aZ!=null&&aY==null){aR=aX;aV=-aV;aM=2;continue}if(aV<0&&aX==aL+aV){H.fill();aT=false;aV=-aV;aM=1;aX=aL=aR+aV;continue}}if(aZ==null||aY==null){continue}if(aZ<=aY&&aZ<aQ.min){if(aY<aQ.min){continue}aK=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.min}else{if(aY<=aZ&&aY<aQ.min){if(aZ<aQ.min){continue}aJ=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.min}}if(aZ>=aY&&aZ>aQ.max){if(aY>aQ.max){continue}aK=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.max}else{if(aY>=aZ&&aY>aQ.max){if(aZ>aQ.max){continue}aJ=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.max}}if(!aT){H.beginPath();H.moveTo(aQ.p2c(aZ),aP.p2c(aN));aT=true}if(aK>=aP.max&&aJ>=aP.max){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.max));H.lineTo(aQ.p2c(aY),aP.p2c(aP.max));continue}else{if(aK<=aP.min&&aJ<=aP.min){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.min));H.lineTo(aQ.p2c(aY),aP.p2c(aP.min));continue}}var aO=aZ,aS=aY;if(aK<=aJ&&aK<aP.min&&aJ>=aP.min){aZ=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.min}else{if(aJ<=aK&&aJ<aP.min&&aK>=aP.min){aY=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.min}}if(aK>=aJ&&aK>aP.max&&aJ<=aP.max){aZ=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.max}else{if(aJ>=aK&&aJ>aP.max&&aK<=aP.max){aY=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.max}}if(aZ!=aO){H.lineTo(aQ.p2c(aO),aP.p2c(aK))}H.lineTo(aQ.p2c(aZ),aP.p2c(aK));H.lineTo(aQ.p2c(aY),aP.p2c(aJ));if(aY!=aS){H.lineTo(aQ.p2c(aY),aP.p2c(aJ));H.lineTo(aQ.p2c(aS),aP.p2c(aJ))}}}H.save();H.translate(q.left,q.top);H.lineJoin="round";var aG=aE.lines.lineWidth,aB=aE.shadowSize;if(aG>0&&aB>0){H.lineWidth=aB;H.strokeStyle="rgba(0,0,0,0.1)";var aH=Math.PI/18;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/2),Math.cos(aH)*(aG/2+aB/2),aE.xaxis,aE.yaxis);H.lineWidth=aB/2;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/4),Math.cos(aH)*(aG/2+aB/4),aE.xaxis,aE.yaxis)}H.lineWidth=aG;H.strokeStyle=aE.color;var aC=ae(aE.lines,aE.color,0,w);if(aC){H.fillStyle=aC;aF(aE.datapoints,aE.xaxis,aE.yaxis)}if(aG>0){aD(aE.datapoints,0,0,aE.xaxis,aE.yaxis)}H.restore()}function ao(aE){function aH(aN,aM,aU,aK,aS,aT,aQ,aJ){var aR=aN.points,aI=aN.pointsize;for(var aL=0;aL<aR.length;aL+=aI){var aP=aR[aL],aO=aR[aL+1];if(aP==null||aP<aT.min||aP>aT.max||aO<aQ.min||aO>aQ.max){continue}H.beginPath();aP=aT.p2c(aP);aO=aQ.p2c(aO)+aK;if(aJ=="circle"){H.arc(aP,aO,aM,0,aS?Math.PI:Math.PI*2,false)}else{aJ(H,aP,aO,aM,aS)}H.closePath();if(aU){H.fillStyle=aU;H.fill()}H.stroke()}}H.save();H.translate(q.left,q.top);var aG=aE.points.lineWidth,aC=aE.shadowSize,aB=aE.points.radius,aF=aE.points.symbol;if(aG>0&&aC>0){var aD=aC/2;H.lineWidth=aD;H.strokeStyle="rgba(0,0,0,0.1)";aH(aE.datapoints,aB,null,aD+aD/2,true,aE.xaxis,aE.yaxis,aF);H.strokeStyle="rgba(0,0,0,0.2)";aH(aE.datapoints,aB,null,aD/2,true,aE.xaxis,aE.yaxis,aF)}H.lineWidth=aG;H.strokeStyle=aE.color;aH(aE.datapoints,aB,ae(aE.points,aE.color),0,false,aE.xaxis,aE.yaxis,aF);H.restore()}function E(aN,aM,aV,aI,aQ,aF,aD,aL,aK,aU,aR,aC){var aE,aT,aJ,aP,aG,aB,aO,aH,aS;if(aR){aH=aB=aO=true;aG=false;aE=aV;aT=aN;aP=aM+aI;aJ=aM+aQ;if(aT<aE){aS=aT;aT=aE;aE=aS;aG=true;aB=false}}else{aG=aB=aO=true;aH=false;aE=aN+aI;aT=aN+aQ;aJ=aV;aP=aM;if(aP<aJ){aS=aP;aP=aJ;aJ=aS;aH=true;aO=false}}if(aT<aL.min||aE>aL.max||aP<aK.min||aJ>aK.max){return}if(aE<aL.min){aE=aL.min;aG=false}if(aT>aL.max){aT=aL.max;aB=false}if(aJ<aK.min){aJ=aK.min;aH=false}if(aP>aK.max){aP=aK.max;aO=false}aE=aL.p2c(aE);aJ=aK.p2c(aJ);aT=aL.p2c(aT);aP=aK.p2c(aP);if(aD){aU.beginPath();aU.moveTo(aE,aJ);aU.lineTo(aE,aP);aU.lineTo(aT,aP);aU.lineTo(aT,aJ);aU.fillStyle=aD(aJ,aP);aU.fill()}if(aC>0&&(aG||aB||aO||aH)){aU.beginPath();aU.moveTo(aE,aJ+aF);if(aG){aU.lineTo(aE,aP+aF)}else{aU.moveTo(aE,aP+aF)}if(aO){aU.lineTo(aT,aP+aF)}else{aU.moveTo(aT,aP+aF)}if(aB){aU.lineTo(aT,aJ+aF)}else{aU.moveTo(aT,aJ+aF)}if(aH){aU.lineTo(aE,aJ+aF)}else{aU.moveTo(aE,aJ+aF)}aU.stroke()}}function e(aD){function aC(aJ,aI,aL,aG,aK,aN,aM){var aO=aJ.points,aF=aJ.pointsize;for(var aH=0;aH<aO.length;aH+=aF){if(aO[aH]==null){continue}E(aO[aH],aO[aH+1],aO[aH+2],aI,aL,aG,aK,aN,aM,H,aD.bars.horizontal,aD.bars.lineWidth)}}H.save();H.translate(q.left,q.top);H.lineWidth=aD.bars.lineWidth;H.strokeStyle=aD.color;var aB=aD.bars.align=="left"?0:-aD.bars.barWidth/2;var aE=aD.bars.fill?function(aF,aG){return ae(aD.bars,aD.color,aF,aG)}:null;aC(aD.datapoints,aB,aB+aD.bars.barWidth,0,aE,aD.xaxis,aD.yaxis);H.restore()}function ae(aD,aB,aC,aF){var aE=aD.fill;if(!aE){return null}if(aD.fillColor){return am(aD.fillColor,aC,aF,aB)}var aG=c.color.parse(aB);aG.a=typeof aE=="number"?aE:0.4;aG.normalize();return aG.toString()}function o(){av.find(".legend").remove();if(!O.legend.show){return}var aH=[],aF=false,aN=O.legend.labelFormatter,aM,aJ;for(var aE=0;aE<Q.length;++aE){aM=Q[aE];aJ=aM.label;if(!aJ){continue}if(aE%O.legend.noColumns==0){if(aF){aH.push("</tr>")}aH.push("<tr>");aF=true}if(aN){aJ=aN(aJ,aM)}aH.push('<td class="legendColorBox"><div style="border:1px solid '+O.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+aM.color+';overflow:hidden"></div></div></td><td class="legendLabel">'+aJ+"</td>")}if(aF){aH.push("</tr>")}if(aH.length==0){return}var aL='<table style="font-size:smaller;color:'+O.grid.color+'">'+aH.join("")+"</table>";if(O.legend.container!=null){c(O.legend.container).html(aL)}else{var aI="",aC=O.legend.position,aD=O.legend.margin;if(aD[0]==null){aD=[aD,aD]}if(aC.charAt(0)=="n"){aI+="top:"+(aD[1]+q.top)+"px;"}else{if(aC.charAt(0)=="s"){aI+="bottom:"+(aD[1]+q.bottom)+"px;"}}if(aC.charAt(1)=="e"){aI+="right:"+(aD[0]+q.right)+"px;"}else{if(aC.charAt(1)=="w"){aI+="left:"+(aD[0]+q.left)+"px;"}}var aK=c('<div class="legend">'+aL.replace('style="','style="position:absolute;'+aI+";")+"</div>").appendTo(av);if(O.legend.backgroundOpacity!=0){var aG=O.legend.backgroundColor;if(aG==null){aG=O.grid.backgroundColor;if(aG&&typeof aG=="string"){aG=c.color.parse(aG)}else{aG=c.color.extract(aK,"background-color")}aG.a=1;aG=aG.toString()}var aB=aK.children();c('<div style="position:absolute;width:'+aB.width()+"px;height:"+aB.height()+"px;"+aI+"background-color:"+aG+';"> </div>').prependTo(aK).css("opacity",O.legend.backgroundOpacity)}}}var ab=[],M=null;function K(aI,aG,aD){var aO=O.grid.mouseActiveRadius,a0=aO*aO+1,aY=null,aR=false,aW,aU;for(aW=Q.length-1;aW>=0;--aW){if(!aD(Q[aW])){continue}var aP=Q[aW],aH=aP.xaxis,aF=aP.yaxis,aV=aP.datapoints.points,aT=aP.datapoints.pointsize,aQ=aH.c2p(aI),aN=aF.c2p(aG),aC=aO/aH.scale,aB=aO/aF.scale;if(aH.options.inverseTransform){aC=Number.MAX_VALUE}if(aF.options.inverseTransform){aB=Number.MAX_VALUE}if(aP.lines.show||aP.points.show){for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1];if(aK==null){continue}if(aK-aQ>aC||aK-aQ<-aC||aJ-aN>aB||aJ-aN<-aB){continue}var aM=Math.abs(aH.p2c(aK)-aI),aL=Math.abs(aF.p2c(aJ)-aG),aS=aM*aM+aL*aL;if(aS<a0){a0=aS;aY=[aW,aU/aT]}}}if(aP.bars.show&&!aY){var aE=aP.bars.align=="left"?0:-aP.bars.barWidth/2,aX=aE+aP.bars.barWidth;for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1],aZ=aV[aU+2];if(aK==null){continue}if(Q[aW].bars.horizontal?(aQ<=Math.max(aZ,aK)&&aQ>=Math.min(aZ,aK)&&aN>=aJ+aE&&aN<=aJ+aX):(aQ>=aK+aE&&aQ<=aK+aX&&aN>=Math.min(aZ,aJ)&&aN<=Math.max(aZ,aJ))){aY=[aW,aU/aT]}}}}if(aY){aW=aY[0];aU=aY[1];aT=Q[aW].datapoints.pointsize;return{datapoint:Q[aW].datapoints.points.slice(aU*aT,(aU+1)*aT),dataIndex:aU,series:Q[aW],seriesIndex:aW}}return null}function aa(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return aC.hoverable!=false})}}function l(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return false})}}function R(aB){u("plotclick",aB,function(aC){return aC.clickable!=false})}function u(aC,aB,aD){var aE=y.offset(),aH=aB.pageX-aE.left-q.left,aF=aB.pageY-aE.top-q.top,aJ=C({left:aH,top:aF});aJ.pageX=aB.pageX;aJ.pageY=aB.pageY;var aK=K(aH,aF,aD);if(aK){aK.pageX=parseInt(aK.series.xaxis.p2c(aK.datapoint[0])+aE.left+q.left);aK.pageY=parseInt(aK.series.yaxis.p2c(aK.datapoint[1])+aE.top+q.top)}if(O.grid.autoHighlight){for(var aG=0;aG<ab.length;++aG){var aI=ab[aG];if(aI.auto==aC&&!(aK&&aI.series==aK.series&&aI.point[0]==aK.datapoint[0]&&aI.point[1]==aK.datapoint[1])){T(aI.series,aI.point)}}if(aK){x(aK.series,aK.datapoint,aC)}}av.trigger(aC,[aJ,aK])}function f(){if(!M){M=setTimeout(s,30)}}function s(){M=null;A.save();A.clearRect(0,0,G,I);A.translate(q.left,q.top);var aC,aB;for(aC=0;aC<ab.length;++aC){aB=ab[aC];if(aB.series.bars.show){v(aB.series,aB.point)}else{ay(aB.series,aB.point)}}A.restore();an(ak.drawOverlay,[A])}function x(aD,aB,aF){if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){var aE=aD.datapoints.pointsize;aB=aD.datapoints.points.slice(aE*aB,aE*(aB+1))}var aC=al(aD,aB);if(aC==-1){ab.push({series:aD,point:aB,auto:aF});f()}else{if(!aF){ab[aC].auto=false}}}function T(aD,aB){if(aD==null&&aB==null){ab=[];f()}if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){aB=aD.data[aB]}var aC=al(aD,aB);if(aC!=-1){ab.splice(aC,1);f()}}function al(aD,aE){for(var aB=0;aB<ab.length;++aB){var aC=ab[aB];if(aC.series==aD&&aC.point[0]==aE[0]&&aC.point[1]==aE[1]){return aB}}return -1}function ay(aE,aD){var aC=aD[0],aI=aD[1],aH=aE.xaxis,aG=aE.yaxis;if(aC<aH.min||aC>aH.max||aI<aG.min||aI>aG.max){return}var aF=aE.points.radius+aE.points.lineWidth/2;A.lineWidth=aF;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aB=1.5*aF,aC=aH.p2c(aC),aI=aG.p2c(aI);A.beginPath();if(aE.points.symbol=="circle"){A.arc(aC,aI,aB,0,2*Math.PI,false)}else{aE.points.symbol(A,aC,aI,aB,false)}A.closePath();A.stroke()}function v(aE,aB){A.lineWidth=aE.bars.lineWidth;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aD=c.color.parse(aE.color).scale("a",0.5).toString();var aC=aE.bars.align=="left"?0:-aE.bars.barWidth/2;E(aB[0],aB[1],aB[2]||0,aC,aC+aE.bars.barWidth,0,function(){return aD},aE.xaxis,aE.yaxis,A,aE.bars.horizontal,aE.bars.lineWidth)}function am(aJ,aB,aH,aC){if(typeof aJ=="string"){return aJ}else{var aI=H.createLinearGradient(0,aH,0,aB);for(var aE=0,aD=aJ.colors.length;aE<aD;++aE){var aF=aJ.colors[aE];if(typeof aF!="string"){var aG=c.color.parse(aC);if(aF.brightness!=null){aG=aG.scale("rgb",aF.brightness)}if(aF.opacity!=null){aG.a*=aF.opacity}aF=aG.toString()}aI.addColorStop(aE/(aD-1),aF)}return aI}}}c.plot=function(g,e,d){var f=new b(c(g),e,d,c.plot.plugins);return f};c.plot.version="0.7";c.plot.plugins=[];c.plot.formatDate=function(l,f,h){var o=function(d){d=""+d;return d.length==1?"0"+d:d};var e=[];var p=false,j=false;var n=l.getUTCHours();var k=n<12;if(h==null){h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(f.search(/%p|%P/)!=-1){if(n>12){n=n-12}else{if(n==0){n=12}}}for(var g=0;g<f.length;++g){var m=f.charAt(g);if(p){switch(m){case"h":m=""+n;break;case"H":m=o(n);break;case"M":m=o(l.getUTCMinutes());break;case"S":m=o(l.getUTCSeconds());break;case"d":m=""+l.getUTCDate();break;case"m":m=""+(l.getUTCMonth()+1);break;case"y":m=""+l.getUTCFullYear();break;case"b":m=""+h[l.getUTCMonth()];break;case"p":m=(k)?("am"):("pm");break;case"P":m=(k)?("AM"):("PM");break;case"0":m="";j=true;break}if(m&&j){m=o(m);j=false}e.push(m);if(!j){p=false}}else{if(m=="%"){p=true}else{e.push(m)}}}return e.join("")};function a(e,d){return d*Math.floor(e/d)}})(jQuery); \ No newline at end of file
diff --git a/module/web/static/js/libs/jquery.mobile-1.1.1.min.js b/module/web/static/js/libs/jquery.mobile-1.1.1.min.js
new file mode 100644
index 000000000..70f71928f
--- /dev/null
+++ b/module/web/static/js/libs/jquery.mobile-1.1.1.min.js
@@ -0,0 +1,181 @@
+/*! jQuery Mobile v1.1.1 1981b3f5ec22675ae47df8f0bdf9622e7780e90e jquerymobile.com | jquery.org/license */
+(function(l,t,k){typeof define==="function"&&define.amd?define(["jquery"],function(E){k(E,l,t);return E.mobile}):k(l.jQuery,l,t)})(this,document,function(l,t,k,E){(function(a,c,b,d){function e(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function g(b){for(var e={},g,d;b;){g=a.data(b,s);for(d in g)if(g[d])e[d]=e.hasVirtualBinding=true;b=b.parentNode}return e}function f(){y&&(clearTimeout(y),y=0);y=setTimeout(function(){G=y=0;A.length=0;F=false;H=true},a.vmouse.resetTimerDuration)}
+function j(b,g,c){var f,h;if(!(h=c&&c[b])){if(c=!c)a:{for(c=g.target;c;){if((h=a.data(c,s))&&(!b||h[b]))break a;c=c.parentNode}c=null}h=c}if(h){f=g;var c=f.type,j,F;f=a.Event(f);f.type=b;h=f.originalEvent;j=a.event.props;c.search(/^(mouse|click)/)>-1&&(j=w);if(h)for(F=j.length;F;)b=j[--F],f[b]=h[b];if(c.search(/mouse(down|up)|click/)>-1&&!f.which)f.which=1;if(c.search(/^touch/)!==-1&&(b=e(h),c=b.touches,b=b.changedTouches,c=c&&c.length?c[0]:b&&b.length?b[0]:d))for(h=0,len=x.length;h<len;h++)b=x[h],
+f[b]=c[b];a(g.target).trigger(f)}return f}function h(b){var e=a.data(b.target,u);if(!F&&(!G||G!==e))if(e=j("v"+b.type,b))e.isDefaultPrevented()&&b.preventDefault(),e.isPropagationStopped()&&b.stopPropagation(),e.isImmediatePropagationStopped()&&b.stopImmediatePropagation()}function q(b){var c=e(b).touches,d;if(c&&c.length===1&&(d=b.target,c=g(d),c.hasVirtualBinding))G=N++,a.data(d,u,G),y&&(clearTimeout(y),y=0),z=H=false,d=e(b).touches[0],t=d.pageX,C=d.pageY,j("vmouseover",b,c),j("vmousedown",b,c)}
+function o(a){H||(z||j("vmousecancel",a,g(a.target)),z=true,f())}function m(b){if(!H){var c=e(b).touches[0],d=z,h=a.vmouse.moveDistanceThreshold;z=z||Math.abs(c.pageX-t)>h||Math.abs(c.pageY-C)>h;flags=g(b.target);z&&!d&&j("vmousecancel",b,flags);j("vmousemove",b,flags);f()}}function v(a){if(!H){H=true;var b=g(a.target),c;j("vmouseup",a,b);if(!z&&(c=j("vclick",a,b))&&c.isDefaultPrevented())c=e(a).changedTouches[0],A.push({touchID:G,x:c.clientX,y:c.clientY}),F=true;j("vmouseout",a,b);z=false;f()}}function n(b){var b=
+a.data(b,s),e;if(b)for(e in b)if(b[e])return true;return false}function k(){}function p(b){var e=b.substr(1);return{setup:function(){n(this)||a.data(this,s,{});a.data(this,s)[b]=true;l[b]=(l[b]||0)+1;l[b]===1&&J.bind(e,h);a(this).bind(e,k);if(M)l.touchstart=(l.touchstart||0)+1,l.touchstart===1&&J.bind("touchstart",q).bind("touchend",v).bind("touchmove",m).bind("scroll",o)},teardown:function(){--l[b];l[b]||J.unbind(e,h);M&&(--l.touchstart,l.touchstart||J.unbind("touchstart",q).unbind("touchmove",m).unbind("touchend",
+v).unbind("scroll",o));var g=a(this),c=a.data(this,s);c&&(c[b]=false);g.unbind(e,k);n(this)||g.removeData(s)}}}var s="virtualMouseBindings",u="virtualTouchID",c="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),x="clientX clientY pageX pageY screenX screenY".split(" "),w=a.event.props.concat(a.event.mouseHooks?a.event.mouseHooks.props:[]),l={},y=0,t=0,C=0,z=false,A=[],F=false,H=false,M="addEventListener"in b,J=a(b),N=1,G=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,
+resetTimerDuration:1500};for(var K=0;K<c.length;K++)a.event.special[c[K]]=p(c[K]);M&&b.addEventListener("click",function(b){var e=A.length,g=b.target,c,d,f,h,j;if(e){c=b.clientX;d=b.clientY;threshold=a.vmouse.clickDistanceThreshold;for(f=g;f;){for(h=0;h<e;h++)if(j=A[h],f===g&&Math.abs(j.x-c)<threshold&&Math.abs(j.y-d)<threshold||a.data(f,u)===j.touchID){b.preventDefault();b.stopPropagation();return}f=f.parentNode}}},true)})(l,t,k);(function(a,c,b){function d(a){a=a||location.href;return"#"+a.replace(/^[^#]*#?(.*)$/,
+"$1")}var e="hashchange",g=k,f,j=a.event.special,h=g.documentMode,q="on"+e in c&&(h===b||h>7);a.fn[e]=function(a){return a?this.bind(e,a):this.trigger(e)};a.fn[e].delay=50;j[e]=a.extend(j[e],{setup:function(){if(q)return false;a(f.start)},teardown:function(){if(q)return false;a(f.stop)}});f=function(){function f(){var b=d(),g=s(n);if(b!==n)p(n=b,g),a(c).trigger(e);else if(g!==n)location.href=location.href.replace(/#.*/,"")+g;j=setTimeout(f,a.fn[e].delay)}var h={},j,n=d(),k=function(a){return a},p=
+k,s=k;h.start=function(){j||f()};h.stop=function(){j&&clearTimeout(j);j=b};a.browser.msie&&!q&&function(){var b,c;h.start=function(){if(!b)c=(c=a.fn[e].src)&&c+d(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){c||p(d());f()}).attr("src",c||"javascript:0").insertAfter("body")[0].contentWindow,g.onpropertychange=function(){try{if(event.propertyName==="title")b.document.title=g.title}catch(a){}}};h.stop=k;s=function(){return d(b.location.href)};p=function(c,d){var f=b.document,
+h=a.fn[e].domain;if(c!==d)f.title=g.title,f.open(),h&&f.write('<script>document.domain="'+h+'"<\/script>'),f.close(),b.location.hash=c}}();return h}()})(l,this);(function(a,c){if(a.cleanData){var b=a.cleanData;a.cleanData=function(e){for(var c=0,f;(f=e[c])!=null;c++)a(f).triggerHandler("remove");b(e)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return d.call(a(this),
+b,c)})}}a.widget=function(b,c,f){var d=b.split(".")[0],h,b=b.split(".")[1];h=d+"-"+b;if(!f)f=c,c=a.Widget;a.expr[":"][h]=function(c){return!!a.data(c,b)};a[d]=a[d]||{};a[d][b]=function(a,b){arguments.length&&this._createWidget(a,b)};c=new c;c.options=a.extend(true,{},c.options);a[d][b].prototype=a.extend(true,c,{namespace:d,widgetName:b,widgetEventPrefix:a[d][b].prototype.widgetEventPrefix||b,widgetBaseClass:h},f);a.widget.bridge(b,a[d][b])};a.widget.bridge=function(b,g){a.fn[b]=function(d){var j=
+typeof d==="string",h=Array.prototype.slice.call(arguments,1),q=this,d=!j&&h.length?a.extend.apply(null,[true,d].concat(h)):d;if(j&&d.charAt(0)==="_")return q;j?this.each(function(){var g=a.data(this,b);if(!g)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+d+"'";if(!a.isFunction(g[d]))throw"no such method '"+d+"' for "+b+" widget instance";var j=g[d].apply(g,h);if(j!==g&&j!==c)return q=j,false}):this.each(function(){var c=a.data(this,b);c?c.option(d||{})._init():
+a.data(this,b,new g(d,this))});return q}};a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(b,c){a.data(c,this.widgetName,this);this.element=a(c);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b=
+{};a.metadata&&(b=a.metadata.get(element)[this.widgetName]);return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,d){var f=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(d===c)return this.options[b];
+f={};f[b]=d}this._setOptions(f);return this},_setOptions:function(b){var c=this;a.each(b,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b;a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,c,d){var j=this.options[b],c=a.Event(c);
+c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();d=d||{};if(c.originalEvent)for(var b=a.event.props.length,h;b;)h=a.event.props[--b],c[h]=c.originalEvent[h];this.element.trigger(c,d);return!(a.isFunction(j)&&j.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(l);(function(a,c){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments);this._trigger("init")},_getCreateOptions:function(){var b=this.element,d={};
+a.each(this.options,function(a){var g=b.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));g!==c&&(d[a]=g)});return d},enhanceWithin:function(b,c){this.enhance(a(this.options.initSelector,a(b)),c)},enhance:function(b,c){var e,g=a(b),g=a.mobile.enhanceable(g);c&&g.length&&(e=(e=a.mobile.closestPageData(g))&&e.keepNativeSelector()||"",g=g.not(e));g[this.widgetName]()},raise:function(a){throw"Widget ["+this.widgetName+"]: "+a;}})})(l);(function(a,c){var b={};a.mobile=a.extend({},{version:"1.1.1",
+ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",focusClass:"ui-focus",ajaxEnabled:true,hashListeningEnabled:true,linkBindingEnabled:true,defaultPageTransition:"fade",maxTransitionWidth:false,minScrollBack:250,touchOverflowEnabled:false,defaultDialogTransition:"pop",loadingMessage:"loading",pageLoadErrorMessage:"Error Loading Page",loadingMessageTextVisible:false,loadingMessageTheme:"a",pageLoadErrorMessageTheme:"e",autoInitializePage:true,pushStateEnabled:true,
+ignoreContentEnabled:false,orientationChangeEnabled:true,buttonMarkup:{hoverDelay:200},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(b){if(a.type(b)!==
+"number")b=a.mobile.defaultHomeScroll;a.event.special.scrollstart.enabled=false;setTimeout(function(){c.scrollTo(0,b);a(k).trigger("silentscroll",{x:0,y:b})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},nsNormalizeDict:b,nsNormalize:function(c){return!c?void 0:b[c]||(b[c]=a.camelCase(a.mobile.ns+c))},getInheritedTheme:function(a,b){for(var c=a[0],d="",e=/ui-(bar|body|overlay)-([a-z])\b/,o,m;c;){if((o=c.className||"")&&(m=e.exec(o))&&(d=m[2]))break;c=c.parentNode}return d||
+b||"a"},closestPageData:function(a){return a.closest(':jqmData(role="page"), :jqmData(role="dialog")').data("page")},enhanceable:function(a){return this.haveParents(a,"enhance")},hijackable:function(a){return this.haveParents(a,"ajax")},haveParents:function(b,c){if(!a.mobile.ignoreContentEnabled)return b;for(var d=b.length,e=a(),q,o,m,v=0;v<d;v++){o=b.eq(v);m=false;for(q=b[v];q;){if((q.getAttribute?q.getAttribute("data-"+a.mobile.ns+c):"")==="false"){m=true;break}q=q.parentNode}m||(e=e.add(o))}return e},
+getScreenHeight:function(){return c.innerHeight||a(c).height()}},a.mobile);a.fn.jqmData=function(b,c){var d;typeof b!="undefined"&&(b&&(b=a.mobile.nsNormalize(b)),d=this.data.apply(this,arguments.length<2?[b]:[b,c]));return d};a.jqmData=function(b,c,d){var e;typeof c!="undefined"&&(e=a.data(b,c?a.mobile.nsNormalize(c):c,d));return e};a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))};a.jqmRemoveData=function(b,c){return a.removeData(b,a.mobile.nsNormalize(c))};a.fn.removeWithDependents=
+function(){a.removeWithDependents(this)};a.removeWithDependents=function(b){b=a(b);(b.jqmData("dependents")||a()).remove();b.remove()};a.fn.addDependents=function(b){a.addDependents(a(this),b)};a.addDependents=function(b,c){var d=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(d,c))};a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()};a.fn.jqmEnhanceable=function(){return a.mobile.enhanceable(this)};a.fn.jqmHijackable=function(){return a.mobile.hijackable(this)};
+var d=a.find,e=/:jqmData\(([^)]*)\)/g;a.find=function(b,c,j,h){b=b.replace(e,"[data-"+(a.mobile.ns||"")+"$1]");return d.call(this,b,c,j,h)};a.extend(a.find,d);a.find.matches=function(b,c){return a.find(b,null,null,c)};a.find.matchesSelector=function(b,c){return a.find(c,null,null,[b]).length>0}})(l,this);(function(a){a(t);var c=a("html");a.mobile.media=function(){var b={},d=a("<div id='jquery-mediatest'></div>"),e=a("<body>").append(d);return function(a){if(!(a in b)){var f=k.createElement("style"),
+j="@media "+a+" { #jquery-mediatest { position:absolute; } }";f.type="text/css";f.styleSheet?f.styleSheet.cssText=j:f.appendChild(k.createTextNode(j));c.prepend(e).prepend(f);b[a]=d.css("position")==="absolute";e.add(f).remove()}return b[a]}}()})(l);(function(a,c){function b(a){var b=a.charAt(0).toUpperCase()+a.substr(1),a=(a+" "+f.join(b+" ")+b).split(" "),d;for(d in a)if(g[a[d]]!==c)return true}function d(a,b,c){var d=k.createElement("div"),c=c?[c]:f,e;for(i=0;i<c.length;i++){var h=c[i],j="-"+h.charAt(0).toLowerCase()+
+h.substr(1)+"-"+a+": "+b+";",h=h.charAt(0).toUpperCase()+h.substr(1)+(a.charAt(0).toUpperCase()+a.substr(1));d.setAttribute("style",j);d.style[h]&&(e=true)}return!!e}var e=a("<body>").prependTo("html"),g=e[0].style,f=["Webkit","Moz","O"],j="palmGetResource"in t,h=t.opera,q=t.operamini&&{}.toString.call(t.operamini)==="[object OperaMini]",o=t.blackberry;a.extend(a.mobile,{browser:{}});a.mobile.browser.ie=function(){for(var a=3,b=k.createElement("div"),c=b.all||[];b.innerHTML="<\!--[if gt IE "+ ++a+
+"]><br><![endif]--\>",c[0];);return a>4?a:!a}();a.extend(a.support,{orientation:"orientation"in t&&"onorientationchange"in t,touch:"ontouchend"in k,cssTransitions:"WebKitTransitionEvent"in t||d("transition","height 100ms linear")&&!h,pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!b("content"),touchOverflow:!!b("overflowScrolling"),cssTransform3d:d("perspective","10px","moz")||a.mobile.media("(-"+f.join("-transform-3d),(-")+"-transform-3d),(transform-3d)"),
+boxShadow:!!b("boxShadow")&&!o,scrollTop:("pageXOffset"in t||"scrollTop"in k.documentElement||"scrollTop"in e[0])&&!j&&!q,dynamicBaseTag:function(){var b=location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),d=null,h="",f;c.length?h=c.attr("href"):c=d=a("<base>",{href:b}).appendTo("head");f=a("<a href='testurl' />").prependTo(e)[0].href;c[0].href=h||location.pathname;d&&d.remove();return f.indexOf(b)===0}(),cssPointerEvents:function(){var a=k.createElement("x"),b=k.documentElement,
+c=t.getComputedStyle;if(!("pointerEvents"in a.style))return false;a.style.pointerEvents="auto";a.style.pointerEvents="x";b.appendChild(a);c=c&&c(a,"").pointerEvents==="auto";b.removeChild(a);return!!c}()});e.remove();j=function(){var a=t.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")>-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();a.mobile.gradeA=function(){return a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=
+7};a.mobile.ajaxBlacklist=t.blackberry&&!t.WebKitPoint||q||j;j&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")});a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(l);(function(a,c,b){function d(b,c,d){var e=d.type;d.type=c;a.event.handle.call(b,d);d.type=e}a.each("touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=
+function(a){return a?this.bind(c,a):this.trigger(c)};a.attrFn[c]=true});var e=a.support.touch,g=e?"touchstart":"mousedown",f=e?"touchend":"mouseup",j=e?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function b(a,h){e=h;d(c,e?"scrollstart":"scrollstop",a)}var c=this,e,f;a(c).bind("touchmove scroll",function(c){a.event.special.scrollstart.enabled&&(e||b(c,true),clearTimeout(f),f=setTimeout(function(){b(c,false)},50))})}};a.event.special.tap={setup:function(){var b=
+this,c=a(b);c.bind("vmousedown",function(e){function f(){clearTimeout(p)}function j(){f();c.unbind("vclick",g).unbind("vmouseup",f);a(k).unbind("vmousecancel",j)}function g(a){j();r==a.target&&d(b,"tap",a)}if(e.which&&e.which!==1)return false;var r=e.target,p;c.bind("vmouseup",f).bind("vclick",g);a(k).bind("vmousecancel",j);p=setTimeout(function(){d(b,"taphold",a.Event("taphold",{target:r}))},750)})}};a.event.special.swipe={scrollSupressionThreshold:10,durationThreshold:1E3,horizontalDistanceThreshold:30,
+verticalDistanceThreshold:75,setup:function(){var c=a(this);c.bind(g,function(d){function e(b){if(k){var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;n={time:(new Date).getTime(),coords:[c.pageX,c.pageY]};Math.abs(k.coords[0]-n.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&b.preventDefault()}}var g=d.originalEvent.touches?d.originalEvent.touches[0]:d,k={time:(new Date).getTime(),coords:[g.pageX,g.pageY],origin:a(d.target)},n;c.bind(j,e).one(f,function(){c.unbind(j,e);k&&n&&
+n.time-k.time<a.event.special.swipe.durationThreshold&&Math.abs(k.coords[0]-n.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(k.coords[1]-n.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&k.origin.trigger("swipe").trigger(k.coords[0]>n.coords[0]?"swipeleft":"swiperight");k=n=b})})}};(function(a,b){function c(){var a=e();a!==f&&(f=a,d.trigger("orientationchange"))}var d=a(b),e,f,j,g,l={0:true,180:true};if(a.support.orientation&&(j=b.innerWidth||a(b).width(),g=b.innerHeight||
+a(b).height(),j=j>g&&j-g>50,g=l[b.orientation],j&&g||!j&&!g))l={"-90":true,90:true};a.event.special.orientationchange={setup:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;f=e();d.bind("throttledresize",c)},teardown:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;d.unbind("throttledresize",c)},add:function(a){var b=a.handler;a.handler=function(a){a.orientation=e();return b.apply(this,arguments)}}};a.event.special.orientationchange.orientation=
+e=function(){var c=true,c=k.documentElement;return(c=a.support.orientation?l[b.orientation]:c&&c.clientWidth/c.clientHeight<1.1)?"portrait":"landscape"}})(l,c);(function(){a.event.special.throttledresize={setup:function(){a(this).bind("resize",b)},teardown:function(){a(this).unbind("resize",b)}};var b=function(){e=(new Date).getTime();f=e-c;f>=250?(c=e,a(this).trigger("throttledresize")):(d&&clearTimeout(d),d=setTimeout(b,250-f))},c=0,d,e,f})();a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",
+swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})})(l,this);(function(a){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:false,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){var a=this;if(a._trigger("beforecreate")===false)return false;a.element.attr("tabindex","0").addClass("ui-page ui-body-"+a.options.theme).bind("pagebeforehide",function(){a.removeContainerBackground()}).bind("pagebeforeshow",
+function(){a.setContainerBackground()})},removeContainerBackground:function(){a.mobile.pageContainer.removeClass("ui-overlay-"+a.mobile.getInheritedTheme(this.element.parent()))},setContainerBackground:function(c){this.options.theme&&a.mobile.pageContainer.addClass("ui-overlay-"+(c||this.options.theme))},keepNativeSelector:function(){var c=this.options;return c.keepNative&&a.trim(c.keepNative)&&c.keepNative!==c.keepNativeDefault?[c.keepNative,c.keepNativeDefault].join(", "):c.keepNativeDefault}})})(l);
+(function(a,c,b){var d=function(d){d===b&&(d=true);return function(b,e,h,q){var k=new a.Deferred,m=e?" reverse":"",l=a.mobile.urlHistory.getActive().lastScroll||a.mobile.defaultHomeScroll,n=a.mobile.getScreenHeight(),r=a.mobile.maxTransitionWidth!==false&&a(c).width()>a.mobile.maxTransitionWidth,p=!a.support.cssTransitions||r||!b||b==="none"||Math.max(a(c).scrollTop(),l)>a.mobile.getMaxScrollForTransition(),s=function(){a.mobile.pageContainer.toggleClass("ui-mobile-viewport-transitioning viewport-"+
+b)},u=function(){a.event.special.scrollstart.enabled=false;c.scrollTo(0,l);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},x=function(){q.removeClass(a.mobile.activePageClass+" out in reverse "+b).height("")},r=function(){q&&d&&x();h.addClass(a.mobile.activePageClass);a.mobile.focusPage(h);h.height(n+l);u();p||h.animationComplete(w);h.addClass(b+" in"+m);p&&w()},w=function(){d||q&&x();h.removeClass("out in reverse "+b).height("");s();a(c).scrollTop()!==l&&u();k.resolve(b,e,h,
+q,true)};s();q&&!p?(d?q.animationComplete(r):r(),q.height(n+a(c).scrollTop()).addClass(b+" out"+m)):r();return k.promise()}},e=d(),d=d(false);a.mobile.defaultTransitionHandler=e;a.mobile.transitionHandlers={"default":a.mobile.defaultTransitionHandler,sequential:e,simultaneous:d};a.mobile.transitionFallbacks={};a.mobile.getMaxScrollForTransition=a.mobile.getMaxScrollForTransition||function(){return a.mobile.getScreenHeight()*3}})(l,this);(function(a,c){function b(b){l&&(!l.closest(".ui-page-active").length||
+b)&&l.removeClass(a.mobile.activeBtnClass);l=null}function d(){p=false;r.length>0&&a.mobile.changePage.apply(null,r.pop())}function e(b,c,d,e){c&&c.data("page")._trigger("beforehide",null,{nextPage:b});b.data("page")._trigger("beforeshow",null,{prevPage:c||a("")});a.mobile.hidePageLoadingMsg();d&&!a.support.cssTransform3d&&a.mobile.transitionFallbacks[d]&&(d=a.mobile.transitionFallbacks[d]);d=(a.mobile.transitionHandlers[d||"default"]||a.mobile.defaultTransitionHandler)(d,e,b,c);d.done(function(){c&&
+c.data("page")._trigger("hide",null,{nextPage:b});b.data("page")._trigger("show",null,{prevPage:c||a("")})});return d}function g(){var b=a("."+a.mobile.activePageClass),c=parseFloat(b.css("padding-top")),d=parseFloat(b.css("padding-bottom")),e=parseFloat(b.css("border-top-width")),f=parseFloat(b.css("border-bottom-width"));b.css("min-height",y()-c-d-e-f)}function f(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c);b.page()}function j(a){for(;a;){if(typeof a.nodeName==="string"&&a.nodeName.toLowerCase()==
+"a")break;a=a.parentNode}return a}function h(b){var b=a(b).closest(".ui-page").jqmData("url"),c=w.hrefNoHash;if(!b||!m.isPath(b))b=c;return m.makeUrlAbsolute(b,c)}var q=a(t);a("html");var o=a("head"),m={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,parseUrl:function(b){if(a.type(b)==="object")return b;b=m.urlParseRE.exec(b||"")||[];return{href:b[0]||"",hrefNoHash:b[1]||
+"",hrefNoSearch:b[2]||"",domain:b[3]||"",protocol:b[4]||"",doubleSlash:b[5]||"",authority:b[6]||"",username:b[8]||"",password:b[9]||"",host:b[10]||"",hostname:b[11]||"",port:b[12]||"",pathname:b[13]||"",directory:b[14]||"",filename:b[15]||"",search:b[16]||"",hash:b[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;for(var a=a||"",c=(b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"")?b.split("/"):[],d=a.split("/"),e=0;e<d.length;e++){var f=d[e];switch(f){case ".":break;case "..":c.length&&
+c.pop();break;default:c.push(f)}}return"/"+c.join("/")},isSameDomain:function(a,b){return m.parseUrl(a).domain===m.parseUrl(b).domain},isRelativeUrl:function(a){return m.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return m.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!m.isRelativeUrl(a))return a;var c=m.parseUrl(a),d=m.parseUrl(b),e=c.protocol||d.protocol,f=c.protocol?c.doubleSlash:c.doubleSlash||d.doubleSlash,h=c.authority||d.authority,j=c.pathname!=="",g=m.makePathAbsolute(c.pathname||
+d.filename,d.pathname);return e+f+h+g+(c.search||!j&&d.search||"")+c.hash},addSearchParams:function(b,c){var d=m.parseUrl(b),e=typeof c==="object"?a.param(c):c,f=d.search||"?";return d.hrefNoSearch+f+(f.charAt(f.length-1)!=="?"?"&":"")+e+(d.hash||"")},convertUrlToDataUrl:function(a){var b=m.parseUrl(a);if(m.isEmbeddedPage(b))return b.hash.split(s)[0].replace(/^#/,"");else if(m.isSameDomain(b,w))return b.hrefNoHash.replace(w.domain,"").split(s)[0];return a},get:function(a){if(a===c)a=location.hash;
+return m.stripHash(a).replace(/[^\/]*\.[^\/*]+$/,"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(s)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(w.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return m.stripHash(a.replace(/\?.*$/,"").replace(s,""))},isHashValid:function(a){return/^#[^#]+$/.test(a)},isExternal:function(a){a=m.parseUrl(a);return a.protocol&&
+a.domain!==x.domain?true:false},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var b=m.parseUrl(m.makeUrlAbsolute(b,w)),d=a.mobile.firstPage,d=d&&d[0]?d[0].id:c;return(b.hrefNoHash===x.hrefNoHash||D&&b.hrefNoHash===w.hrefNoHash)&&(!b.hash||b.hash==="#"||d&&b.hash.replace(/^#/,"")===d)},isEmbeddedPage:function(a){a=m.parseUrl(a);return a.protocol!==""?a.hash&&(a.hrefNoHash===x.hrefNoHash||D&&a.hrefNoHash===w.hrefNoHash):/^#/.test(a.href)},isPermittedCrossDomainRequest:function(b,
+c){return a.mobile.allowCrossDomainPages&&b.protocol==="file:"&&c.search(/^https?:/)!=-1}},l=null,n={stack:[],activeIndex:0,getActive:function(){return n.stack[n.activeIndex]},getPrev:function(){return n.stack[n.activeIndex-1]},getNext:function(){return n.stack[n.activeIndex+1]},addNew:function(a,b,c,d,e){n.getNext()&&n.clearForward();n.stack.push({url:a,transition:b,title:c,pageUrl:d,role:e});n.activeIndex=n.stack.length-1},clearForward:function(){n.stack=n.stack.slice(0,n.activeIndex+1)},directHashChange:function(b){var d,
+e,f;this.getActive();a.each(n.stack,function(a,c){b.currentUrl===c.url&&(d=a<n.activeIndex,e=!d,f=a)});this.activeIndex=f!==c?f:this.activeIndex;d?(b.either||b.isBack)(true):e&&(b.either||b.isForward)(false)},ignoreNextHashChange:false},r=[],p=false,s="&ui-state=dialog",u=o.children("base"),x=m.parseUrl(location.href),w=u.length?m.parseUrl(m.makeUrlAbsolute(u.attr("href"),x.href)):x,D=x.hrefNoHash!==w.hrefNoHash,y=a.mobile.getScreenHeight,B=a.support.dynamicBaseTag?{element:u.length?u:a("<base>",
+{href:w.hrefNoHash}).prependTo(o),set:function(a){B.element.attr("href",m.makeUrlAbsolute(a,w))},reset:function(){B.element.attr("href",w.hrefNoHash)}}:c;a.mobile.focusPage=function(a){var b=a.find("[autofocus]"),c=a.find(".ui-title:eq(0)");b.length?b.focus():c.length?c.focus():a.focus()};var C=true,z,A;z=function(){if(C){var b=a.mobile.urlHistory.getActive();if(b){var c=q.scrollTop();b.lastScroll=c<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:c}}};A=function(){setTimeout(z,100)};q.bind(a.support.pushState?
+"popstate":"hashchange",function(){C=false});q.one(a.support.pushState?"popstate":"hashchange",function(){C=true});q.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){C=true;q.unbind("scrollstop",A);q.bind("scrollstop",A)})});q.bind("scrollstop",A);a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd animationend",b):(setTimeout(b,0),a(this))};a.mobile.path=m;a.mobile.base=B;a.mobile.urlHistory=n;a.mobile.dialogHashKey=
+s;a.mobile.allowCrossDomainPages=false;a.mobile.getDocumentUrl=function(b){return b?a.extend({},x):x.href};a.mobile.getDocumentBase=function(b){return b?a.extend({},w):w.href};a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&&b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c);c.isDefaultPrevented()||b.removeWithDependents()})};a.mobile.loadPage=function(b,d){var e=a.Deferred(),j=a.extend({},
+a.mobile.loadPage.defaults,d),g=null,q=null,k=m.makeUrlAbsolute(b,a.mobile.activePage&&h(a.mobile.activePage)||w.hrefNoHash);if(j.data&&j.type==="get")k=m.addSearchParams(k,j.data),j.data=c;if(j.data&&j.type==="post")j.reloadPage=true;var n=m.getFilePath(k),o=m.convertUrlToDataUrl(k);j.pageContainer=j.pageContainer||a.mobile.pageContainer;g=j.pageContainer.children(":jqmData(url='"+o+"')");g.length===0&&o&&!m.isPath(o)&&(g=j.pageContainer.children("#"+o).attr("data-"+a.mobile.ns+"url",o));if(g.length===
+0)if(a.mobile.firstPage&&m.isFirstPageUrl(n))a.mobile.firstPage.parent().length&&(g=a(a.mobile.firstPage));else if(m.isEmbeddedPage(n))return e.reject(k,d),e.promise();B&&B.reset();if(g.length){if(!j.reloadPage)return f(g,j.role),e.resolve(k,d,g),e.promise();q=g}var l=j.pageContainer,u=new a.Event("pagebeforeload"),p={url:b,absUrl:k,dataUrl:o,deferred:e,options:j};l.trigger(u,p);if(u.isDefaultPrevented())return e.promise();if(j.showLoadMsg)var r=setTimeout(function(){a.mobile.showPageLoadingMsg()},
+j.loadMsgDelay);!a.mobile.allowCrossDomainPages&&!m.isSameDomain(x,k)?e.reject(k,d):a.ajax({url:n,type:j.type,data:j.data,dataType:"html",success:function(c,h,x){var l=a("<div></div>"),u=c.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,w=RegExp("\\bdata-"+a.mobile.ns+"url=[\"']?([^\"'>]*)[\"']?");RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)").test(c)&&RegExp.$1&&w.test(RegExp.$1)&&RegExp.$1&&(b=n=m.getFilePath(RegExp.$1));B&&B.set(n);l.get(0).innerHTML=c;g=l.find(":jqmData(role='page'), :jqmData(role='dialog')").first();
+g.length||(g=a("<div data-"+a.mobile.ns+"role='page'>"+c.split(/<\/?body[^>]*>/gmi)[1]+"</div>"));u&&!g.jqmData("title")&&(~u.indexOf("&")&&(u=a("<div>"+u+"</div>").text()),g.jqmData("title",u));if(!a.support.dynamicBaseTag){var s=m.get(n);g.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b),c=c.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(c)||
+a(this).attr(b,s+c)})}g.attr("data-"+a.mobile.ns+"url",m.convertUrlToDataUrl(n)).attr("data-"+a.mobile.ns+"external-page",true).appendTo(j.pageContainer);g.one("pagecreate",a.mobile._bindPageRemove);f(g,j.role);k.indexOf("&"+a.mobile.subPageUrlKey)>-1&&(g=j.pageContainer.children(":jqmData(url='"+o+"')"));j.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg());p.xhr=x;p.textStatus=h;p.page=g;j.pageContainer.trigger("pageload",p);e.resolve(k,d,g,q)},error:function(b,c,f){B&&B.set(m.get());p.xhr=
+b;p.textStatus=c;p.errorThrown=f;b=new a.Event("pageloadfailed");j.pageContainer.trigger(b,p);b.isDefaultPrevented()||(j.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg(),a.mobile.showPageLoadingMsg(a.mobile.pageLoadErrorMessageTheme,a.mobile.pageLoadErrorMessage,true),setTimeout(a.mobile.hidePageLoadingMsg,1500)),e.reject(k,d))}});return e.promise()};a.mobile.loadPage.defaults={type:"get",data:c,reloadPage:false,role:c,showLoadMsg:false,pageContainer:c,loadMsgDelay:50};a.mobile.changePage=
+function(j,g){if(p)r.unshift(arguments);else{var h=a.extend({},a.mobile.changePage.defaults,g);h.pageContainer=h.pageContainer||a.mobile.pageContainer;h.fromPage=h.fromPage||a.mobile.activePage;var q=h.pageContainer,o=new a.Event("pagebeforechange"),l={toPage:j,options:h};q.trigger(o,l);if(!o.isDefaultPrevented())if(j=l.toPage,p=true,typeof j=="string")a.mobile.loadPage(j,h).done(function(b,c,d,e){p=false;c.duplicateCachedPage=e;a.mobile.changePage(d,c)}).fail(function(){p=false;b(true);d();h.pageContainer.trigger("pagechangefailed",
+l)});else{if(j[0]===a.mobile.firstPage[0]&&!h.dataUrl)h.dataUrl=x.hrefNoHash;var o=h.fromPage,u=h.dataUrl&&m.convertUrlToDataUrl(h.dataUrl)||j.jqmData("url"),w=u;m.getFilePath(u);var v=n.getActive(),t=n.activeIndex===0,y=0,D=k.title,B=h.role==="dialog"||j.jqmData("role")==="dialog";if(o&&o[0]===j[0]&&!h.allowSamePageTransition)p=false,q.trigger("pagechange",l),h.fromHashChange&&n.directHashChange({currentUrl:u,isBack:function(){},isForward:function(){}});else{f(j,h.role);h.fromHashChange&&n.directHashChange({currentUrl:u,
+isBack:function(){y=-1},isForward:function(){y=1}});try{k.activeElement&&k.activeElement.nodeName.toLowerCase()!="body"?a(k.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(z){}var C=false;if(B&&v){if(v.url.indexOf(s)>-1&&!a.mobile.activePage.is(".ui-dialog"))h.changeHash=false,C=true;u=(v.url||"")+s;n.activeIndex===0&&u===n.initialDst&&(u+=s)}if(h.changeHash!==false&&u)n.ignoreNextHashChange=true,m.set(u);var A=!v?D:j.jqmData("title")||j.children(":jqmData(role='header')").find(".ui-title").getEncodedText();
+A&&D==k.title&&(D=A);j.jqmData("title")||j.jqmData("title",D);h.transition=h.transition||(y&&!t?v.transition:c)||(B?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition);!y&&!C&&n.addNew(u,h.transition,D,w,h.role);k.title=n.getActive().title;a.mobile.activePage=j;h.reverse=h.reverse||y<0;e(j,o,h.transition,h.reverse).done(function(c,e,f,g,m){b();h.duplicateCachedPage&&h.duplicateCachedPage.remove();m||a.mobile.focusPage(j);d();q.trigger("pagechange",l)})}}}};a.mobile.changePage.defaults=
+{transition:c,reverse:false,changeHash:true,fromHashChange:false,role:c,duplicateCachedPage:c,pageContainer:c,showLoadMsg:true,dataUrl:c,fromPage:c,allowSamePageTransition:false};a.mobile.navreadyDeferred=a.Deferred();a.mobile.navreadyDeferred.done(function(){a(k).delegate("form","submit",function(b){var c=a(this);if(a.mobile.ajaxEnabled&&!c.is(":jqmData(ajax='false')")&&c.jqmHijackable().length){var d=c.attr("method"),e=c.attr("target"),j=c.attr("action");if(!j&&(j=h(c),j===w.hrefNoHash))j=x.hrefNoSearch;
+j=m.makeUrlAbsolute(j,h(c));m.isExternal(j)&&!m.isPermittedCrossDomainRequest(x,j)||e||(a.mobile.changePage(j,{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),direction:c.jqmData("direction"),reloadPage:true}),b.preventDefault())}});a(k).bind("vclick",function(c){if(!(c.which>1)&&a.mobile.linkBindingEnabled&&(c=j(c.target),a(c).jqmHijackable().length&&c&&m.parseUrl(c.getAttribute("href")||"#").hash!=="#"))b(true),l=a(c).closest(".ui-btn").not(".ui-disabled"),
+l.addClass(a.mobile.activeBtnClass)});a(k).bind("click",function(d){if(a.mobile.linkBindingEnabled){var e=j(d.target),f=a(e),g;if(e&&!(d.which>1)&&f.jqmHijackable().length){g=function(){t.setTimeout(function(){b(true)},200)};if(f.is(":jqmData(rel='back')"))return t.history.back(),false;var k=h(f),e=m.makeUrlAbsolute(f.attr("href")||"#",k);if(!a.mobile.ajaxEnabled&&!m.isEmbeddedPage(e))g();else{if(e.search("#")!=-1)if(e=e.replace(/[^#]*#/,""))e=m.isPath(e)?m.makeUrlAbsolute(e,k):m.makeUrlAbsolute("#"+
+e,x.hrefNoHash);else{d.preventDefault();return}f.is("[rel='external']")||f.is(":jqmData(ajax='false')")||f.is("[target]")||m.isExternal(e)&&!m.isPermittedCrossDomainRequest(x,e)?g():(g=f.jqmData("transition"),k=(k=f.jqmData("direction"))&&k==="reverse"||f.jqmData("back"),f=f.attr("data-"+a.mobile.ns+"rel")||c,a.mobile.changePage(e,{transition:g,reverse:k,role:f}),d.preventDefault())}}}});a(k).delegate(".ui-page","pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=
+a(this),d=c.attr("href");d&&a.inArray(d,b)===-1&&(b.push(d),a.mobile.loadPage(d,{role:c.attr("data-"+a.mobile.ns+"rel")}))})});a.mobile._handleHashChange=function(b){var d=m.stripHash(b),e={transition:a.mobile.urlHistory.stack.length===0?"none":c,changeHash:false,fromHashChange:true};if(0===n.stack.length)n.initialDst=d;if(!a.mobile.hashListeningEnabled||n.ignoreNextHashChange)n.ignoreNextHashChange=false;else{if(n.stack.length>1&&d.indexOf(s)>-1&&n.initialDst!==d)if(a.mobile.activePage.is(".ui-dialog"))n.directHashChange({currentUrl:d,
+either:function(b){var c=a.mobile.urlHistory.getActive();d=c.pageUrl;a.extend(e,{role:c.role,transition:c.transition,reverse:b})}});else{n.directHashChange({currentUrl:d,isBack:function(){t.history.back()},isForward:function(){t.history.forward()}});return}d?(d=typeof d==="string"&&!m.isPath(d)?m.makeUrlAbsolute("#"+d,w):d,a.mobile.changePage(d,e)):a.mobile.changePage(a.mobile.firstPage,e)}};q.bind("hashchange",function(){a.mobile._handleHashChange(location.hash)});a(k).bind("pageshow",g);a(t).bind("throttledresize",
+g)})})(l);(function(a,c){var b={},d=a(c),e=a.mobile.path.parseUrl(location.href),g=a.Deferred(),f=a.Deferred();a(k).ready(a.proxy(f,"resolve"));a(k).one("mobileinit",a.proxy(g,"resolve"));a.extend(b,{initialFilePath:e.pathname+e.search,hashChangeTimeout:200,hashChangeEnableTimer:E,initialHref:e.hrefNoHash,state:function(){return{hash:location.hash||"#"+b.initialFilePath,title:k.title,initialHref:b.initialHref}},resetUIKeys:function(b){var c="&"+a.mobile.subPageUrlKey,d=b.indexOf(a.mobile.dialogHashKey);
+d>-1?b=b.slice(0,d)+"#"+b.slice(d):b.indexOf(c)>-1&&(b=b.split(c).join("#"+c));return b},nextHashChangePrevented:function(c){a.mobile.urlHistory.ignoreNextHashChange=c;b.onHashChangeDisabled=c},onHashChange:function(){if(!b.onHashChangeDisabled){var c,d;c=location.hash;var e=a.mobile.path.isPath(c),f=e?location.href:a.mobile.getDocumentUrl();c=e?c.replace("#",""):c;d=b.state();c=a.mobile.path.makeUrlAbsolute(c,f);e&&(c=b.resetUIKeys(c));history.replaceState(d,k.title,c)}},onPopState:function(c){if(c=
+c.originalEvent.state)clearTimeout(b.hashChangeEnableTimer),b.nextHashChangePrevented(false),a.mobile._handleHashChange(c.hash),b.nextHashChangePrevented(true),b.hashChangeEnableTimer=setTimeout(function(){b.nextHashChangePrevented(false)},b.hashChangeTimeout)},init:function(){d.bind("hashchange",b.onHashChange);d.bind("popstate",b.onPopState);location.hash===""&&history.replaceState(b.state(),k.title,location.href)}});a.when(f,g,a.mobile.navreadyDeferred).done(function(){a.mobile.pushStateEnabled&&
+a.support.pushState&&b.init()})})(l,this);l.mobile.transitionFallbacks.pop="fade";(function(a){a.mobile.transitionHandlers.slide=a.mobile.transitionHandlers.simultaneous;a.mobile.transitionFallbacks.slide="fade"})(l,this);l.mobile.transitionFallbacks.slidedown="fade";l.mobile.transitionFallbacks.slideup="fade";l.mobile.transitionFallbacks.flip="fade";l.mobile.transitionFallbacks.flow="fade";l.mobile.transitionFallbacks.turn="fade";(function(a){a.mobile.page.prototype.options.degradeInputs={color:false,
+date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:"text",tel:false,time:false,url:false,week:false};a(k).bind("pagecreate create",function(c){var b=a.mobile.closestPageData(a(c.target)),d;if(b)d=b.options,a(c.target).find("input").not(b.keepNativeSelector()).each(function(){var b=a(this),c=this.getAttribute("type"),f=d.degradeInputs[c]||"text";if(d.degradeInputs[c]){var j=a("<div>").html(b.clone()).html(),h=j.indexOf(" type=")>-1;b.replaceWith(j.replace(h?
+/\s+type=["']?\w+['"]?/:/\/?>/,' type="'+f+'" data-'+a.mobile.ns+'type="'+c+'"'+(h?"":">")))}})})})(l);(function(a,c){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,c=this.element,e=a("<a href='#' data-"+a.mobile.ns+"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>"),g=a("<div/>",{role:"dialog","class":"ui-dialog-contain ui-corner-all ui-overlay-shadow"});
+c.addClass("ui-dialog ui-overlay-"+this.options.overlayTheme);c.wrapInner(g).children().find(":jqmData(role='header')").prepend(e).end().children(":first-child").addClass("ui-corner-top").end().children(":last-child").addClass("ui-corner-bottom");e.bind("click",function(){b.close()});c.bind("vclick submit",function(b){var b=a(b.target).closest(b.type==="vclick"?"a":"form"),c;b.length&&!b.jqmData("transition")&&(c=a.mobile.urlHistory.getActive()||{},b.attr("data-"+a.mobile.ns+"transition",c.transition||
+a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(){b._isClosed=false;a(this).find("."+a.mobile.activeBtnClass).not(".ui-slider-bg").removeClass(a.mobile.activeBtnClass)}).bind("pagebeforeshow",function(){b.options.overlayTheme&&b.element.page("removeContainerBackground").page("setContainerBackground",b.options.overlayTheme)})},close:function(){if(!this._isClosed)this._isClosed=true,a.mobile.hashListeningEnabled?c.history.back():a.mobile.changePage(a.mobile.urlHistory.getPrev().url)}});
+a(k).delegate(a.mobile.dialog.prototype.options.initSelector,"pagecreate",function(){a.mobile.dialog.prototype.enhance(this)})})(l,this);(function(a){a.mobile.page.prototype.options.backBtnText="Back";a.mobile.page.prototype.options.addBackBtn=false;a.mobile.page.prototype.options.backBtnTheme=null;a.mobile.page.prototype.options.headerTheme="a";a.mobile.page.prototype.options.footerTheme="a";a.mobile.page.prototype.options.contentTheme=null;a(k).bind("pagecreate",function(c){var b=a(c.target),d=
+b.data("page").options,e=b.jqmData("role"),g=d.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",b).jqmEnhanceable().each(function(){var c=a(this),j=c.jqmData("role"),h=c.jqmData("theme"),k=h||d.contentTheme||e==="dialog"&&g,o;c.addClass("ui-"+j);if(j==="header"||j==="footer"){var m=h||(j==="header"?d.headerTheme:d.footerTheme)||g;c.addClass("ui-bar-"+m).attr("role",j==="header"?"banner":"contentinfo");j==="header"&&(h=c.children("a"),o=h.hasClass("ui-btn-left"),
+k=h.hasClass("ui-btn-right"),o=o||h.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length,k||h.eq(1).addClass("ui-btn-right"));d.addBackBtn&&j==="header"&&a(".ui-page").length>1&&b.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!o&&a("<a href='javascript:void(0);' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+d.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",d.backBtnTheme||m).prependTo(c);c.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({role:"heading",
+"aria-level":"1"})}else j==="content"&&(k&&c.addClass("ui-body-"+k),c.attr("role","main"))})})})(l);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br").contents().filter(function(){return this.nodeType===3&&!/\S/.test(this.nodeValue)}).remove()};a(k).bind("pagecreate create",function(c){a(":jqmData(role='fieldcontain')",c.target).jqmEnhanceable().fieldcontain()})})(l);(function(a){a.fn.grid=function(c){return this.each(function(){var b=a(this),d=a.extend({grid:null},
+c),e=b.children(),g={solo:1,a:2,b:3,c:4,d:5},d=d.grid;if(!d)if(e.length<=5)for(var f in g)g[f]===e.length&&(d=f);else d="a",b.addClass("ui-grid-duo");g=g[d];b.addClass("ui-grid-"+d);e.filter(":nth-child("+g+"n+1)").addClass("ui-block-a");g>1&&e.filter(":nth-child("+g+"n+2)").addClass("ui-block-b");g>2&&e.filter(":nth-child(3n+3)").addClass("ui-block-c");g>3&&e.filter(":nth-child(4n+4)").addClass("ui-block-d");g>4&&e.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(l);(function(a){a(k).bind("pagecreate create",
+function(c){a(":jqmData(role='nojs')",c.target).addClass("ui-nojs")})})(l);(function(a,c){function b(a){for(var b;a;){if((b=typeof a.className==="string"&&a.className+" ")&&b.indexOf("ui-btn ")>-1&&b.indexOf("ui-disabled ")<0)break;a=a.parentNode}return a}a.fn.buttonMarkup=function(b){for(var b=b&&a.type(b)=="object"?b:{},g=0;g<this.length;g++){var f=this.eq(g),j=f[0],h=a.extend({},a.fn.buttonMarkup.defaults,{icon:b.icon!==c?b.icon:f.jqmData("icon"),iconpos:b.iconpos!==c?b.iconpos:f.jqmData("iconpos"),
+theme:b.theme!==c?b.theme:f.jqmData("theme")||a.mobile.getInheritedTheme(f,"c"),inline:b.inline!==c?b.inline:f.jqmData("inline"),shadow:b.shadow!==c?b.shadow:f.jqmData("shadow"),corners:b.corners!==c?b.corners:f.jqmData("corners"),iconshadow:b.iconshadow!==c?b.iconshadow:f.jqmData("iconshadow"),mini:b.mini!==c?b.mini:f.jqmData("mini")},b),q="ui-btn-inner",o,m,l,n,r,p;a.each(h,function(b,c){j.setAttribute("data-"+a.mobile.ns+b,c);f.jqmData(b,c)});(p=a.data(j.tagName==="INPUT"||j.tagName==="BUTTON"?
+j.parentNode:j,"buttonElements"))?(j=p.outer,f=a(j),l=p.inner,n=p.text,a(p.icon).remove(),p.icon=null):(l=k.createElement(h.wrapperEls),n=k.createElement(h.wrapperEls));r=h.icon?k.createElement("span"):null;d&&!p&&d();if(!h.theme)h.theme=a.mobile.getInheritedTheme(f,"c");o="ui-btn ui-btn-up-"+h.theme;o+=h.inline?" ui-btn-inline":"";o+=h.shadow?" ui-shadow":"";o+=h.corners?" ui-btn-corner-all":"";h.mini!==c&&(o+=h.mini?" ui-mini":" ui-fullsize");h.inline!==c&&(o+=h.inline===false?" ui-btn-block":" ui-btn-inline");
+if(h.icon)h.icon="ui-icon-"+h.icon,h.iconpos=h.iconpos||"left",m="ui-icon "+h.icon,h.iconshadow&&(m+=" ui-icon-shadow");h.iconpos&&(o+=" ui-btn-icon-"+h.iconpos,h.iconpos=="notext"&&!f.attr("title")&&f.attr("title",f.getEncodedText()));q+=h.corners?" ui-btn-corner-all":"";h.iconpos&&h.iconpos==="notext"&&!f.attr("title")&&f.attr("title",f.getEncodedText());p&&f.removeClass(p.bcls||"");f.removeClass("ui-link").addClass(o);l.className=q;n.className="ui-btn-text";p||l.appendChild(n);if(r&&(r.className=
+m,!p||!p.icon))r.appendChild(k.createTextNode("\u00a0")),l.appendChild(r);for(;j.firstChild&&!p;)n.appendChild(j.firstChild);p||j.appendChild(l);p={bcls:o,outer:j,inner:l,text:n,icon:r};a.data(j,"buttonElements",p);a.data(l,"buttonElements",p);a.data(n,"buttonElements",p);r&&a.data(r,"buttonElements",p)}return this};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,wrapperEls:"span"};var d=function(){var c=a.mobile.buttonMarkup.hoverDelay,g,f;a(k).bind({"vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart":function(d){var h,
+k=a(b(d.target)),d=d.type;if(k.length)if(h=k.attr("data-"+a.mobile.ns+"theme"),d==="vmousedown")a.support.touch?g=setTimeout(function(){k.removeClass("ui-btn-up-"+h).addClass("ui-btn-down-"+h)},c):k.removeClass("ui-btn-up-"+h).addClass("ui-btn-down-"+h);else if(d==="vmousecancel"||d==="vmouseup")k.removeClass("ui-btn-down-"+h).addClass("ui-btn-up-"+h);else if(d==="vmouseover"||d==="focus")a.support.touch?f=setTimeout(function(){k.removeClass("ui-btn-up-"+h).addClass("ui-btn-hover-"+h)},c):k.removeClass("ui-btn-up-"+
+h).addClass("ui-btn-hover-"+h);else if(d==="vmouseout"||d==="blur"||d==="scrollstart")k.removeClass("ui-btn-hover-"+h+" ui-btn-down-"+h).addClass("ui-btn-up-"+h),g&&clearTimeout(g),f&&clearTimeout(f)},"focusin focus":function(c){a(b(c.target)).addClass(a.mobile.focusClass)},"focusout blur":function(c){a(b(c.target)).removeClass(a.mobile.focusClass)}});d=null};a(k).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a",
+b.target).not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})})(l);(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:true,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,iconTheme:"d",mini:false,initSelector:":jqmData(role='collapsible')"},_create:function(){var c=this.element,b=this.options,d=c.addClass("ui-collapsible"),e=c.children(b.heading).first(),
+g=d.wrapInner("<div class='ui-collapsible-content'></div>").find(".ui-collapsible-content"),f=c.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set");e.is("legend")&&(e=a("<div role='heading'>"+e.html()+"</div>").insertBefore(e),e.next().remove());if(f.length){if(!b.theme)b.theme=f.jqmData("theme")||a.mobile.getInheritedTheme(f,"c");if(!b.contentTheme)b.contentTheme=f.jqmData("content-theme");if(!b.iconPos)b.iconPos=f.jqmData("iconpos");if(!b.mini)b.mini=f.jqmData("mini")}g.addClass(b.contentTheme?
+"ui-body-"+b.contentTheme:"");e.insertBefore(g).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:false,corners:false,iconpos:c.jqmData("iconpos")||b.iconPos||"left",icon:"plus",mini:b.mini,theme:b.theme}).add(".ui-btn-inner",c).addClass("ui-corner-top ui-corner-bottom");d.bind("expand collapse",function(c){if(!c.isDefaultPrevented()){c.preventDefault();
+var h=a(this),c=c.type==="collapse",k=b.contentTheme;e.toggleClass("ui-collapsible-heading-collapsed",c).find(".ui-collapsible-heading-status").text(c?b.expandCueText:b.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-minus",!c).toggleClass("ui-icon-plus",c).end().find("a").first().removeClass(a.mobile.activeBtnClass);h.toggleClass("ui-collapsible-collapsed",c);g.toggleClass("ui-collapsible-content-collapsed",c).attr("aria-hidden",c);if(k&&(!f.length||d.jqmData("collapsible-last")))e.find("a").first().add(e.find(".ui-btn-inner")).toggleClass("ui-corner-bottom",
+c),g.toggleClass("ui-corner-bottom",!c);g.trigger("updatelayout")}}).trigger(b.collapsed?"collapse":"expand");e.bind("tap",function(){e.find("a").first().addClass(a.mobile.activeBtnClass)}).bind("click",function(a){var b=e.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";d.trigger(b);a.preventDefault();a.stopPropagation()})}});a(k).bind("pagecreate create",function(c){a.mobile.collapsible.prototype.enhanceWithin(c.target)})})(l);(function(a,c){a.widget("mobile.collapsibleset",a.mobile.widget,
+{options:{initSelector:":jqmData(role='collapsible-set')"},_create:function(){var b=this.element.addClass("ui-collapsible-set"),d=this.options;if(!d.theme)d.theme=a.mobile.getInheritedTheme(b,"c");if(!d.contentTheme)d.contentTheme=b.jqmData("content-theme");if(!d.corners)d.corners=b.jqmData("corners")===c?true:false;b.jqmData("collapsiblebound")||b.jqmData("collapsiblebound",true).bind("expand collapse",function(b){var c=b.type==="collapse",b=a(b.target).closest(".ui-collapsible"),d=b.data("collapsible");
+d.options.contentTheme&&b.jqmData("collapsible-last")&&(b.find(d.options.heading).first().find("a").first().toggleClass("ui-corner-bottom",c).find(".ui-btn-inner").toggleClass("ui-corner-bottom",c),b.find(".ui-collapsible-content").toggleClass("ui-corner-bottom",!c))}).bind("expand",function(b){a(b.target).closest(".ui-collapsible").siblings(".ui-collapsible").trigger("collapse")})},_init:function(){this.refresh()},refresh:function(){var b=this.options,c=this.element.children(":jqmData(role='collapsible')");
+a.mobile.collapsible.prototype.enhance(c.not(".ui-collapsible"));c.each(function(){a(this).find(a.mobile.collapsible.prototype.options.heading).find("a").first().removeClass("ui-corner-top ui-corner-bottom").find(".ui-btn-inner").removeClass("ui-corner-top ui-corner-bottom")});c.first().find("a").first().addClass(b.corners?"ui-corner-top":"").find(".ui-btn-inner").addClass("ui-corner-top");c.last().jqmData("collapsible-last",true).find("a").first().addClass(b.corners?"ui-corner-bottom":"").find(".ui-btn-inner").addClass("ui-corner-bottom")}});
+a(k).bind("pagecreate create",function(b){a.mobile.collapsibleset.prototype.enhanceWithin(b.target)})})(l);(function(a,c){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"},_create:function(){var b=this.element,d=b.find("a"),e=d.filter(":jqmData(icon)").length?this.options.iconpos:c;b.addClass("ui-navbar ui-mini").attr("role","navigation").find("ul").jqmEnhanceable().grid({grid:this.options.grid});d.buttonMarkup({corners:false,shadow:false,
+inline:true,iconpos:e});b.delegate("a","vclick",function(b){a(b.target).hasClass("ui-disabled")||(d.removeClass(a.mobile.activeBtnClass),a(this).addClass(a.mobile.activeBtnClass))});b.closest(".ui-page").bind("pagebeforeshow",function(){d.filter(".ui-state-persist").addClass(a.mobile.activeBtnClass)})}});a(k).bind("pagecreate create",function(b){a.mobile.navbar.prototype.enhanceWithin(b.target)})})(l);(function(a){var c={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",
+headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:false,initSelector:":jqmData(role='listview')"},_create:function(){var a="";a+=this.options.inset?" ui-listview-inset ui-corner-all ui-shadow ":"";this.element.addClass(function(c,e){return e+" ui-listview "+a});this.refresh(true)},_removeCorners:function(a,c){a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));c==="top"?a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl"):c==="bottom"?a.removeClass("ui-corner-bottom ui-corner-br ui-corner-bl"):
+a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl ui-corner-bottom ui-corner-br ui-corner-bl")},_refreshCorners:function(a){var c,e;this.options.inset&&(c=this.element.children("li"),e=a?c.not(".ui-screen-hidden"):c.filter(":visible"),this._removeCorners(c),c=e.first().addClass("ui-corner-top"),c.add(c.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),
+e=e.last().addClass("ui-corner-bottom"),e.add(e.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl"));a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,c,e,g){var f={};for(f[e]=f[g]=true;a;){if(f[a.nodeName])return a;a=a[c]}return null},_getChildrenByTagName:function(b,c,e){var g=[],f={};f[c]=f[e]=true;for(b=b.firstChild;b;)f[b.nodeName]&&g.push(b),b=b.nextSibling;return a(g)},_addThumbClasses:function(b){var c,
+e,g=b.length;for(c=0;c<g;c++)e=a(this._findFirstElementByTagName(b[c].firstChild,"nextSibling","img","IMG")),e.length&&(e.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(e[0].parentNode,"parentNode","li","LI")).addClass(e.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page");this._createSubPages();var c=this.options,e=this.element,g=e.jqmData("dividertheme")||c.dividerTheme,f=e.jqmData("splittheme"),j=e.jqmData("spliticon"),
+h=this._getChildrenByTagName(e[0],"li","LI"),l=a.support.cssPseudoElement||!a.nodeName(e[0],"ol")?0:1,o={},m,v,n,r,p,s,u;l&&e.find(".ui-li-dec").remove();if(!c.theme)c.theme=a.mobile.getInheritedTheme(this.element,"c");for(var x=0,w=h.length;x<w;x++){m=h.eq(x);v="ui-li";if(b||!m.hasClass("ui-li"))n=m.jqmData("theme")||c.theme,r=this._getChildrenByTagName(m[0],"a","A"),s=m.jqmData("role")==="list-divider",r.length&&!s?(s=m.jqmData("icon"),m.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,
+iconpos:"right",icon:r.length>1||s===false?false:s||"arrow-r",theme:n}),s!=false&&r.length==1&&m.addClass("ui-li-has-arrow"),r.first().removeClass("ui-link").addClass("ui-link-inherit"),r.length>1&&(v+=" ui-li-has-alt",r=r.last(),p=f||r.jqmData("theme")||c.splitTheme,u=r.jqmData("icon"),r.appendTo(m).attr("title",r.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,corners:false,theme:n,icon:false,iconpos:"notext"}).find(".ui-btn-inner").append(a(k.createElement("span")).buttonMarkup({shadow:true,
+corners:true,theme:p,iconpos:"notext",icon:u||s||j||c.splitIcon})))):s?(v+=" ui-li-divider ui-bar-"+g,m.attr("role","heading"),l&&(l=1)):v+=" ui-li-static ui-body-"+n;l&&v.indexOf("ui-li-divider")<0&&(n=m.is(".ui-li-static:first")?m:m.find(".ui-link-inherit"),n.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+l++ +". </span>"));o[v]||(o[v]=[]);o[v].push(m[0])}for(v in o)a(o[v]).addClass(v).children(".ui-btn-inner").addClass(v);e.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=
+a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+(e.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all");this._addThumbClasses(h);this._addThumbClasses(e.find(".ui-link-inherit"));this._refreshCorners(b)},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,d=b.closest(".ui-page"),e=d.jqmData("url"),g=e||d[0][a.expando],
+f=b.attr("id"),j=this.options,h="data-"+a.mobile.ns,k=this,l=d.find(":jqmData(role='footer')").jqmData("id"),m;typeof c[g]==="undefined"&&(c[g]=-1);f=f||++c[g];a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=a(this),g=d.attr("id")||f+"-"+c,c=d.parent(),k=a(d.prevAll().toArray().reverse()),k=k.length?k:a("<span>"+a.trim(c.contents()[0].nodeValue)+"</span>"),q=k.first().getEncodedText(),g=(e||"")+"&"+a.mobile.subPageUrlKey+"="+g,u=d.jqmData("theme")||j.theme,x=d.jqmData("counttheme")||
+b.jqmData("counttheme")||j.countTheme;m=true;d.detach().wrap("<div "+h+"role='page' "+h+"url='"+g+"' "+h+"theme='"+u+"' "+h+"count-theme='"+x+"'><div "+h+"role='content'></div></div>").parent().before("<div "+h+"role='header' "+h+"theme='"+j.headerTheme+"'><div class='ui-title'>"+q+"</div></div>").after(l?a("<div "+h+"role='footer' "+h+"id='"+l+"'>"):"").parent().appendTo(a.mobile.pageContainer).page();d=c.find("a:first");d.length||(d=a("<a/>").html(k||q).prependTo(c.empty()));d.attr("href","#"+g)}).listview();
+m&&d.is(":jqmData(external-page='true')")&&d.data("page").options.domCache===false&&d.unbind("pagehide.remove").bind("pagehide.remove",function(b,c){var f=c.nextPage,h=new a.Event("pageremove");c.nextPage&&(f=f.jqmData("url"),f.indexOf(e+"&"+a.mobile.subPageUrlKey)!==0&&(k.childPages().remove(),d.trigger(h),h.isDefaultPrevented()||d.removeWithDependents()))})},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}});a(k).bind("pagecreate create",
+function(b){a.mobile.listview.prototype.enhanceWithin(b.target)})})(l);(function(a,c){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"},_create:function(){var b=this,d=this.element,e=a(d).closest("label"),g=e.length?e:a(d).closest("form,fieldset,:jqmData(role='page'),:jqmData(role='dialog')").find("label").filter("[for='"+d[0].id+"']"),f=d[0].type,e=d.jqmData("mini")||d.closest("form,fieldset").jqmData("mini"),j=f+"-on",
+h=f+"-off",l=d.parents(":jqmData(type='horizontal')").length?c:h,o=d.jqmData("iconpos")||d.closest("form,fieldset").jqmData("iconpos");if(!(f!=="checkbox"&&f!=="radio")){a.extend(this,{label:g,inputtype:f,checkedClass:"ui-"+j+(l?"":" "+a.mobile.activeBtnClass),uncheckedClass:"ui-"+h,checkedicon:"ui-icon-"+j,uncheckedicon:"ui-icon-"+h});if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");g.buttonMarkup({theme:this.options.theme,icon:l,shadow:false,mini:e,iconpos:o});
+e=k.createElement("div");e.className="ui-"+f;d.add(g).wrapAll(e);g.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(d.is(":disabled"))a.preventDefault();else return b._cacheVals(),d.prop("checked",f==="radio"&&true||!d.prop("checked")),d.triggerHandler("click"),b._getInputSet().not(d).prop("checked",false),b._updateAll(),false}});d.bind({vmousedown:function(){b._cacheVals()},vclick:function(){var c=a(this);c.is(":checked")?(c.prop("checked",
+true),b._getInputSet().not(c).prop("checked",false)):c.prop("checked",false);b._updateAll()},focus:function(){g.addClass(a.mobile.focusClass)},blur:function(){g.removeClass(a.mobile.focusClass)}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){a(this).jqmData("cacheVal",this.checked)})},_getInputSet:function(){return this.inputtype==="checkbox"?this.element:this.element.closest("form,fieldset,:jqmData(role='page')").find("input[name='"+this.element[0].name+"'][type='"+
+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var c=a(this);(this.checked||b.inputtype==="checkbox")&&c.trigger("change")}).checkboxradio("refresh")},refresh:function(){var a=this.element[0],c=this.label,e=c.find(".ui-icon");a.checked?(c.addClass(this.checkedClass).removeClass(this.uncheckedClass),e.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(c.removeClass(this.checkedClass).addClass(this.uncheckedClass),e.removeClass(this.checkedicon).addClass(this.uncheckedicon));
+a.disabled?this.disable():this.enable()},disable:function(){this.element.prop("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",false).parent().removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target,true)})})(l);(function(a,c){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:false,corners:true,shadow:true,iconshadow:true,initSelector:"button, [type='button'], [type='submit'], [type='reset'], [type='image']",
+mini:false},_create:function(){var b=this.element,d,e=this.options,g;g="";var f;if(b[0].tagName==="A")!b.hasClass("ui-btn")&&b.buttonMarkup();else{if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");~b[0].className.indexOf("ui-btn-left")&&(g="ui-btn-left");~b[0].className.indexOf("ui-btn-right")&&(g="ui-btn-right");if(b.attr("type")==="submit"||b.attr("type")==="reset")g?g+=" ui-submit":g="ui-submit";a("label[for='"+b.attr("id")+"']").addClass("ui-submit");d=this.button=
+a("<div></div>").text(b.text()||b.val()).insertBefore(b).buttonMarkup({theme:e.theme,icon:e.icon,iconpos:e.iconpos,inline:e.inline,corners:e.corners,shadow:e.shadow,iconshadow:e.iconshadow,mini:e.mini}).addClass(g).append(b.addClass("ui-btn-hidden"));e=b.attr("type");g=b.attr("name");e!=="button"&&e!=="reset"&&g&&b.bind("vclick",function(){f===c&&(f=a("<input>",{type:"hidden",name:b.attr("name"),value:b.attr("value")}).insertBefore(b),a(k).one("submit",function(){f.remove();f=c}))});b.bind({focus:function(){d.addClass(a.mobile.focusClass)},
+blur:function(){d.removeClass(a.mobile.focusClass)}});this.refresh()}},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)},refresh:function(){var b=this.element;b.prop("disabled")?this.disable():this.enable();a(this.button.data("buttonElements").text).text(b.text()||
+b.val())}});a(k).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target,true)})})(l);(function(a){a.fn.controlgroup=function(c){function b(a,b){a.removeClass("ui-btn-corner-all ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-controlgroup-last ui-shadow").eq(0).addClass(b[0]).end().last().addClass(b[1]).addClass("ui-controlgroup-last")}return this.each(function(){var d=a(this),e=a.extend({direction:d.jqmData("type")||"vertical",shadow:false,excludeInvisible:true,
+mini:d.jqmData("mini")},c),g=d.children("legend"),f=e.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];d.find("input").first().attr("type");d.wrapInner("<div class='ui-controlgroup-controls'></div>");g.length&&(a("<div role='heading' class='ui-controlgroup-label'>"+g.html()+"</div>").insertBefore(d.children(0)),g.remove());d.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+e.direction);b(d.find(".ui-btn"+(e.excludeInvisible?":visible":"")).not(".ui-slider-handle"),
+f);b(d.find(".ui-btn-inner"),f);e.shadow&&d.addClass("ui-shadow");e.mini&&d.addClass("ui-mini")})}})(l);(function(a){a(k).bind("pagecreate create",function(c){a(c.target).find("a").jqmEnhanceable().not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})})(l);(function(a){var c=a("meta[name=viewport]"),b=c.attr("content"),d=b+",maximum-scale=1, user-scalable=no",e=b+",maximum-scale=10, user-scalable=yes",g=/(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test(b);
+a.mobile.zoom=a.extend({},{enabled:!g,locked:false,disable:function(b){if(!g&&!a.mobile.zoom.locked)c.attr("content",d),a.mobile.zoom.enabled=false,a.mobile.zoom.locked=b||false},enable:function(b){if(!g&&(!a.mobile.zoom.locked||b===true))c.attr("content",e),a.mobile.zoom.enabled=true,a.mobile.zoom.locked=false},restore:function(){if(!g)c.attr("content",b),a.mobile.zoom.enabled=true}})})(l);(function(a){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&
+navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])",clearSearchButtonText:"clear text"},_create:function(){var c=
+this.element,b=this.options,d=b.theme||a.mobile.getInheritedTheme(this.element,"c"),e=" ui-body-"+d,g=c.jqmData("mini")==true,f=g?" ui-mini":"",j,h;a("label[for='"+c.attr("id")+"']").addClass("ui-input-text");j=c.addClass("ui-input-text ui-body-"+d);typeof c[0].autocorrect!=="undefined"&&!a.support.touchOverflow&&(c[0].setAttribute("autocorrect","off"),c[0].setAttribute("autocomplete","off"));c.is("[type='search'],:jqmData(type='search')")?(j=c.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+
+e+f+"'></div>").parent(),h=a("<a href='#' class='ui-input-clear' title='"+b.clearSearchButtonText+"'>"+b.clearSearchButtonText+"</a>").bind("click",function(a){c.val("").focus().trigger("change");h.addClass("ui-input-clear-hidden");a.preventDefault()}).appendTo(j).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true,mini:g}),d=function(){setTimeout(function(){h.toggleClass("ui-input-clear-hidden",!c.val())},0)},d(),c.bind("paste cut keyup focus change blur",d)):c.addClass("ui-corner-all ui-shadow-inset"+
+e+f);c.focus(function(){j.addClass(a.mobile.focusClass)}).blur(function(){j.removeClass(a.mobile.focusClass)}).bind("focus",function(){b.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("blur",function(){b.preventFocusZoom&&a.mobile.zoom.enable(true)});if(c.is("textarea")){var l=function(){var a=c[0].scrollHeight;c[0].clientHeight<a&&c.height(a+15)},o;c.keyup(function(){clearTimeout(o);o=setTimeout(l,100)});a(k).one("pagechange",l);a.trim(c.val())&&a(t).load(l)}},disable:function(){(this.element.attr("disabled",
+true).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(c){a.mobile.textinput.prototype.enhanceWithin(c.target,true)})})(l);(function(a){a.mobile.listview.prototype.options.filter=false;a.mobile.listview.prototype.options.filterPlaceholder=
+"Filter items...";a.mobile.listview.prototype.options.filterTheme="c";a.mobile.listview.prototype.options.filterCallback=function(a,b){return a.toLowerCase().indexOf(b)===-1};a(k).delegate(":jqmData(role='listview')","listviewcreate",function(){var c=a(this),b=c.data("listview");if(b.options.filter){var d=a("<form>",{"class":"ui-listview-filter ui-bar-"+b.options.filterTheme,role:"search"});a("<input>",{placeholder:b.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval",
+"").bind("keyup change",function(){var d=a(this),g=this.value.toLowerCase(),f=null,f=d.jqmData("lastval")+"",j=false,h="";d.jqmData("lastval",g);f=g.length<f.length||g.indexOf(f)!==0?c.children():c.children(":not(.ui-screen-hidden)");if(g){for(var k=f.length-1;k>=0;k--)d=a(f[k]),h=d.jqmData("filtertext")||d.text(),d.is("li:jqmData(role=list-divider)")?(d.toggleClass("ui-filter-hidequeue",!j),j=false):b.options.filterCallback(h,g)?d.toggleClass("ui-filter-hidequeue",true):j=true;f.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",
+false);f.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",true).toggleClass("ui-filter-hidequeue",false)}else f.toggleClass("ui-screen-hidden",false);b._refreshCorners()}).appendTo(d).textinput();b.options.inset&&d.addClass("ui-listview-filter-inset");d.bind("submit",function(){return false}).insertBefore(c)}})})(l);(function(a,c){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
+mini:false},_create:function(){var b=this,d=this.element,e=a.mobile.getInheritedTheme(d,"c"),g=this.options.theme||e,e=this.options.trackTheme||e,f=d[0].nodeName.toLowerCase(),j=f=="select"?"ui-slider-switch":"",h=d.attr("id"),l=a("[for='"+h+"']"),o=l.attr("id")||h+"-label",l=l.attr("id",o),m=function(){return f=="input"?parseFloat(d.val()):d[0].selectedIndex},v=f=="input"?parseFloat(d.attr("min")):0,n=f=="input"?parseFloat(d.attr("max")):d.find("option").length-1,r=t.parseFloat(d.attr("step")||1),
+p=this.options.inline||d.jqmData("inline")==true?" ui-slider-inline":"",s=this.options.mini||d.jqmData("mini")?" ui-slider-mini":"",u=k.createElement("a"),x=a(u),h=k.createElement("div"),w=a(h),D=d.jqmData("highlight")&&f!="select"?function(){var b=k.createElement("div");b.className="ui-slider-bg "+a.mobile.activeBtnClass+" ui-btn-corner-all";return a(b).prependTo(w)}():false;u.setAttribute("href","#");h.setAttribute("role","application");h.className=["ui-slider ",j," ui-btn-down-",e," ui-btn-corner-all",
+p,s].join("");u.className="ui-slider-handle";h.appendChild(u);x.buttonMarkup({corners:true,theme:g,shadow:true}).attr({role:"slider","aria-valuemin":v,"aria-valuemax":n,"aria-valuenow":m(),"aria-valuetext":m(),title:m(),"aria-labelledby":o});a.extend(this,{slider:w,handle:x,valuebg:D,dragging:false,beforeStart:null,userModified:false,mouseMoved:false});if(f=="select"){g=k.createElement("div");g.className="ui-slider-inneroffset";j=0;for(o=h.childNodes.length;j<o;j++)g.appendChild(h.childNodes[j]);
+h.appendChild(g);x.addClass("ui-slider-handle-snapping");g=d.find("option");h=0;for(j=g.length;h<j;h++)o=!h?"b":"a",p=!h?" ui-btn-down-"+e:" "+a.mobile.activeBtnClass,k.createElement("div"),s=k.createElement("span"),s.className=["ui-slider-label ui-slider-label-",o,p," ui-btn-corner-all"].join(""),s.setAttribute("role","img"),s.appendChild(k.createTextNode(g[h].innerHTML)),a(s).prependTo(w);b._labels=a(".ui-slider-label",w)}l.addClass("ui-slider");d.addClass(f==="input"?"ui-slider-input":"ui-slider-switch").change(function(){b.mouseMoved||
+b.refresh(m(),true)}).keyup(function(){b.refresh(m(),true,true)}).blur(function(){b.refresh(m(),true)});a(k).bind("vmousemove",function(a){if(b.dragging)return b.mouseMoved=true,f==="select"&&x.removeClass("ui-slider-handle-snapping"),b.refresh(a),b.userModified=b.beforeStart!==d[0].selectedIndex,false});w.bind("vmousedown",function(a){b.dragging=true;b.userModified=false;b.mouseMoved=false;if(f==="select")b.beforeStart=d[0].selectedIndex;b.refresh(a);return false}).bind("vclick",false);w.add(k).bind("vmouseup",
+function(){if(b.dragging)return b.dragging=false,f==="select"&&(x.addClass("ui-slider-handle-snapping"),b.mouseMoved?b.userModified?b.refresh(b.beforeStart==0?1:0):b.refresh(b.beforeStart):b.refresh(b.beforeStart==0?1:0)),b.mouseMoved=false});w.insertAfter(d);f=="select"&&this.handle.bind({focus:function(){w.addClass(a.mobile.focusClass)},blur:function(){w.removeClass(a.mobile.focusClass)}});this.handle.bind({vmousedown:function(){a(this).focus()},vclick:false,keydown:function(c){var d=m();if(!b.options.disabled){switch(c.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:if(c.preventDefault(),
+!b._keySliding)b._keySliding=true,a(this).addClass("ui-state-active")}switch(c.keyCode){case a.mobile.keyCode.HOME:b.refresh(v);break;case a.mobile.keyCode.END:b.refresh(n);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:b.refresh(d+r);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.refresh(d-r)}}},keyup:function(){if(b._keySliding)b._keySliding=false,a(this).removeClass("ui-state-active")}});this.refresh(c,c,true)},
+refresh:function(b,c,e){(this.options.disabled||this.element.attr("disabled"))&&this.disable();var g=this.element,f=g[0].nodeName.toLowerCase(),j=f==="input"?parseFloat(g.attr("min")):0,h=f==="input"?parseFloat(g.attr("max")):g.find("option").length-1,k=f==="input"&&parseFloat(g.attr("step"))>0?parseFloat(g.attr("step")):1;if(typeof b==="object"){if(!this.dragging||b.pageX<this.slider.offset().left-8||b.pageX>this.slider.offset().left+this.slider.width()+8)return;b=Math.round((b.pageX-this.slider.offset().left)/
+this.slider.width()*100)}else b==null&&(b=f==="input"?parseFloat(g.val()||0):g[0].selectedIndex),b=(parseFloat(b)-j)/(h-j)*100;if(!isNaN(b)){b<0&&(b=0);b>100&&(b=100);var l=b/100*(h-j)+j,m=(l-j)%k;l-=m;Math.abs(m)*2>=k&&(l+=m>0?k:-k);l=parseFloat(l.toFixed(5));l<j&&(l=j);l>h&&(l=h);this.handle.css("left",b+"%");this.handle.attr({"aria-valuenow":f==="input"?l:g.find("option").eq(l).attr("value"),"aria-valuetext":f==="input"?l:g.find("option").eq(l).getEncodedText(),title:f==="input"?l:g.find("option").eq(l).getEncodedText()});
+this.valuebg&&this.valuebg.css("width",b+"%");if(this._labels){var j=this.handle.width()/this.slider.width()*100,t=b&&j+(100-j)*b/100,n=b===100?0:Math.min(j+100-t,100);this._labels.each(function(){var b=a(this).is(".ui-slider-label-a");a(this).width((b?t:n)+"%")})}if(!e)e=false,f==="input"?(e=g.val()!==l,g.val(l)):(e=g[0].selectedIndex!==l,g[0].selectedIndex=l),!c&&e&&g.trigger("change")}},enable:function(){this.element.attr("disabled",false);this.slider.removeClass("ui-disabled").attr("aria-disabled",
+false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}});a(k).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target,true)})})(l);(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:false,corners:true,shadow:true,iconshadow:true,overlayTheme:"a",
+hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:true,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"select:not(:jqmData(role='slider'))",mini:false},_button:function(){return a("<div/>")},_setDisabled:function(a){this.element.attr("disabled",a);this.button.attr("aria-disabled",a);return this._setOption("disabled",a)},_focusButton:function(){var a=this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},
+_preExtension:function(){var c="";~this.element[0].className.indexOf("ui-btn-left")&&(c=" ui-btn-left");~this.element[0].className.indexOf("ui-btn-right")&&(c=" ui-btn-right");this.select=this.element.wrap("<div class='ui-select"+c+"'>");this.selectID=this.select.attr("id");this.label=a("label[for='"+this.selectID+"']").addClass("ui-select");this.isMultiple=this.select[0].multiple;if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.select,"c")},_create:function(){this._preExtension();
+this._trigger("beforeCreate");this.button=this._button();var c=this,b=this.options,d=b.inline||this.select.jqmData("inline"),e=b.mini||this.select.jqmData("mini"),g=b.icon?b.iconpos||this.select.jqmData("iconpos"):false,d=this.button.text(a(this.select[0].options.item(this.select[0].selectedIndex==-1?0:this.select[0].selectedIndex)).text()).insertBefore(this.select).buttonMarkup({theme:b.theme,icon:b.icon,iconpos:g,inline:d,corners:b.corners,shadow:b.shadow,iconshadow:b.iconshadow,mini:e});b.nativeMenu&&
+t.opera&&t.opera.version&&d.addClass("ui-select-nativeonly");if(this.isMultiple)this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(d.addClass("ui-li-has-count"));(b.disabled||this.element.attr("disabled"))&&this.disable();this.select.change(function(){c.refresh()});this.build()},build:function(){var c=this;this.select.appendTo(c.button).bind("vmousedown",function(){c.button.addClass(a.mobile.activeBtnClass)}).bind("focus",function(){c.button.addClass(a.mobile.focusClass)}).bind("blur",
+function(){c.button.removeClass(a.mobile.focusClass)}).bind("focus vmouseover",function(){c.button.trigger("vmouseover")}).bind("vmousemove",function(){c.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){c.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur",function(){c.button.removeClass("ui-btn-down-"+c.options.theme)});c.button.bind("vmousedown",function(){c.options.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("mouseup",
+function(){c.options.preventFocusZoom&&a.mobile.zoom.enable(true)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var c=this,b=this.selected();this.button.find(".ui-btn-text").text(function(){return!c.isMultiple?b.text():b.length?b.map(function(){return a(this).text()}).get().join(", "):c.placeholder})},setButtonCount:function(){var a=
+this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText();this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(true);this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(false);this.button.removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(c){a.mobile.selectmenu.prototype.enhanceWithin(c.target,true)})})(l);(function(a){var c=function(b){var c=b.selectID,
+e=b.label,g=b.select.closest(".ui-page"),f=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(g),j=b._selectOptions(),h=b.isMultiple=b.select[0].multiple,l=c+"-button",o=c+"-menu",m=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+b.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+b.options.overlayTheme+"'><div data-"+a.mobile.ns+"role='header'><div class='ui-title'>"+e.getEncodedText()+"</div></div><div data-"+a.mobile.ns+"role='content'></div></div>"),
+v=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-"+b.options.overlayTheme+" "+a.mobile.defaultDialogTransition}).insertAfter(f),n=a("<ul>",{"class":"ui-selectmenu-list",id:o,role:"listbox","aria-labelledby":l}).attr("data-"+a.mobile.ns+"theme",b.options.theme).appendTo(v),r=a("<div>",{"class":"ui-header ui-bar-"+b.options.theme}).prependTo(v),p=a("<h1>",{"class":"ui-title"}).appendTo(r),s;b.isMultiple&&(s=a("<a>",{text:b.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+
+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(r).buttonMarkup());a.extend(b,{select:b.select,selectID:c,buttonId:l,menuId:o,thisPage:g,menuPage:m,label:e,screen:f,selectOptions:j,isMultiple:h,theme:b.options.theme,listbox:v,list:n,header:r,headerTitle:p,headerClose:s,menuPageContent:void 0,menuPageClose:void 0,placeholder:"",build:function(){var c=this;c.refresh();c.select.attr("tabindex","-1").focus(function(){a(this).blur();c.button.focus()});c.button.bind("vclick keydown",
+function(b){if(b.type=="vclick"||b.keyCode&&(b.keyCode===a.mobile.keyCode.ENTER||b.keyCode===a.mobile.keyCode.SPACE))c.open(),b.preventDefault()});c.list.attr("role","listbox").bind("focusin",function(b){a(b.target).attr("tabindex","0").trigger("vmouseover")}).bind("focusout",function(b){a(b.target).attr("tabindex","-1").trigger("vmouseout")}).delegate("li:not(.ui-disabled, .ui-li-divider)","click",function(d){var e=c.select[0].selectedIndex,f=c.list.find("li:not(.ui-li-divider)").index(this),h=c._selectOptions().eq(f)[0];
+h.selected=c.isMultiple?!h.selected:true;c.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",h.selected).toggleClass("ui-icon-checkbox-off",!h.selected);(c.isMultiple||e!==f)&&c.select.trigger("change");c.isMultiple?c.list.find("li:not(.ui-li-divider)").eq(f).addClass("ui-btn-down-"+b.options.theme).find("a").first().focus():c.close();d.preventDefault()}).keydown(function(c){var d=a(c.target),e=d.closest("li");switch(c.keyCode){case 38:return c=e.prev().not(".ui-selectmenu-placeholder"),
+c.is(".ui-li-divider")&&(c=c.prev()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 40:return c=e.next(),c.is(".ui-li-divider")&&(c=c.next()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 13:case 32:return d.trigger("click"),false}});c.menuPage.bind("pagehide",function(){c.list.appendTo(c.listbox);c._focusButton();a.mobile._bindPageRemove.call(c.thisPage)});
+c.screen.bind("vclick",function(){c.close()});c.isMultiple&&c.headerClose.click(function(){if(c.menuType=="overlay")return c.close(),false});c.thisPage.addDependents(this.menuPage)},_isRebuildRequired:function(){var a=this.list.find("li");return this._selectOptions().text()!==a.text()},selected:function(){return this._selectOptions().filter(":selected:not(:jqmData(placeholder='true'))")},refresh:function(b){var c=this,d;(b||this._isRebuildRequired())&&c._buildList();d=this.selectedIndices();c.setButtonText();
+c.setButtonCount();c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",false).each(function(b){a.inArray(b,d)>-1&&(b=a(this),b.attr("aria-selected",true),c.isMultiple?b.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):b.is(".ui-selectmenu-placeholder")?b.next().addClass(a.mobile.activeBtnClass):b.addClass(a.mobile.activeBtnClass))})},close:function(){if(!this.options.disabled&&this.isOpen)this.menuType=="page"?t.history.back():
+(this.screen.addClass("ui-screen-hidden"),this.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in"),this.list.appendTo(this.listbox),this._focusButton()),this.isOpen=false},open:function(){function c(){var e=d.list.find("."+a.mobile.activeBtnClass+" a");e.length===0&&(e=d.list.find("li.ui-btn:not(:jqmData(placeholder='true')) a"));e.first().focus().closest("li").addClass("ui-btn-down-"+b.options.theme)}if(!this.options.disabled){var d=this,e=a(t),f=d.list.parent(),h=f.outerHeight(),
+f=f.outerWidth();a(".ui-page-active");var g=e.scrollTop(),j=d.button.offset().top,l=e.height(),e=e.width();d.button.addClass(a.mobile.activeBtnClass);setTimeout(function(){d.button.removeClass(a.mobile.activeBtnClass)},300);if(h>l-80||!a.support.scrollTop){d.menuPage.appendTo(a.mobile.pageContainer).page();d.menuPageContent=m.find(".ui-content");d.menuPageClose=m.find(".ui-header a");d.thisPage.unbind("pagehide.remove");if(g==0&&j>l)d.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",
+j)});d.menuPage.one("pageshow",function(){c();d.isOpen=true});d.menuType="page";d.menuPageContent.append(d.list);d.menuPage.find("div .ui-title").text(d.label.text());a.mobile.changePage(d.menuPage,{transition:a.mobile.defaultDialogTransition})}else{d.menuType="overlay";d.screen.height(a(k).height()).removeClass("ui-screen-hidden");var n=j-g,o=g+l-j,q=h/2,p=parseFloat(d.list.parent().css("max-width")),h=n>h/2&&o>h/2?j+d.button.outerHeight()/2-q:n>o?g+l-h-30:g+30;f<p?g=(e-f)/2:(g=d.button.offset().left+
+d.button.outerWidth()/2-f/2,g<30?g=30:g+f>e&&(g=e-f-30));d.listbox.append(d.list).removeClass("ui-selectmenu-hidden").css({top:h,left:g}).addClass("in");c();d.isOpen=true}}},_buildList:function(){var b=this.options,c=this.placeholder,d=true,e=this.isMultiple?"checkbox-off":"false";this.list.empty().filter(".ui-listview").listview("destroy");var f=this.select.find("option"),h=f.length,g=this.select[0],j="data-"+a.mobile.ns,l=j+"option-index",m=j+"icon",n=j+"role";j+="placeholder";for(var o=k.createDocumentFragment(),
+q=false,p,t=0;t<h;t++,q=false){var s=f[t],r=a(s),v=s.parentNode,E=r.text(),I=k.createElement("a"),L=[];I.setAttribute("href","#");I.appendChild(k.createTextNode(E));v!==g&&v.nodeName.toLowerCase()==="optgroup"&&(v=v.getAttribute("label"),v!=p&&(p=k.createElement("li"),p.setAttribute(n,"list-divider"),p.setAttribute("role","option"),p.setAttribute("tabindex","-1"),p.appendChild(k.createTextNode(v)),o.appendChild(p),p=v));if(d&&(!s.getAttribute("value")||E.length==0||r.jqmData("placeholder")))if(d=
+false,q=true,s.setAttribute(j,true),b.hidePlaceholderMenuItems&&L.push("ui-selectmenu-placeholder"),!c)c=this.placeholder=E;r=k.createElement("li");s.disabled&&(L.push("ui-disabled"),r.setAttribute("aria-disabled",true));r.setAttribute(l,t);r.setAttribute(m,e);q&&r.setAttribute(j,true);r.className=L.join(" ");r.setAttribute("role","option");I.setAttribute("tabindex","-1");r.appendChild(I);o.appendChild(r)}this.list[0].appendChild(o);!this.isMultiple&&!c.length?this.header.hide():this.headerTitle.text(this.placeholder);
+this.list.listview()},_button:function(){return a("<a>",{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a(k).bind("selectmenubeforecreate",function(b){b=a(b.target).data("selectmenu");b.options.nativeMenu||c(b)})})(l);(function(a){a.widget("mobile.fixedtoolbar",a.mobile.widget,{options:{visibleOnPageShow:true,disablePageZoom:true,transition:"slide",fullscreen:false,tapToggle:true,tapToggleBlacklist:"a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed",
+hideDuringFocus:"input, textarea, select",updatePagePadding:true,trackPersistentToolbars:true,supportBlacklist:function(){var a=navigator.userAgent,b=navigator.platform,d=a.match(/AppleWebKit\/([0-9]+)/),d=!!d&&d[1],e=a.match(/Fennec\/([0-9]+)/),e=!!e&&e[1],g=a.match(/Opera Mobi\/([0-9]+)/),f=!!g&&g[1];return(b.indexOf("iPhone")>-1||b.indexOf("iPad")>-1||b.indexOf("iPod")>-1)&&d&&d<534||t.operamini&&{}.toString.call(t.operamini)==="[object OperaMini]"||g&&f<7458||a.indexOf("Android")>-1&&d&&d<533||
+e&&e<6||"palmGetResource"in t&&d&&d<534||a.indexOf("MeeGo")>-1&&a.indexOf("NokiaBrowser/8.5.0")>-1?true:false},initSelector:":jqmData(position='fixed')"},_create:function(){var a=this.options,b=this.element,d=b.is(":jqmData(role='header')")?"header":"footer",e=b.closest(".ui-page");a.supportBlacklist()?this.destroy():(b.addClass("ui-"+d+"-fixed"),a.fullscreen?(b.addClass("ui-"+d+"-fullscreen"),e.addClass("ui-page-"+d+"-fullscreen")):e.addClass("ui-page-"+d+"-fixed"),this._addTransitionClass(),this._bindPageEvents(),
+this._bindToggleHandlers())},_addTransitionClass:function(){var a=this.options.transition;a&&a!=="none"&&(a==="slide"&&(a=this.element.is(".ui-header")?"slidedown":"slideup"),this.element.addClass(a))},_bindPageEvents:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("pagebeforeshow",function(){b.disablePageZoom&&a.mobile.zoom.disable(true);b.visibleOnPageShow||c.hide(true)}).bind("webkitAnimationStart animationstart updatelayout",function(){b.updatePagePadding&&c.updatePagePadding(this)}).bind("pageshow",
+function(){var d=this;c.updatePagePadding(d);b.updatePagePadding&&a(t).bind("throttledresize."+c.widgetName,function(){c.updatePagePadding(d)})}).bind("pagebeforehide",function(d,e){b.disablePageZoom&&a.mobile.zoom.enable(true);b.updatePagePadding&&a(t).unbind("throttledresize."+c.widgetName);if(b.trackPersistentToolbars){var g=a(".ui-footer-fixed:jqmData(id)",this),f=a(".ui-header-fixed:jqmData(id)",this),j=g.length&&e.nextPage&&a(".ui-footer-fixed:jqmData(id='"+g.jqmData("id")+"')",e.nextPage),
+h=f.length&&e.nextPage&&a(".ui-header-fixed:jqmData(id='"+f.jqmData("id")+"')",e.nextPage),j=j||a();if(j.length||h.length)j.add(h).appendTo(a.mobile.pageContainer),e.nextPage.one("pageshow",function(){j.add(h).appendTo(this)})}})},_visible:true,updatePagePadding:function(c){var b=this.element,d=b.is(".ui-header");this.options.fullscreen||(c=c||b.closest(".ui-page"),a(c).css("padding-"+(d?"top":"bottom"),b.outerHeight()))},_useTransition:function(c){var b=this.element,d=a(t).scrollTop(),e=b.height(),
+g=b.closest(".ui-page").height(),f=a.mobile.getScreenHeight(),b=b.is(":jqmData(role='header')")?"header":"footer";return!c&&(this.options.transition&&this.options.transition!=="none"&&(b==="header"&&!this.options.fullscreen&&d>e||b==="footer"&&!this.options.fullscreen&&d+f<g-e)||this.options.fullscreen)},show:function(a){var b=this.element;this._useTransition(a)?b.removeClass("out ui-fixed-hidden").addClass("in"):b.removeClass("ui-fixed-hidden");this._visible=true},hide:function(a){var b=this.element,
+d="out"+(this.options.transition==="slide"?" reverse":"");this._useTransition(a)?b.addClass(d).removeClass("in").animationComplete(function(){b.addClass("ui-fixed-hidden").removeClass(d)}):b.addClass("ui-fixed-hidden").removeClass(d);this._visible=false},toggle:function(){this[this._visible?"hide":"show"]()},_bindToggleHandlers:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("vclick",function(d){b.tapToggle&&!a(d.target).closest(b.tapToggleBlacklist).length&&c.toggle()}).bind("focusin focusout",
+function(d){if(screen.width<500&&a(d.target).is(b.hideDuringFocus)&&!a(d.target).closest(".ui-header-fixed, .ui-footer-fixed").length)c[d.type==="focusin"&&c._visible?"hide":"show"]()})},destroy:function(){this.element.removeClass("ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden");this.element.closest(".ui-page").removeClass("ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen")}});a(k).bind("pagecreate create",
+function(c){a(c.target).jqmData("fullscreen")&&a(a.mobile.fixedtoolbar.prototype.options.initSelector,c.target).not(":jqmData(fullscreen)").jqmData("fullscreen",true);a.mobile.fixedtoolbar.prototype.enhanceWithin(c.target)})})(l);(function(a,c){if(/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1){var b=a.mobile.zoom,d,e,g,f,j;a(c).bind("orientationchange.iosorientationfix",b.enable).bind("devicemotion.iosorientationfix",function(a){d=a.originalEvent;j=d.accelerationIncludingGravity;
+e=Math.abs(j.x);g=Math.abs(j.y);f=Math.abs(j.z);!c.orientation&&(e>7||(f>6&&g<8||f<8&&g>6)&&e>5)?b.enabled&&b.disable():b.enabled||b.enable()})}})(l,this);(function(a,c){function b(){var b=a("."+a.mobile.activeBtnClass).first();j.css({top:a.support.scrollTop&&f.scrollTop()+f.height()/2||b.length&&b.offset().top||100})}function d(){var c=j.offset(),e=f.scrollTop(),g=a.mobile.getScreenHeight();if(c.top<e||c.top-e>g)j.addClass("ui-loader-fakefix"),b(),f.unbind("scroll",d).bind("scroll",b)}function e(){g.removeClass("ui-mobile-rendering")}
+var g=a("html");a("head");var f=a(c);a(c.document).trigger("mobileinit");if(a.mobile.gradeA()){if(a.mobile.ajaxBlacklist)a.mobile.ajaxEnabled=false;g.addClass("ui-mobile ui-mobile-rendering");setTimeout(e,5E3);var j=a("<div class='ui-loader'><span class='ui-icon ui-icon-loading'></span><h1></h1></div>");a.extend(a.mobile,{showPageLoadingMsg:function(b,c,e){g.addClass("ui-loading");if(a.mobile.loadingMessage){var k=e||a.mobile.loadingMessageTextVisible;b=b||a.mobile.loadingMessageTheme;j.attr("class",
+"ui-loader ui-corner-all ui-body-"+(b||"a")+" ui-loader-"+(k?"verbose":"default")+(e?" ui-loader-textonly":"")).find("h1").text(c||a.mobile.loadingMessage).end().appendTo(a.mobile.pageContainer);d();f.bind("scroll",d)}},hidePageLoadingMsg:function(){g.removeClass("ui-loading");a.mobile.loadingMessage&&j.removeClass("ui-loader-fakefix");a(c).unbind("scroll",b);a(c).unbind("scroll",d)},initializePage:function(){var b=a(":jqmData(role='page'), :jqmData(role='dialog')");b.length||(b=a("body").wrapInner("<div data-"+
+a.mobile.ns+"role='page'></div>").children(0));b.each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)});a.mobile.firstPage=b.first();a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport");f.trigger("pagecontainercreate");a.mobile.showPageLoadingMsg();e();!a.mobile.hashListeningEnabled||!a.mobile.path.isHashValid(location.hash)||!a(location.hash+':jqmData(role="page")').length&&!a.mobile.path.isPath(location.hash)?
+a.mobile.changePage(a.mobile.firstPage,{transition:"none",reverse:true,changeHash:false,fromHashChange:true}):f.trigger("hashchange",[true])}});a.mobile.navreadyDeferred.resolve();a(function(){c.scrollTo(0,1);a.mobile.defaultHomeScroll=!a.support.scrollTop||a(c).scrollTop()===1?0:1;a.fn.controlgroup&&a(k).bind("pagecreate create",function(b){a(":jqmData(role='controlgroup')",b.target).jqmEnhanceable().controlgroup({excludeInvisible:false})});a.mobile.autoInitializePage&&a.mobile.initializePage();
+f.load(a.mobile.silentScroll);a.support.cssPointerEvents||a(k).delegate(".ui-disabled","vclick",function(a){a.preventDefault();a.stopImmediatePropagation()})})}})(l,this)});
diff --git a/module/web/static/js/libs/jquery.omniwindow.js b/module/web/static/js/libs/jquery.omniwindow.js
new file mode 100644
index 000000000..e1f0b8f77
--- /dev/null
+++ b/module/web/static/js/libs/jquery.omniwindow.js
@@ -0,0 +1,141 @@
+// jQuery OmniWindow plugin
+// @version: 0.7.0
+// @author: Rudenka Alexander (mur.mailbox@gmail.com)
+// @license: MIT
+
+;(function($) {
+ "use strict";
+ $.fn.extend({
+ omniWindow: function(options) {
+
+ options = $.extend(true, {
+ animationsPriority: {
+ show: ['overlay', 'modal'],
+ hide: ['modal', 'overlay']
+ },
+ overlay: {
+ selector: '.ow-overlay',
+ hideClass: 'ow-closed',
+ animations: {
+ show: function(subjects, internalCallback) { return internalCallback(subjects); },
+ hide: function(subjects, internalCallback) { return internalCallback(subjects); },
+ internal: {
+ show: function(subjects){ subjects.overlay.removeClass(options.overlay.hideClass); },
+ hide: function(subjects){ subjects.overlay.addClass(options.overlay.hideClass); }
+ }
+ }
+ },
+ modal: {
+ hideClass: 'ow-closed',
+ animations: {
+ show: function(subjects, internalCallback) { return internalCallback(subjects); },
+ hide: function(subjects, internalCallback) { return internalCallback(subjects); },
+ internal: {
+ show: function(subjects){ subjects.modal.removeClass(options.modal.hideClass); },
+ hide: function(subjects){ subjects.modal.addClass(options.modal.hideClass); }
+ }
+ },
+ internal: {
+ stateAttribute: 'ow-active'
+ }
+ },
+ eventsNames: {
+ show: 'show.ow',
+ hide: 'hide.ow',
+ internal: {
+ overlayClick: 'click.ow',
+ keyboardKeyUp: 'keyup.ow'
+ }
+ },
+ callbacks: { // Callbacks execution chain
+ beforeShow: function(subjects, internalCallback) { return internalCallback(subjects); }, // 1 (stop if retruns false)
+ positioning: function(subjects, internalCallback) { return internalCallback(subjects); }, // 2
+ afterShow: function(subjects, internalCallback) { return internalCallback(subjects); }, // 3
+ beforeHide: function(subjects, internalCallback) { return internalCallback(subjects); }, // 4 (stop if retruns false)
+ afterHide: function(subjects, internalCallback) { return internalCallback(subjects); }, // 5
+ internal: {
+ beforeShow: function(subjects) {
+ if (subjects.modal.data(options.modal.internal.stateAttribute)) {
+ return false;
+ } else {
+ subjects.modal.data(options.modal.internal.stateAttribute, true);
+ return true;
+ }
+ },
+ afterShow: function(subjects) {
+ $(document).on(options.eventsNames.internal.keyboardKeyUp, function(e) {
+ if (e.keyCode === 27) { // if the key pressed is the ESC key
+ subjects.modal.trigger(options.eventsNames.hide);
+ }
+ });
+
+ subjects.overlay.on(options.eventsNames.internal.overlayClick, function(){
+ subjects.modal.trigger(options.eventsNames.hide);
+ });
+ },
+ positioning: function(subjects) {
+ subjects.modal.css('margin-left', Math.round(subjects.modal.outerWidth() / -2));
+ },
+ beforeHide: function(subjects) {
+ if (subjects.modal.data(options.modal.internal.stateAttribute)) {
+ subjects.modal.data(options.modal.internal.stateAttribute, false);
+ return true;
+ } else {
+ return false;
+ }
+ },
+ afterHide: function(subjects) {
+ subjects.overlay.off(options.eventsNames.internal.overlayClick);
+ $(document).off(options.eventsNames.internal.keyboardKeyUp);
+
+ subjects.overlay.css('display', ''); // clear inline styles after jQ animations
+ subjects.modal.css('display', '');
+ }
+ }
+ }
+ }, options);
+
+ var animate = function(process, subjects, callbackName) {
+ var first = options.animationsPriority[process][0],
+ second = options.animationsPriority[process][1];
+
+ options[first].animations[process](subjects, function(subjs) { // call USER's FIRST animation (depends on priority)
+ options[first].animations.internal[process](subjs); // call internal FIRST animation
+
+ options[second].animations[process](subjects, function(subjs) { // call USER's SECOND animation
+ options[second].animations.internal[process](subjs); // call internal SECOND animation
+
+ // then we need to call USER's
+ // afterShow of afterHide callback
+ options.callbacks[callbackName](subjects, options.callbacks.internal[callbackName]);
+ });
+ });
+ };
+
+ var showModal = function(subjects) {
+ if (!options.callbacks.beforeShow(subjects, options.callbacks.internal.beforeShow)) { return; } // cancel showing if beforeShow callback return false
+
+ options.callbacks.positioning(subjects, options.callbacks.internal.positioning);
+
+ animate('show', subjects, 'afterShow');
+ };
+
+ var hideModal = function(subjects) {
+ if (!options.callbacks.beforeHide(subjects, options.callbacks.internal.beforeHide)) { return; } // cancel hiding if beforeHide callback return false
+
+ animate('hide', subjects, 'afterHide');
+ };
+
+
+ var $overlay = $(options.overlay.selector);
+
+ return this.each(function() {
+ var $modal = $(this);
+ var subjects = {modal: $modal, overlay: $overlay};
+
+ $modal.bind(options.eventsNames.show, function(){ showModal(subjects); })
+ .bind(options.eventsNames.hide, function(){ hideModal(subjects); });
+ });
+ }
+ });
+})(jQuery); \ No newline at end of file
diff --git a/module/web/static/js/libs/jquery.transit-0.1.3.js b/module/web/static/js/libs/jquery.transit-0.1.3.js
new file mode 100644
index 000000000..2314f2ca2
--- /dev/null
+++ b/module/web/static/js/libs/jquery.transit-0.1.3.js
@@ -0,0 +1,658 @@
+/*!
+ * jQuery Transit - CSS3 transitions and transformations
+ * Copyright(c) 2011 Rico Sta. Cruz <rico@ricostacruz.com>
+ * MIT Licensed.
+ *
+ * http://ricostacruz.com/jquery.transit
+ * http://github.com/rstacruz/jquery.transit
+ */
+
+(function($) {
+ "use strict";
+
+ $.transit = {
+ version: "0.1.3",
+
+ // Map of $.css() keys to values for 'transitionProperty'.
+ // See https://developer.mozilla.org/en/CSS/CSS_transitions#Properties_that_can_be_animated
+ propertyMap: {
+ marginLeft : 'margin',
+ marginRight : 'margin',
+ marginBottom : 'margin',
+ marginTop : 'margin',
+ paddingLeft : 'padding',
+ paddingRight : 'padding',
+ paddingBottom : 'padding',
+ paddingTop : 'padding'
+ },
+
+ // Will simply transition "instantly" if false
+ enabled: true,
+
+ // Set this to false if you don't want to use the transition end property.
+ useTransitionEnd: false
+ };
+
+ var div = document.createElement('div');
+ var support = {};
+
+ // Helper function to get the proper vendor property name.
+ // (`transition` => `WebkitTransition`)
+ function getVendorPropertyName(prop) {
+ var prefixes = ['Moz', 'Webkit', 'O', 'ms'];
+ var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1);
+
+ if (prop in div.style) { return prop; }
+
+ for (var i=0; i<prefixes.length; ++i) {
+ var vendorProp = prefixes[i] + prop_;
+ if (vendorProp in div.style) { return vendorProp; }
+ }
+ }
+
+ // Helper function to check if transform3D is supported.
+ // Should return true for Webkits and Firefox 10+.
+ function checkTransform3dSupport() {
+ div.style[support.transform] = '';
+ div.style[support.transform] = 'rotateY(90deg)';
+ return div.style[support.transform] !== '';
+ }
+
+ var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
+
+ // Check for the browser's transitions support.
+ // You can access this in jQuery's `$.support.transition`.
+ // As per [jQuery's cssHooks documentation](http://api.jquery.com/jQuery.cssHooks/),
+ // we set $.support.transition to a string of the actual property name used.
+ support.transition = getVendorPropertyName('transition');
+ support.transitionDelay = getVendorPropertyName('transitionDelay');
+ support.transform = getVendorPropertyName('transform');
+ support.transformOrigin = getVendorPropertyName('transformOrigin');
+ support.transform3d = checkTransform3dSupport();
+
+ $.extend($.support, support);
+
+ var eventNames = {
+ 'MozTransition': 'transitionend',
+ 'OTransition': 'oTransitionEnd',
+ 'WebkitTransition': 'webkitTransitionEnd',
+ 'msTransition': 'MSTransitionEnd'
+ };
+
+ // Detect the 'transitionend' event needed.
+ var transitionEnd = support.transitionEnd = eventNames[support.transition] || null;
+
+ // Avoid memory leak in IE.
+ div = null;
+
+ // ## $.cssEase
+ // List of easing aliases that you can use with `$.fn.transition`.
+ $.cssEase = {
+ '_default': 'ease',
+ 'in': 'ease-in',
+ 'out': 'ease-out',
+ 'in-out': 'ease-in-out',
+ 'snap': 'cubic-bezier(0,1,.5,1)'
+ };
+
+ // ## 'transform' CSS hook
+ // Allows you to use the `transform` property in CSS.
+ //
+ // $("#hello").css({ transform: "rotate(90deg)" });
+ //
+ // $("#hello").css('transform');
+ // //=> { rotate: '90deg' }
+ //
+ $.cssHooks.transform = {
+ // The getter returns a `Transform` object.
+ get: function(elem) {
+ return $(elem).data('transform');
+ },
+
+ // The setter accepts a `Transform` object or a string.
+ set: function(elem, v) {
+ var value = v;
+
+ if (!(value instanceof Transform)) {
+ value = new Transform(value);
+ }
+
+ // We've seen the 3D version of Scale() not work in Chrome when the
+ // element being scaled extends outside of the viewport. Thus, we're
+ // forcing Chrome to not use the 3d transforms as well. Not sure if
+ // translate is affectede, but not risking it. Detection code from
+ // http://davidwalsh.name/detecting-google-chrome-javascript
+ if (support.transform === 'WebkitTransform' && !isChrome) {
+ elem.style[support.transform] = value.toString(true);
+ } else {
+ elem.style[support.transform] = value.toString();
+ }
+
+ $(elem).data('transform', value);
+ }
+ };
+
+ // ## 'transformOrigin' CSS hook
+ // Allows the use for `transformOrigin` to define where scaling and rotation
+ // is pivoted.
+ //
+ // $("#hello").css({ transformOrigin: '0 0' });
+ //
+ $.cssHooks.transformOrigin = {
+ get: function(elem) {
+ return elem.style[support.transformOrigin];
+ },
+ set: function(elem, value) {
+ elem.style[support.transformOrigin] = value;
+ }
+ };
+
+ // ## 'transition' CSS hook
+ // Allows you to use the `transition` property in CSS.
+ //
+ // $("#hello").css({ transition: 'all 0 ease 0' });
+ //
+ $.cssHooks.transition = {
+ get: function(elem) {
+ return elem.style[support.transition];
+ },
+ set: function(elem, value) {
+ elem.style[support.transition] = value;
+ }
+ };
+
+ // ## Other CSS hooks
+ // Allows you to rotate, scale and translate.
+ registerCssHook('scale');
+ registerCssHook('translate');
+ registerCssHook('rotate');
+ registerCssHook('rotateX');
+ registerCssHook('rotateY');
+ registerCssHook('rotate3d');
+ registerCssHook('perspective');
+ registerCssHook('skewX');
+ registerCssHook('skewY');
+ registerCssHook('x', true);
+ registerCssHook('y', true);
+
+ // ## Transform class
+ // This is the main class of a transformation property that powers
+ // `$.fn.css({ transform: '...' })`.
+ //
+ // This is, in essence, a dictionary object with key/values as `-transform`
+ // properties.
+ //
+ // var t = new Transform("rotate(90) scale(4)");
+ //
+ // t.rotate //=> "90deg"
+ // t.scale //=> "4,4"
+ //
+ // Setters are accounted for.
+ //
+ // t.set('rotate', 4)
+ // t.rotate //=> "4deg"
+ //
+ // Convert it to a CSS string using the `toString()` and `toString(true)` (for WebKit)
+ // functions.
+ //
+ // t.toString() //=> "rotate(90deg) scale(4,4)"
+ // t.toString(true) //=> "rotate(90deg) scale3d(4,4,0)" (WebKit version)
+ //
+ function Transform(str) {
+ if (typeof str === 'string') { this.parse(str); }
+ return this;
+ }
+
+ Transform.prototype = {
+ // ### setFromString()
+ // Sets a property from a string.
+ //
+ // t.setFromString('scale', '2,4');
+ // // Same as set('scale', '2', '4');
+ //
+ setFromString: function(prop, val) {
+ var args =
+ (typeof val === 'string') ? val.split(',') :
+ (val.constructor === Array) ? val :
+ [ val ];
+
+ args.unshift(prop);
+
+ Transform.prototype.set.apply(this, args);
+ },
+
+ // ### set()
+ // Sets a property.
+ //
+ // t.set('scale', 2, 4);
+ //
+ set: function(prop) {
+ var args = Array.prototype.slice.apply(arguments, [1]);
+ if (this.setter[prop]) {
+ this.setter[prop].apply(this, args);
+ } else {
+ this[prop] = args.join(',');
+ }
+ },
+
+ get: function(prop) {
+ if (this.getter[prop]) {
+ return this.getter[prop].apply(this);
+ } else {
+ return this[prop] || 0;
+ }
+ },
+
+ setter: {
+ // ### rotate
+ //
+ // .css({ rotate: 30 })
+ // .css({ rotate: "30" })
+ // .css({ rotate: "30deg" })
+ // .css({ rotate: "30deg" })
+ //
+ rotate: function(theta) {
+ this.rotate = unit(theta, 'deg');
+ },
+
+ rotateX: function(theta) {
+ this.rotateX = unit(theta, 'deg');
+ },
+
+ rotateY: function(theta) {
+ this.rotateY = unit(theta, 'deg');
+ },
+
+ // ### scale
+ //
+ // .css({ scale: 9 }) //=> "scale(9,9)"
+ // .css({ scale: '3,2' }) //=> "scale(3,2)"
+ //
+ scale: function(x, y) {
+ if (y === undefined) { y = x; }
+ this.scale = x + "," + y;
+ },
+
+ // ### skewX + skewY
+ skewX: function(x) {
+ this.skewX = unit(x, 'deg');
+ },
+
+ skewY: function(y) {
+ this.skewY = unit(y, 'deg');
+ },
+
+ // ### perspectvie
+ perspective: function(dist) {
+ this.perspective = unit(dist, 'px');
+ },
+
+ // ### x / y
+ // Translations. Notice how this keeps the other value.
+ //
+ // .css({ x: 4 }) //=> "translate(4px, 0)"
+ // .css({ y: 10 }) //=> "translate(4px, 10px)"
+ //
+ x: function(x) {
+ this.set('translate', x, null);
+ },
+
+ y: function(y) {
+ this.set('translate', null, y);
+ },
+
+ // ### translate
+ // Notice how this keeps the other value.
+ //
+ // .css({ translate: '2, 5' }) //=> "translate(2px, 5px)"
+ //
+ translate: function(x, y) {
+ if (this._translateX === undefined) { this._translateX = 0; }
+ if (this._translateY === undefined) { this._translateY = 0; }
+
+ if (x !== null) { this._translateX = unit(x, 'px'); }
+ if (y !== null) { this._translateY = unit(y, 'px'); }
+
+ this.translate = this._translateX + "," + this._translateY;
+ }
+ },
+
+ getter: {
+ x: function() {
+ return this._translateX || 0;
+ },
+
+ y: function() {
+ return this._translateY || 0;
+ },
+
+ scale: function() {
+ var s = (this.scale || "1,1").split(',');
+ if (s[0]) { s[0] = parseFloat(s[0]); }
+ if (s[1]) { s[1] = parseFloat(s[1]); }
+
+ // "2.5,2.5" => 2.5
+ // "2.5,1" => [2.5,1]
+ return (s[0] === s[1]) ? s[0] : s;
+ },
+
+ rotate3d: function() {
+ var s = (this.rotate3d || "0,0,0,0deg").split(',');
+ for (var i=0; i<=3; ++i) {
+ if (s[i]) { s[i] = parseFloat(s[i]); }
+ }
+ if (s[3]) { s[3] = unit(s[3], 'deg'); }
+
+ return s;
+ }
+ },
+
+ // ### parse()
+ // Parses from a string. Called on constructor.
+ parse: function(str) {
+ var self = this;
+ str.replace(/([a-zA-Z0-9]+)\((.*?)\)/g, function(x, prop, val) {
+ self.setFromString(prop, val);
+ });
+ },
+
+ // ### toString()
+ // Converts to a `transition` CSS property string. If `use3d` is given,
+ // it converts to a `-webkit-transition` CSS property string instead.
+ toString: function(use3d) {
+ var re = [];
+
+ for (var i in this) {
+ if (this.hasOwnProperty(i)) {
+ // Don't use 3D transformations if the browser can't support it.
+ if ((!support.transform3d) && (
+ (i === 'rotateX') ||
+ (i === 'rotateY') ||
+ (i === 'perspective') ||
+ (i === 'transformOrigin'))) { continue; }
+
+ if (i[0] !== '_') {
+ if (use3d && (i === 'scale')) {
+ re.push(i + "3d(" + this[i] + ",1)");
+ } else if (use3d && (i === 'translate')) {
+ re.push(i + "3d(" + this[i] + ",0)");
+ } else {
+ re.push(i + "(" + this[i] + ")");
+ }
+ }
+ }
+ }
+
+ return re.join(" ");
+ }
+ };
+
+ function callOrQueue(self, queue, fn) {
+ if (queue === true) {
+ self.queue(fn);
+ } else if (queue) {
+ self.queue(queue, fn);
+ } else {
+ fn();
+ }
+ }
+
+ // ### getProperties(dict)
+ // Returns properties (for `transition-property`) for dictionary `props`. The
+ // value of `props` is what you would expect in `$.css(...)`.
+ function getProperties(props) {
+ var re = [];
+
+ $.each(props, function(key) {
+ key = $.camelCase(key); // Convert "text-align" => "textAlign"
+ key = $.transit.propertyMap[key] || key;
+ key = uncamel(key); // Convert back to dasherized
+
+ if ($.inArray(key, re) === -1) { re.push(key); }
+ });
+
+ return re;
+ }
+
+ // ### getTransition()
+ // Returns the transition string to be used for the `transition` CSS property.
+ //
+ // Example:
+ //
+ // getTransition({ opacity: 1, rotate: 30 }, 500, 'ease');
+ // //=> 'opacity 500ms ease, -webkit-transform 500ms ease'
+ //
+ function getTransition(properties, duration, easing, delay) {
+ // Get the CSS properties needed.
+ var props = getProperties(properties);
+
+ // Account for aliases (`in` => `ease-in`).
+ if ($.cssEase[easing]) { easing = $.cssEase[easing]; }
+
+ // Build the duration/easing/delay attributes for it.
+ var attribs = '' + toMS(duration) + ' ' + easing;
+ if (parseInt(delay, 10) > 0) { attribs += ' ' + toMS(delay); }
+
+ // For more properties, add them this way:
+ // "margin 200ms ease, padding 200ms ease, ..."
+ var transitions = [];
+ $.each(props, function(i, name) {
+ transitions.push(name + ' ' + attribs);
+ });
+
+ return transitions.join(', ');
+ }
+
+ // ## $.fn.transition
+ // Works like $.fn.animate(), but uses CSS transitions.
+ //
+ // $("...").transition({ opacity: 0.1, scale: 0.3 });
+ //
+ // // Specific duration
+ // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500);
+ //
+ // // With duration and easing
+ // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in');
+ //
+ // // With callback
+ // $("...").transition({ opacity: 0.1, scale: 0.3 }, function() { ... });
+ //
+ // // With everything
+ // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in', function() { ... });
+ //
+ // // Alternate syntax
+ // $("...").transition({
+ // opacity: 0.1,
+ // duration: 200,
+ // delay: 40,
+ // easing: 'in',
+ // complete: function() { /* ... */ }
+ // });
+ //
+ $.fn.transition = $.fn.transit = function(properties, duration, easing, callback) {
+ var self = this;
+ var delay = 0;
+ var queue = true;
+
+ // Account for `.transition(properties, callback)`.
+ if (typeof duration === 'function') {
+ callback = duration;
+ duration = undefined;
+ }
+
+ // Account for `.transition(properties, duration, callback)`.
+ if (typeof easing === 'function') {
+ callback = easing;
+ easing = undefined;
+ }
+
+ // Alternate syntax.
+ if (typeof properties.easing !== 'undefined') {
+ easing = properties.easing;
+ delete properties.easing;
+ }
+
+ if (typeof properties.duration !== 'undefined') {
+ duration = properties.duration;
+ delete properties.duration;
+ }
+
+ if (typeof properties.complete !== 'undefined') {
+ callback = properties.complete;
+ delete properties.complete;
+ }
+
+ if (typeof properties.queue !== 'undefined') {
+ queue = properties.queue;
+ delete properties.queue;
+ }
+
+ if (typeof properties.delay !== 'undefined') {
+ delay = properties.delay;
+ delete properties.delay;
+ }
+
+ // Set defaults. (`400` duration, `ease` easing)
+ if (typeof duration === 'undefined') { duration = $.fx.speeds._default; }
+ if (typeof easing === 'undefined') { easing = $.cssEase._default; }
+
+ duration = toMS(duration);
+
+ // Build the `transition` property.
+ var transitionValue = getTransition(properties, duration, easing, delay);
+
+ // Compute delay until callback.
+ // If this becomes 0, don't bother setting the transition property.
+ var work = $.transit.enabled && support.transition;
+ var i = work ? (parseInt(duration, 10) + parseInt(delay, 10)) : 0;
+
+ // If there's nothing to do...
+ if (i === 0) {
+ var fn = function(next) {
+ self.css(properties);
+ if (callback) { callback.apply(self); }
+ if (next) { next(); }
+ };
+
+ callOrQueue(self, queue, fn);
+ return self;
+ }
+
+ // Save the old transitions of each element so we can restore it later.
+ var oldTransitions = {};
+
+ var run = function(nextCall) {
+ var bound = false;
+
+ // Prepare the callback.
+ var cb = function() {
+ if (bound) { self.unbind(transitionEnd, cb); }
+
+ if (i > 0) {
+ self.each(function() {
+ this.style[support.transition] = (oldTransitions[this] || null);
+ });
+ }
+
+ if (typeof callback === 'function') { callback.apply(self); }
+ if (typeof nextCall === 'function') { nextCall(); }
+ };
+
+ if ((i > 0) && (transitionEnd) && ($.transit.useTransitionEnd)) {
+ // Use the 'transitionend' event if it's available.
+ bound = true;
+ self.bind(transitionEnd, cb);
+ } else {
+ // Fallback to timers if the 'transitionend' event isn't supported.
+ window.setTimeout(cb, i);
+ }
+
+ // Apply transitions.
+ self.each(function() {
+ if (i > 0) {
+ this.style[support.transition] = transitionValue;
+ }
+ $(this).css(properties);
+ });
+ };
+
+ // Defer running. This allows the browser to paint any pending CSS it hasn't
+ // painted yet before doing the transitions.
+ var deferredRun = function(next) {
+ var i = 0;
+
+ // Durations that are too slow will get transitions mixed up.
+ // (Tested on Mac/FF 7.0.1)
+ if ((support.transition === 'MozTransition') && (i < 25)) { i = 25; }
+
+ window.setTimeout(function() { run(next); }, i);
+ };
+
+ // Use jQuery's fx queue.
+ callOrQueue(self, queue, deferredRun);
+
+ // Chainability.
+ return this;
+ };
+
+ function registerCssHook(prop, isPixels) {
+ // For certain properties, the 'px' should not be implied.
+ if (!isPixels) { $.cssNumber[prop] = true; }
+
+ $.transit.propertyMap[prop] = support.transform;
+
+ $.cssHooks[prop] = {
+ get: function(elem) {
+ var t = $(elem).css('transform') || new Transform();
+ return t.get(prop);
+ },
+
+ set: function(elem, value) {
+ var t = $(elem).css('transform');
+ t = (typeof t === 'string' || t === null) ? new Transform() : t;
+ t.setFromString(prop, value);
+ $(elem).css({ transform: t });
+ }
+ };
+ }
+
+ // ### uncamel(str)
+ // Converts a camelcase string to a dasherized string.
+ // (`marginLeft` => `margin-left`)
+ function uncamel(str) {
+ return str.replace(/([A-Z])/g, function(letter) { return '-' + letter.toLowerCase(); });
+ }
+
+ // ### unit(number, unit)
+ // Ensures that number `number` has a unit. If no unit is found, assume the
+ // default is `unit`.
+ //
+ // unit(2, 'px') //=> "2px"
+ // unit("30deg", 'rad') //=> "30deg"
+ //
+ function unit(i, units) {
+ if ((typeof i === "string") && (!i.match(/^[\-0-9\.]+$/))) {
+ return i;
+ } else {
+ return "" + i + units;
+ }
+ }
+
+ // ### toMS(duration)
+ // Converts given `duration` to a millisecond string.
+ //
+ // toMS('fast') //=> '400ms'
+ // toMS(10) //=> '10ms'
+ //
+ function toMS(duration) {
+ var i = duration;
+
+ // Allow for string durations like 'fast'.
+ if ($.fx.speeds[i]) { i = $.fx.speeds[i]; }
+
+ return unit(i, 'ms');
+ }
+
+ // Export some functions for testable-ness.
+ $.transit.getTransitionValue = getTransition;
+})(jQuery);
diff --git a/module/web/static/js/libs/jqueryui/accordion.js b/module/web/static/js/libs/jqueryui/accordion.js
new file mode 100644
index 000000000..b5a0a9dd0
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/accordion.js
@@ -0,0 +1,614 @@
+define(['jquery','./core','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Accordion 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget( "ui.accordion", {
+ options: {
+ active: 0,
+ animated: "slide",
+ autoHeight: true,
+ clearStyle: false,
+ collapsible: false,
+ event: "click",
+ fillSpace: false,
+ header: "> li > :first-child,> :not(li):even",
+ icons: {
+ header: "ui-icon-triangle-1-e",
+ headerSelected: "ui-icon-triangle-1-s"
+ },
+ navigation: false,
+ navigationFilter: function() {
+ return this.href.toLowerCase() === location.href.toLowerCase();
+ }
+ },
+
+ _create: function() {
+ var self = this,
+ options = self.options;
+
+ self.running = 0;
+
+ self.element
+ .addClass( "ui-accordion ui-widget ui-helper-reset" )
+ // in lack of child-selectors in CSS
+ // we need to mark top-LIs in a UL-accordion for some IE-fix
+ .children( "li" )
+ .addClass( "ui-accordion-li-fix" );
+
+ self.headers = self.element.find( options.header )
+ .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
+ .bind( "mouseenter.accordion", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).addClass( "ui-state-hover" );
+ })
+ .bind( "mouseleave.accordion", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).removeClass( "ui-state-hover" );
+ })
+ .bind( "focus.accordion", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).addClass( "ui-state-focus" );
+ })
+ .bind( "blur.accordion", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).removeClass( "ui-state-focus" );
+ });
+
+ self.headers.next()
+ .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
+
+ if ( options.navigation ) {
+ var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
+ if ( current.length ) {
+ var header = current.closest( ".ui-accordion-header" );
+ if ( header.length ) {
+ // anchor within header
+ self.active = header;
+ } else {
+ // anchor within content
+ self.active = current.closest( ".ui-accordion-content" ).prev();
+ }
+ }
+ }
+
+ self.active = self._findActive( self.active || options.active )
+ .addClass( "ui-state-default ui-state-active" )
+ .toggleClass( "ui-corner-all" )
+ .toggleClass( "ui-corner-top" );
+ self.active.next().addClass( "ui-accordion-content-active" );
+
+ self._createIcons();
+ self.resize();
+
+ // ARIA
+ self.element.attr( "role", "tablist" );
+
+ self.headers
+ .attr( "role", "tab" )
+ .bind( "keydown.accordion", function( event ) {
+ return self._keydown( event );
+ })
+ .next()
+ .attr( "role", "tabpanel" );
+
+ self.headers
+ .not( self.active || "" )
+ .attr({
+ "aria-expanded": "false",
+ "aria-selected": "false",
+ tabIndex: -1
+ })
+ .next()
+ .hide();
+
+ // make sure at least one header is in the tab order
+ if ( !self.active.length ) {
+ self.headers.eq( 0 ).attr( "tabIndex", 0 );
+ } else {
+ self.active
+ .attr({
+ "aria-expanded": "true",
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ }
+
+ // only need links in tab order for Safari
+ if ( !$.browser.safari ) {
+ self.headers.find( "a" ).attr( "tabIndex", -1 );
+ }
+
+ if ( options.event ) {
+ self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
+ self._clickHandler.call( self, event, this );
+ event.preventDefault();
+ });
+ }
+ },
+
+ _createIcons: function() {
+ var options = this.options;
+ if ( options.icons ) {
+ $( "<span></span>" )
+ .addClass( "ui-icon " + options.icons.header )
+ .prependTo( this.headers );
+ this.active.children( ".ui-icon" )
+ .toggleClass(options.icons.header)
+ .toggleClass(options.icons.headerSelected);
+ this.element.addClass( "ui-accordion-icons" );
+ }
+ },
+
+ _destroyIcons: function() {
+ this.headers.children( ".ui-icon" ).remove();
+ this.element.removeClass( "ui-accordion-icons" );
+ },
+
+ destroy: function() {
+ var options = this.options;
+
+ this.element
+ .removeClass( "ui-accordion ui-widget ui-helper-reset" )
+ .removeAttr( "role" );
+
+ this.headers
+ .unbind( ".accordion" )
+ .removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "aria-selected" )
+ .removeAttr( "tabIndex" );
+
+ this.headers.find( "a" ).removeAttr( "tabIndex" );
+ this._destroyIcons();
+ var contents = this.headers.next()
+ .css( "display", "" )
+ .removeAttr( "role" )
+ .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
+ if ( options.autoHeight || options.fillHeight ) {
+ contents.css( "height", "" );
+ }
+
+ return $.Widget.prototype.destroy.call( this );
+ },
+
+ _setOption: function( key, value ) {
+ $.Widget.prototype._setOption.apply( this, arguments );
+
+ if ( key == "active" ) {
+ this.activate( value );
+ }
+ if ( key == "icons" ) {
+ this._destroyIcons();
+ if ( value ) {
+ this._createIcons();
+ }
+ }
+ // #5332 - opacity doesn't cascade to positioned elements in IE
+ // so we need to add the disabled class to the headers and panels
+ if ( key == "disabled" ) {
+ this.headers.add(this.headers.next())
+ [ value ? "addClass" : "removeClass" ](
+ "ui-accordion-disabled ui-state-disabled" );
+ }
+ },
+
+ _keydown: function( event ) {
+ if ( this.options.disabled || event.altKey || event.ctrlKey ) {
+ return;
+ }
+
+ var keyCode = $.ui.keyCode,
+ length = this.headers.length,
+ currentIndex = this.headers.index( event.target ),
+ toFocus = false;
+
+ switch ( event.keyCode ) {
+ case keyCode.RIGHT:
+ case keyCode.DOWN:
+ toFocus = this.headers[ ( currentIndex + 1 ) % length ];
+ break;
+ case keyCode.LEFT:
+ case keyCode.UP:
+ toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
+ break;
+ case keyCode.SPACE:
+ case keyCode.ENTER:
+ this._clickHandler( { target: event.target }, event.target );
+ event.preventDefault();
+ }
+
+ if ( toFocus ) {
+ $( event.target ).attr( "tabIndex", -1 );
+ $( toFocus ).attr( "tabIndex", 0 );
+ toFocus.focus();
+ return false;
+ }
+
+ return true;
+ },
+
+ resize: function() {
+ var options = this.options,
+ maxHeight;
+
+ if ( options.fillSpace ) {
+ if ( $.browser.msie ) {
+ var defOverflow = this.element.parent().css( "overflow" );
+ this.element.parent().css( "overflow", "hidden");
+ }
+ maxHeight = this.element.parent().height();
+ if ($.browser.msie) {
+ this.element.parent().css( "overflow", defOverflow );
+ }
+
+ this.headers.each(function() {
+ maxHeight -= $( this ).outerHeight( true );
+ });
+
+ this.headers.next()
+ .each(function() {
+ $( this ).height( Math.max( 0, maxHeight -
+ $( this ).innerHeight() + $( this ).height() ) );
+ })
+ .css( "overflow", "auto" );
+ } else if ( options.autoHeight ) {
+ maxHeight = 0;
+ this.headers.next()
+ .each(function() {
+ maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
+ })
+ .height( maxHeight );
+ }
+
+ return this;
+ },
+
+ activate: function( index ) {
+ // TODO this gets called on init, changing the option without an explicit call for that
+ this.options.active = index;
+ // call clickHandler with custom event
+ var active = this._findActive( index )[ 0 ];
+ this._clickHandler( { target: active }, active );
+
+ return this;
+ },
+
+ _findActive: function( selector ) {
+ return selector
+ ? typeof selector === "number"
+ ? this.headers.filter( ":eq(" + selector + ")" )
+ : this.headers.not( this.headers.not( selector ) )
+ : selector === false
+ ? $( [] )
+ : this.headers.filter( ":eq(0)" );
+ },
+
+ // TODO isn't event.target enough? why the separate target argument?
+ _clickHandler: function( event, target ) {
+ var options = this.options;
+ if ( options.disabled ) {
+ return;
+ }
+
+ // called only when using activate(false) to close all parts programmatically
+ if ( !event.target ) {
+ if ( !options.collapsible ) {
+ return;
+ }
+ this.active
+ .removeClass( "ui-state-active ui-corner-top" )
+ .addClass( "ui-state-default ui-corner-all" )
+ .children( ".ui-icon" )
+ .removeClass( options.icons.headerSelected )
+ .addClass( options.icons.header );
+ this.active.next().addClass( "ui-accordion-content-active" );
+ var toHide = this.active.next(),
+ data = {
+ options: options,
+ newHeader: $( [] ),
+ oldHeader: options.active,
+ newContent: $( [] ),
+ oldContent: toHide
+ },
+ toShow = ( this.active = $( [] ) );
+ this._toggle( toShow, toHide, data );
+ return;
+ }
+
+ // get the click target
+ var clicked = $( event.currentTarget || target ),
+ clickedIsActive = clicked[0] === this.active[0];
+
+ // TODO the option is changed, is that correct?
+ // TODO if it is correct, shouldn't that happen after determining that the click is valid?
+ options.active = options.collapsible && clickedIsActive ?
+ false :
+ this.headers.index( clicked );
+
+ // if animations are still active, or the active header is the target, ignore click
+ if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
+ return;
+ }
+
+ // find elements to show and hide
+ var active = this.active,
+ toShow = clicked.next(),
+ toHide = this.active.next(),
+ data = {
+ options: options,
+ newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
+ oldHeader: this.active,
+ newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
+ oldContent: toHide
+ },
+ down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
+
+ // when the call to ._toggle() comes after the class changes
+ // it causes a very odd bug in IE 8 (see #6720)
+ this.active = clickedIsActive ? $([]) : clicked;
+ this._toggle( toShow, toHide, data, clickedIsActive, down );
+
+ // switch classes
+ active
+ .removeClass( "ui-state-active ui-corner-top" )
+ .addClass( "ui-state-default ui-corner-all" )
+ .children( ".ui-icon" )
+ .removeClass( options.icons.headerSelected )
+ .addClass( options.icons.header );
+ if ( !clickedIsActive ) {
+ clicked
+ .removeClass( "ui-state-default ui-corner-all" )
+ .addClass( "ui-state-active ui-corner-top" )
+ .children( ".ui-icon" )
+ .removeClass( options.icons.header )
+ .addClass( options.icons.headerSelected );
+ clicked
+ .next()
+ .addClass( "ui-accordion-content-active" );
+ }
+
+ return;
+ },
+
+ _toggle: function( toShow, toHide, data, clickedIsActive, down ) {
+ var self = this,
+ options = self.options;
+
+ self.toShow = toShow;
+ self.toHide = toHide;
+ self.data = data;
+
+ var complete = function() {
+ if ( !self ) {
+ return;
+ }
+ return self._completed.apply( self, arguments );
+ };
+
+ // trigger changestart event
+ self._trigger( "changestart", null, self.data );
+
+ // count elements to animate
+ self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
+
+ if ( options.animated ) {
+ var animOptions = {};
+
+ if ( options.collapsible && clickedIsActive ) {
+ animOptions = {
+ toShow: $( [] ),
+ toHide: toHide,
+ complete: complete,
+ down: down,
+ autoHeight: options.autoHeight || options.fillSpace
+ };
+ } else {
+ animOptions = {
+ toShow: toShow,
+ toHide: toHide,
+ complete: complete,
+ down: down,
+ autoHeight: options.autoHeight || options.fillSpace
+ };
+ }
+
+ if ( !options.proxied ) {
+ options.proxied = options.animated;
+ }
+
+ if ( !options.proxiedDuration ) {
+ options.proxiedDuration = options.duration;
+ }
+
+ options.animated = $.isFunction( options.proxied ) ?
+ options.proxied( animOptions ) :
+ options.proxied;
+
+ options.duration = $.isFunction( options.proxiedDuration ) ?
+ options.proxiedDuration( animOptions ) :
+ options.proxiedDuration;
+
+ var animations = $.ui.accordion.animations,
+ duration = options.duration,
+ easing = options.animated;
+
+ if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
+ easing = "slide";
+ }
+ if ( !animations[ easing ] ) {
+ animations[ easing ] = function( options ) {
+ this.slide( options, {
+ easing: easing,
+ duration: duration || 700
+ });
+ };
+ }
+
+ animations[ easing ]( animOptions );
+ } else {
+ if ( options.collapsible && clickedIsActive ) {
+ toShow.toggle();
+ } else {
+ toHide.hide();
+ toShow.show();
+ }
+
+ complete( true );
+ }
+
+ // TODO assert that the blur and focus triggers are really necessary, remove otherwise
+ toHide.prev()
+ .attr({
+ "aria-expanded": "false",
+ "aria-selected": "false",
+ tabIndex: -1
+ })
+ .blur();
+ toShow.prev()
+ .attr({
+ "aria-expanded": "true",
+ "aria-selected": "true",
+ tabIndex: 0
+ })
+ .focus();
+ },
+
+ _completed: function( cancel ) {
+ this.running = cancel ? 0 : --this.running;
+ if ( this.running ) {
+ return;
+ }
+
+ if ( this.options.clearStyle ) {
+ this.toShow.add( this.toHide ).css({
+ height: "",
+ overflow: ""
+ });
+ }
+
+ // other classes are removed before the animation; this one needs to stay until completed
+ this.toHide.removeClass( "ui-accordion-content-active" );
+ // Work around for rendering bug in IE (#5421)
+ if ( this.toHide.length ) {
+ this.toHide.parent()[0].className = this.toHide.parent()[0].className;
+ }
+
+ this._trigger( "change", null, this.data );
+ }
+});
+
+$.extend( $.ui.accordion, {
+ version: "1.8.23",
+ animations: {
+ slide: function( options, additions ) {
+ options = $.extend({
+ easing: "swing",
+ duration: 300
+ }, options, additions );
+ if ( !options.toHide.size() ) {
+ options.toShow.animate({
+ height: "show",
+ paddingTop: "show",
+ paddingBottom: "show"
+ }, options );
+ return;
+ }
+ if ( !options.toShow.size() ) {
+ options.toHide.animate({
+ height: "hide",
+ paddingTop: "hide",
+ paddingBottom: "hide"
+ }, options );
+ return;
+ }
+ var overflow = options.toShow.css( "overflow" ),
+ percentDone = 0,
+ showProps = {},
+ hideProps = {},
+ fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
+ originalWidth;
+ // fix width before calculating height of hidden element
+ var s = options.toShow;
+ originalWidth = s[0].style.width;
+ s.width( s.parent().width()
+ - parseFloat( s.css( "paddingLeft" ) )
+ - parseFloat( s.css( "paddingRight" ) )
+ - ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
+ - ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
+
+ $.each( fxAttrs, function( i, prop ) {
+ hideProps[ prop ] = "hide";
+
+ var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
+ showProps[ prop ] = {
+ value: parts[ 1 ],
+ unit: parts[ 2 ] || "px"
+ };
+ });
+ options.toShow.css({ height: 0, overflow: "hidden" }).show();
+ options.toHide
+ .filter( ":hidden" )
+ .each( options.complete )
+ .end()
+ .filter( ":visible" )
+ .animate( hideProps, {
+ step: function( now, settings ) {
+ // only calculate the percent when animating height
+ // IE gets very inconsistent results when animating elements
+ // with small values, which is common for padding
+ if ( settings.prop == "height" ) {
+ percentDone = ( settings.end - settings.start === 0 ) ? 0 :
+ ( settings.now - settings.start ) / ( settings.end - settings.start );
+ }
+
+ options.toShow[ 0 ].style[ settings.prop ] =
+ ( percentDone * showProps[ settings.prop ].value )
+ + showProps[ settings.prop ].unit;
+ },
+ duration: options.duration,
+ easing: options.easing,
+ complete: function() {
+ if ( !options.autoHeight ) {
+ options.toShow.css( "height", "" );
+ }
+ options.toShow.css({
+ width: originalWidth,
+ overflow: overflow
+ });
+ options.complete();
+ }
+ });
+ },
+ bounceslide: function( options ) {
+ this.slide( options, {
+ easing: options.down ? "easeOutBounce" : "swing",
+ duration: options.down ? 1000 : 200
+ });
+ }
+ }
+});
+
+})( jQuery );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/autocomplete.js b/module/web/static/js/libs/jqueryui/autocomplete.js
new file mode 100644
index 000000000..dd6bf1119
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/autocomplete.js
@@ -0,0 +1,634 @@
+define(['jquery','./core','./widget','./position'], function (jQuery) {
+/*!
+ * jQuery UI Autocomplete 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ */
+(function( $, undefined ) {
+
+// used to prevent race conditions with remote data sources
+var requestIndex = 0;
+
+$.widget( "ui.autocomplete", {
+ options: {
+ appendTo: "body",
+ autoFocus: false,
+ delay: 300,
+ minLength: 1,
+ position: {
+ my: "left top",
+ at: "left bottom",
+ collision: "none"
+ },
+ source: null
+ },
+
+ pending: 0,
+
+ _create: function() {
+ var self = this,
+ doc = this.element[ 0 ].ownerDocument,
+ suppressKeyPress;
+ this.isMultiLine = this.element.is( "textarea" );
+
+ this.element
+ .addClass( "ui-autocomplete-input" )
+ .attr( "autocomplete", "off" )
+ // TODO verify these actually work as intended
+ .attr({
+ role: "textbox",
+ "aria-autocomplete": "list",
+ "aria-haspopup": "true"
+ })
+ .bind( "keydown.autocomplete", function( event ) {
+ if ( self.options.disabled || self.element.propAttr( "readOnly" ) ) {
+ return;
+ }
+
+ suppressKeyPress = false;
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ self._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ self._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ self._keyEvent( "previous", event );
+ break;
+ case keyCode.DOWN:
+ self._keyEvent( "next", event );
+ break;
+ case keyCode.ENTER:
+ case keyCode.NUMPAD_ENTER:
+ // when menu is open and has focus
+ if ( self.menu.active ) {
+ // #6055 - Opera still allows the keypress to occur
+ // which causes forms to submit
+ suppressKeyPress = true;
+ event.preventDefault();
+ }
+ //passthrough - ENTER and TAB both select the current element
+ case keyCode.TAB:
+ if ( !self.menu.active ) {
+ return;
+ }
+ self.menu.select( event );
+ break;
+ case keyCode.ESCAPE:
+ self.element.val( self.term );
+ self.close( event );
+ break;
+ default:
+ // keypress is triggered before the input value is changed
+ clearTimeout( self.searching );
+ self.searching = setTimeout(function() {
+ // only search if the value has changed
+ if ( self.term != self.element.val() ) {
+ self.selectedItem = null;
+ self.search( null, event );
+ }
+ }, self.options.delay );
+ break;
+ }
+ })
+ .bind( "keypress.autocomplete", function( event ) {
+ if ( suppressKeyPress ) {
+ suppressKeyPress = false;
+ event.preventDefault();
+ }
+ })
+ .bind( "focus.autocomplete", function() {
+ if ( self.options.disabled ) {
+ return;
+ }
+
+ self.selectedItem = null;
+ self.previous = self.element.val();
+ })
+ .bind( "blur.autocomplete", function( event ) {
+ if ( self.options.disabled ) {
+ return;
+ }
+
+ clearTimeout( self.searching );
+ // clicks on the menu (or a button to trigger a search) will cause a blur event
+ self.closing = setTimeout(function() {
+ self.close( event );
+ self._change( event );
+ }, 150 );
+ });
+ this._initSource();
+ this.menu = $( "<ul></ul>" )
+ .addClass( "ui-autocomplete" )
+ .appendTo( $( this.options.appendTo || "body", doc )[0] )
+ // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
+ .mousedown(function( event ) {
+ // clicking on the scrollbar causes focus to shift to the body
+ // but we can't detect a mouseup or a click immediately afterward
+ // so we have to track the next mousedown and close the menu if
+ // the user clicks somewhere outside of the autocomplete
+ var menuElement = self.menu.element[ 0 ];
+ if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
+ setTimeout(function() {
+ $( document ).one( 'mousedown', function( event ) {
+ if ( event.target !== self.element[ 0 ] &&
+ event.target !== menuElement &&
+ !$.ui.contains( menuElement, event.target ) ) {
+ self.close();
+ }
+ });
+ }, 1 );
+ }
+
+ // use another timeout to make sure the blur-event-handler on the input was already triggered
+ setTimeout(function() {
+ clearTimeout( self.closing );
+ }, 13);
+ })
+ .menu({
+ focus: function( event, ui ) {
+ var item = ui.item.data( "item.autocomplete" );
+ if ( false !== self._trigger( "focus", event, { item: item } ) ) {
+ // use value to match what will end up in the input, if it was a key event
+ if ( /^key/.test(event.originalEvent.type) ) {
+ self.element.val( item.value );
+ }
+ }
+ },
+ selected: function( event, ui ) {
+ var item = ui.item.data( "item.autocomplete" ),
+ previous = self.previous;
+
+ // only trigger when focus was lost (click on menu)
+ if ( self.element[0] !== doc.activeElement ) {
+ self.element.focus();
+ self.previous = previous;
+ // #6109 - IE triggers two focus events and the second
+ // is asynchronous, so we need to reset the previous
+ // term synchronously and asynchronously :-(
+ setTimeout(function() {
+ self.previous = previous;
+ self.selectedItem = item;
+ }, 1);
+ }
+
+ if ( false !== self._trigger( "select", event, { item: item } ) ) {
+ self.element.val( item.value );
+ }
+ // reset the term after the select event
+ // this allows custom select handling to work properly
+ self.term = self.element.val();
+
+ self.close( event );
+ self.selectedItem = item;
+ },
+ blur: function( event, ui ) {
+ // don't set the value of the text field if it's already correct
+ // this prevents moving the cursor unnecessarily
+ if ( self.menu.element.is(":visible") &&
+ ( self.element.val() !== self.term ) ) {
+ self.element.val( self.term );
+ }
+ }
+ })
+ .zIndex( this.element.zIndex() + 1 )
+ // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
+ .css({ top: 0, left: 0 })
+ .hide()
+ .data( "menu" );
+ if ( $.fn.bgiframe ) {
+ this.menu.element.bgiframe();
+ }
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ self.beforeunloadHandler = function() {
+ self.element.removeAttr( "autocomplete" );
+ };
+ $( window ).bind( "beforeunload", self.beforeunloadHandler );
+ },
+
+ destroy: function() {
+ this.element
+ .removeClass( "ui-autocomplete-input" )
+ .removeAttr( "autocomplete" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-autocomplete" )
+ .removeAttr( "aria-haspopup" );
+ this.menu.element.remove();
+ $( window ).unbind( "beforeunload", this.beforeunloadHandler );
+ $.Widget.prototype.destroy.call( this );
+ },
+
+ _setOption: function( key, value ) {
+ $.Widget.prototype._setOption.apply( this, arguments );
+ if ( key === "source" ) {
+ this._initSource();
+ }
+ if ( key === "appendTo" ) {
+ this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
+ }
+ if ( key === "disabled" && value && this.xhr ) {
+ this.xhr.abort();
+ }
+ },
+
+ _initSource: function() {
+ var self = this,
+ array,
+ url;
+ if ( $.isArray(this.options.source) ) {
+ array = this.options.source;
+ this.source = function( request, response ) {
+ response( $.ui.autocomplete.filter(array, request.term) );
+ };
+ } else if ( typeof this.options.source === "string" ) {
+ url = this.options.source;
+ this.source = function( request, response ) {
+ if ( self.xhr ) {
+ self.xhr.abort();
+ }
+ self.xhr = $.ajax({
+ url: url,
+ data: request,
+ dataType: "json",
+ success: function( data, status ) {
+ response( data );
+ },
+ error: function() {
+ response( [] );
+ }
+ });
+ };
+ } else {
+ this.source = this.options.source;
+ }
+ },
+
+ search: function( value, event ) {
+ value = value != null ? value : this.element.val();
+
+ // always save the actual value, not the one passed as an argument
+ this.term = this.element.val();
+
+ if ( value.length < this.options.minLength ) {
+ return this.close( event );
+ }
+
+ clearTimeout( this.closing );
+ if ( this._trigger( "search", event ) === false ) {
+ return;
+ }
+
+ return this._search( value );
+ },
+
+ _search: function( value ) {
+ this.pending++;
+ this.element.addClass( "ui-autocomplete-loading" );
+
+ this.source( { term: value }, this._response() );
+ },
+
+ _response: function() {
+ var that = this,
+ index = ++requestIndex;
+
+ return function( content ) {
+ if ( index === requestIndex ) {
+ that.__response( content );
+ }
+
+ that.pending--;
+ if ( !that.pending ) {
+ that.element.removeClass( "ui-autocomplete-loading" );
+ }
+ };
+ },
+
+ __response: function( content ) {
+ if ( !this.options.disabled && content && content.length ) {
+ content = this._normalize( content );
+ this._suggest( content );
+ this._trigger( "open" );
+ } else {
+ this.close();
+ }
+ },
+
+ close: function( event ) {
+ clearTimeout( this.closing );
+ if ( this.menu.element.is(":visible") ) {
+ this.menu.element.hide();
+ this.menu.deactivate();
+ this._trigger( "close", event );
+ }
+ },
+
+ _change: function( event ) {
+ if ( this.previous !== this.element.val() ) {
+ this._trigger( "change", event, { item: this.selectedItem } );
+ }
+ },
+
+ _normalize: function( items ) {
+ // assume all items have the right format when the first item is complete
+ if ( items.length && items[0].label && items[0].value ) {
+ return items;
+ }
+ return $.map( items, function(item) {
+ if ( typeof item === "string" ) {
+ return {
+ label: item,
+ value: item
+ };
+ }
+ return $.extend({
+ label: item.label || item.value,
+ value: item.value || item.label
+ }, item );
+ });
+ },
+
+ _suggest: function( items ) {
+ var ul = this.menu.element
+ .empty()
+ .zIndex( this.element.zIndex() + 1 );
+ this._renderMenu( ul, items );
+ // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
+ this.menu.deactivate();
+ this.menu.refresh();
+
+ // size and position menu
+ ul.show();
+ this._resizeMenu();
+ ul.position( $.extend({
+ of: this.element
+ }, this.options.position ));
+
+ if ( this.options.autoFocus ) {
+ this.menu.next( new $.Event("mouseover") );
+ }
+ },
+
+ _resizeMenu: function() {
+ var ul = this.menu.element;
+ ul.outerWidth( Math.max(
+ // Firefox wraps long text (possibly a rounding bug)
+ // so we add 1px to avoid the wrapping (#7513)
+ ul.width( "" ).outerWidth() + 1,
+ this.element.outerWidth()
+ ) );
+ },
+
+ _renderMenu: function( ul, items ) {
+ var self = this;
+ $.each( items, function( index, item ) {
+ self._renderItem( ul, item );
+ });
+ },
+
+ _renderItem: function( ul, item) {
+ return $( "<li></li>" )
+ .data( "item.autocomplete", item )
+ .append( $( "<a></a>" ).text( item.label ) )
+ .appendTo( ul );
+ },
+
+ _move: function( direction, event ) {
+ if ( !this.menu.element.is(":visible") ) {
+ this.search( null, event );
+ return;
+ }
+ if ( this.menu.first() && /^previous/.test(direction) ||
+ this.menu.last() && /^next/.test(direction) ) {
+ this.element.val( this.term );
+ this.menu.deactivate();
+ return;
+ }
+ this.menu[ direction ]( event );
+ },
+
+ widget: function() {
+ return this.menu.element;
+ },
+ _keyEvent: function( keyEvent, event ) {
+ if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+ this._move( keyEvent, event );
+
+ // prevents moving cursor to beginning/end of the text field in some browsers
+ event.preventDefault();
+ }
+ }
+});
+
+$.extend( $.ui.autocomplete, {
+ escapeRegex: function( value ) {
+ return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ },
+ filter: function(array, term) {
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
+ return $.grep( array, function(value) {
+ return matcher.test( value.label || value.value || value );
+ });
+ }
+});
+
+}( jQuery ));
+
+/*
+ * jQuery UI Menu (not officially released)
+ *
+ * This widget isn't yet finished and the API is subject to change. We plan to finish
+ * it for the next release. You're welcome to give it a try anyway and give us feedback,
+ * as long as you're okay with migrating your code later on. We can help with that, too.
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function($) {
+
+$.widget("ui.menu", {
+ _create: function() {
+ var self = this;
+ this.element
+ .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
+ .attr({
+ role: "listbox",
+ "aria-activedescendant": "ui-active-menuitem"
+ })
+ .click(function( event ) {
+ if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
+ return;
+ }
+ // temporary
+ event.preventDefault();
+ self.select( event );
+ });
+ this.refresh();
+ },
+
+ refresh: function() {
+ var self = this;
+
+ // don't refresh list items that are already adapted
+ var items = this.element.children("li:not(.ui-menu-item):has(a)")
+ .addClass("ui-menu-item")
+ .attr("role", "menuitem");
+
+ items.children("a")
+ .addClass("ui-corner-all")
+ .attr("tabindex", -1)
+ // mouseenter doesn't work with event delegation
+ .mouseenter(function( event ) {
+ self.activate( event, $(this).parent() );
+ })
+ .mouseleave(function() {
+ self.deactivate();
+ });
+ },
+
+ activate: function( event, item ) {
+ this.deactivate();
+ if (this.hasScroll()) {
+ var offset = item.offset().top - this.element.offset().top,
+ scroll = this.element.scrollTop(),
+ elementHeight = this.element.height();
+ if (offset < 0) {
+ this.element.scrollTop( scroll + offset);
+ } else if (offset >= elementHeight) {
+ this.element.scrollTop( scroll + offset - elementHeight + item.height());
+ }
+ }
+ this.active = item.eq(0)
+ .children("a")
+ .addClass("ui-state-hover")
+ .attr("id", "ui-active-menuitem")
+ .end();
+ this._trigger("focus", event, { item: item });
+ },
+
+ deactivate: function() {
+ if (!this.active) { return; }
+
+ this.active.children("a")
+ .removeClass("ui-state-hover")
+ .removeAttr("id");
+ this._trigger("blur");
+ this.active = null;
+ },
+
+ next: function(event) {
+ this.move("next", ".ui-menu-item:first", event);
+ },
+
+ previous: function(event) {
+ this.move("prev", ".ui-menu-item:last", event);
+ },
+
+ first: function() {
+ return this.active && !this.active.prevAll(".ui-menu-item").length;
+ },
+
+ last: function() {
+ return this.active && !this.active.nextAll(".ui-menu-item").length;
+ },
+
+ move: function(direction, edge, event) {
+ if (!this.active) {
+ this.activate(event, this.element.children(edge));
+ return;
+ }
+ var next = this.active[direction + "All"](".ui-menu-item").eq(0);
+ if (next.length) {
+ this.activate(event, next);
+ } else {
+ this.activate(event, this.element.children(edge));
+ }
+ },
+
+ // TODO merge with previousPage
+ nextPage: function(event) {
+ if (this.hasScroll()) {
+ // TODO merge with no-scroll-else
+ if (!this.active || this.last()) {
+ this.activate(event, this.element.children(".ui-menu-item:first"));
+ return;
+ }
+ var base = this.active.offset().top,
+ height = this.element.height(),
+ result = this.element.children(".ui-menu-item").filter(function() {
+ var close = $(this).offset().top - base - height + $(this).height();
+ // TODO improve approximation
+ return close < 10 && close > -10;
+ });
+
+ // TODO try to catch this earlier when scrollTop indicates the last page anyway
+ if (!result.length) {
+ result = this.element.children(".ui-menu-item:last");
+ }
+ this.activate(event, result);
+ } else {
+ this.activate(event, this.element.children(".ui-menu-item")
+ .filter(!this.active || this.last() ? ":first" : ":last"));
+ }
+ },
+
+ // TODO merge with nextPage
+ previousPage: function(event) {
+ if (this.hasScroll()) {
+ // TODO merge with no-scroll-else
+ if (!this.active || this.first()) {
+ this.activate(event, this.element.children(".ui-menu-item:last"));
+ return;
+ }
+
+ var base = this.active.offset().top,
+ height = this.element.height(),
+ result = this.element.children(".ui-menu-item").filter(function() {
+ var close = $(this).offset().top - base + height - $(this).height();
+ // TODO improve approximation
+ return close < 10 && close > -10;
+ });
+
+ // TODO try to catch this earlier when scrollTop indicates the last page anyway
+ if (!result.length) {
+ result = this.element.children(".ui-menu-item:first");
+ }
+ this.activate(event, result);
+ } else {
+ this.activate(event, this.element.children(".ui-menu-item")
+ .filter(!this.active || this.first() ? ":last" : ":first"));
+ }
+ },
+
+ hasScroll: function() {
+ return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");
+ },
+
+ select: function( event ) {
+ this._trigger("selected", event, { item: this.active });
+ }
+});
+
+}(jQuery));
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/button.js b/module/web/static/js/libs/jqueryui/button.js
new file mode 100644
index 000000000..efc824f71
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/button.js
@@ -0,0 +1,417 @@
+define(['jquery','./core','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Button 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var lastActive, startXPos, startYPos, clickDragged,
+ baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
+ stateClasses = "ui-state-hover ui-state-active ",
+ typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
+ formResetHandler = function() {
+ var buttons = $( this ).find( ":ui-button" );
+ setTimeout(function() {
+ buttons.button( "refresh" );
+ }, 1 );
+ },
+ radioGroup = function( radio ) {
+ var name = radio.name,
+ form = radio.form,
+ radios = $( [] );
+ if ( name ) {
+ if ( form ) {
+ radios = $( form ).find( "[name='" + name + "']" );
+ } else {
+ radios = $( "[name='" + name + "']", radio.ownerDocument )
+ .filter(function() {
+ return !this.form;
+ });
+ }
+ }
+ return radios;
+ };
+
+$.widget( "ui.button", {
+ options: {
+ disabled: null,
+ text: true,
+ label: null,
+ icons: {
+ primary: null,
+ secondary: null
+ }
+ },
+ _create: function() {
+ this.element.closest( "form" )
+ .unbind( "reset.button" )
+ .bind( "reset.button", formResetHandler );
+
+ if ( typeof this.options.disabled !== "boolean" ) {
+ this.options.disabled = !!this.element.propAttr( "disabled" );
+ } else {
+ this.element.propAttr( "disabled", this.options.disabled );
+ }
+
+ this._determineButtonType();
+ this.hasTitle = !!this.buttonElement.attr( "title" );
+
+ var self = this,
+ options = this.options,
+ toggleButton = this.type === "checkbox" || this.type === "radio",
+ hoverClass = "ui-state-hover" + ( !toggleButton ? " ui-state-active" : "" ),
+ focusClass = "ui-state-focus";
+
+ if ( options.label === null ) {
+ options.label = this.buttonElement.html();
+ }
+
+ this.buttonElement
+ .addClass( baseClasses )
+ .attr( "role", "button" )
+ .bind( "mouseenter.button", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).addClass( "ui-state-hover" );
+ if ( this === lastActive ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ .bind( "mouseleave.button", function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).removeClass( hoverClass );
+ })
+ .bind( "click.button", function( event ) {
+ if ( options.disabled ) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+ });
+
+ this.element
+ .bind( "focus.button", function() {
+ // no need to check disabled, focus won't be triggered anyway
+ self.buttonElement.addClass( focusClass );
+ })
+ .bind( "blur.button", function() {
+ self.buttonElement.removeClass( focusClass );
+ });
+
+ if ( toggleButton ) {
+ this.element.bind( "change.button", function() {
+ if ( clickDragged ) {
+ return;
+ }
+ self.refresh();
+ });
+ // if mouse moves between mousedown and mouseup (drag) set clickDragged flag
+ // prevents issue where button state changes but checkbox/radio checked state
+ // does not in Firefox (see ticket #6970)
+ this.buttonElement
+ .bind( "mousedown.button", function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ clickDragged = false;
+ startXPos = event.pageX;
+ startYPos = event.pageY;
+ })
+ .bind( "mouseup.button", function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
+ clickDragged = true;
+ }
+ });
+ }
+
+ if ( this.type === "checkbox" ) {
+ this.buttonElement.bind( "click.button", function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ $( this ).toggleClass( "ui-state-active" );
+ self.buttonElement.attr( "aria-pressed", self.element[0].checked );
+ });
+ } else if ( this.type === "radio" ) {
+ this.buttonElement.bind( "click.button", function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ self.buttonElement.attr( "aria-pressed", "true" );
+
+ var radio = self.element[ 0 ];
+ radioGroup( radio )
+ .not( radio )
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ });
+ } else {
+ this.buttonElement
+ .bind( "mousedown.button", function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ lastActive = this;
+ $( document ).one( "mouseup", function() {
+ lastActive = null;
+ });
+ })
+ .bind( "mouseup.button", function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).removeClass( "ui-state-active" );
+ })
+ .bind( "keydown.button", function(event) {
+ if ( options.disabled ) {
+ return false;
+ }
+ if ( event.keyCode == $.ui.keyCode.SPACE || event.keyCode == $.ui.keyCode.ENTER ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ .bind( "keyup.button", function() {
+ $( this ).removeClass( "ui-state-active" );
+ });
+
+ if ( this.buttonElement.is("a") ) {
+ this.buttonElement.keyup(function(event) {
+ if ( event.keyCode === $.ui.keyCode.SPACE ) {
+ // TODO pass through original event correctly (just as 2nd argument doesn't work)
+ $( this ).click();
+ }
+ });
+ }
+ }
+
+ // TODO: pull out $.Widget's handling for the disabled option into
+ // $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
+ // be overridden by individual plugins
+ this._setOption( "disabled", options.disabled );
+ this._resetButton();
+ },
+
+ _determineButtonType: function() {
+
+ if ( this.element.is(":checkbox") ) {
+ this.type = "checkbox";
+ } else if ( this.element.is(":radio") ) {
+ this.type = "radio";
+ } else if ( this.element.is("input") ) {
+ this.type = "input";
+ } else {
+ this.type = "button";
+ }
+
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ // we don't search against the document in case the element
+ // is disconnected from the DOM
+ var ancestor = this.element.parents().filter(":last"),
+ labelSelector = "label[for='" + this.element.attr("id") + "']";
+ this.buttonElement = ancestor.find( labelSelector );
+ if ( !this.buttonElement.length ) {
+ ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
+ this.buttonElement = ancestor.filter( labelSelector );
+ if ( !this.buttonElement.length ) {
+ this.buttonElement = ancestor.find( labelSelector );
+ }
+ }
+ this.element.addClass( "ui-helper-hidden-accessible" );
+
+ var checked = this.element.is( ":checked" );
+ if ( checked ) {
+ this.buttonElement.addClass( "ui-state-active" );
+ }
+ this.buttonElement.attr( "aria-pressed", checked );
+ } else {
+ this.buttonElement = this.element;
+ }
+ },
+
+ widget: function() {
+ return this.buttonElement;
+ },
+
+ destroy: function() {
+ this.element
+ .removeClass( "ui-helper-hidden-accessible" );
+ this.buttonElement
+ .removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
+ .removeAttr( "role" )
+ .removeAttr( "aria-pressed" )
+ .html( this.buttonElement.find(".ui-button-text").html() );
+
+ if ( !this.hasTitle ) {
+ this.buttonElement.removeAttr( "title" );
+ }
+
+ $.Widget.prototype.destroy.call( this );
+ },
+
+ _setOption: function( key, value ) {
+ $.Widget.prototype._setOption.apply( this, arguments );
+ if ( key === "disabled" ) {
+ if ( value ) {
+ this.element.propAttr( "disabled", true );
+ } else {
+ this.element.propAttr( "disabled", false );
+ }
+ return;
+ }
+ this._resetButton();
+ },
+
+ refresh: function() {
+ var isDisabled = this.element.is( ":disabled" );
+ if ( isDisabled !== this.options.disabled ) {
+ this._setOption( "disabled", isDisabled );
+ }
+ if ( this.type === "radio" ) {
+ radioGroup( this.element[0] ).each(function() {
+ if ( $( this ).is( ":checked" ) ) {
+ $( this ).button( "widget" )
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ $( this ).button( "widget" )
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ });
+ } else if ( this.type === "checkbox" ) {
+ if ( this.element.is( ":checked" ) ) {
+ this.buttonElement
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ this.buttonElement
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ }
+ },
+
+ _resetButton: function() {
+ if ( this.type === "input" ) {
+ if ( this.options.label ) {
+ this.element.val( this.options.label );
+ }
+ return;
+ }
+ var buttonElement = this.buttonElement.removeClass( typeClasses ),
+ buttonText = $( "<span></span>", this.element[0].ownerDocument )
+ .addClass( "ui-button-text" )
+ .html( this.options.label )
+ .appendTo( buttonElement.empty() )
+ .text(),
+ icons = this.options.icons,
+ multipleIcons = icons.primary && icons.secondary,
+ buttonClasses = [];
+
+ if ( icons.primary || icons.secondary ) {
+ if ( this.options.text ) {
+ buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
+ }
+
+ if ( icons.primary ) {
+ buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
+ }
+
+ if ( icons.secondary ) {
+ buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
+ }
+
+ if ( !this.options.text ) {
+ buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
+
+ if ( !this.hasTitle ) {
+ buttonElement.attr( "title", buttonText );
+ }
+ }
+ } else {
+ buttonClasses.push( "ui-button-text-only" );
+ }
+ buttonElement.addClass( buttonClasses.join( " " ) );
+ }
+});
+
+$.widget( "ui.buttonset", {
+ options: {
+ items: ":button, :submit, :reset, :checkbox, :radio, a, :data(button)"
+ },
+
+ _create: function() {
+ this.element.addClass( "ui-buttonset" );
+ },
+
+ _init: function() {
+ this.refresh();
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "disabled" ) {
+ this.buttons.button( "option", key, value );
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+ },
+
+ refresh: function() {
+ var rtl = this.element.css( "direction" ) === "rtl";
+
+ this.buttons = this.element.find( this.options.items )
+ .filter( ":ui-button" )
+ .button( "refresh" )
+ .end()
+ .not( ":ui-button" )
+ .button()
+ .end()
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
+ .filter( ":first" )
+ .addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
+ .end()
+ .filter( ":last" )
+ .addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
+ .end()
+ .end();
+ },
+
+ destroy: function() {
+ this.element.removeClass( "ui-buttonset" );
+ this.buttons
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-left ui-corner-right" )
+ .end()
+ .button( "destroy" );
+
+ $.Widget.prototype.destroy.call( this );
+ }
+});
+
+}( jQuery ) );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/core.js b/module/web/static/js/libs/jqueryui/core.js
new file mode 100644
index 000000000..771316b5a
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/core.js
@@ -0,0 +1,337 @@
+define(['jquery'], function (jQuery) {
+/*!
+ * jQuery UI 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function( $, undefined ) {
+
+// prevent duplicate loading
+// this is only a problem because we proxy existing functions
+// and we don't want to double proxy them
+$.ui = $.ui || {};
+if ( $.ui.version ) {
+ return;
+}
+
+$.extend( $.ui, {
+ version: "1.8.23",
+
+ keyCode: {
+ ALT: 18,
+ BACKSPACE: 8,
+ CAPS_LOCK: 20,
+ COMMA: 188,
+ COMMAND: 91,
+ COMMAND_LEFT: 91, // COMMAND
+ COMMAND_RIGHT: 93,
+ CONTROL: 17,
+ DELETE: 46,
+ DOWN: 40,
+ END: 35,
+ ENTER: 13,
+ ESCAPE: 27,
+ HOME: 36,
+ INSERT: 45,
+ LEFT: 37,
+ MENU: 93, // COMMAND_RIGHT
+ NUMPAD_ADD: 107,
+ NUMPAD_DECIMAL: 110,
+ NUMPAD_DIVIDE: 111,
+ NUMPAD_ENTER: 108,
+ NUMPAD_MULTIPLY: 106,
+ NUMPAD_SUBTRACT: 109,
+ PAGE_DOWN: 34,
+ PAGE_UP: 33,
+ PERIOD: 190,
+ RIGHT: 39,
+ SHIFT: 16,
+ SPACE: 32,
+ TAB: 9,
+ UP: 38,
+ WINDOWS: 91 // COMMAND
+ }
+});
+
+// plugins
+$.fn.extend({
+ propAttr: $.fn.prop || $.fn.attr,
+
+ _focus: $.fn.focus,
+ focus: function( delay, fn ) {
+ return typeof delay === "number" ?
+ this.each(function() {
+ var elem = this;
+ setTimeout(function() {
+ $( elem ).focus();
+ if ( fn ) {
+ fn.call( elem );
+ }
+ }, delay );
+ }) :
+ this._focus.apply( this, arguments );
+ },
+
+ scrollParent: function() {
+ var scrollParent;
+ if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
+ scrollParent = this.parents().filter(function() {
+ return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
+ }).eq(0);
+ } else {
+ scrollParent = this.parents().filter(function() {
+ return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
+ }).eq(0);
+ }
+
+ return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
+ },
+
+ zIndex: function( zIndex ) {
+ if ( zIndex !== undefined ) {
+ return this.css( "zIndex", zIndex );
+ }
+
+ if ( this.length ) {
+ var elem = $( this[ 0 ] ), position, value;
+ while ( elem.length && elem[ 0 ] !== document ) {
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
+ // This makes behavior of this function consistent across browsers
+ // WebKit always returns auto if the element is positioned
+ position = elem.css( "position" );
+ if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+ // IE returns 0 when zIndex is not specified
+ // other browsers return a string
+ // we ignore the case of nested elements with an explicit value of 0
+ // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+ value = parseInt( elem.css( "zIndex" ), 10 );
+ if ( !isNaN( value ) && value !== 0 ) {
+ return value;
+ }
+ }
+ elem = elem.parent();
+ }
+ }
+
+ return 0;
+ },
+
+ disableSelection: function() {
+ return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+ ".ui-disableSelection", function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ enableSelection: function() {
+ return this.unbind( ".ui-disableSelection" );
+ }
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+ $.each( [ "Width", "Height" ], function( i, name ) {
+ var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+ type = name.toLowerCase(),
+ orig = {
+ innerWidth: $.fn.innerWidth,
+ innerHeight: $.fn.innerHeight,
+ outerWidth: $.fn.outerWidth,
+ outerHeight: $.fn.outerHeight
+ };
+
+ function reduce( elem, size, border, margin ) {
+ $.each( side, function() {
+ size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0;
+ if ( border ) {
+ size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0;
+ }
+ if ( margin ) {
+ size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0;
+ }
+ });
+ return size;
+ }
+
+ $.fn[ "inner" + name ] = function( size ) {
+ if ( size === undefined ) {
+ return orig[ "inner" + name ].call( this );
+ }
+
+ return this.each(function() {
+ $( this ).css( type, reduce( this, size ) + "px" );
+ });
+ };
+
+ $.fn[ "outer" + name] = function( size, margin ) {
+ if ( typeof size !== "number" ) {
+ return orig[ "outer" + name ].call( this, size );
+ }
+
+ return this.each(function() {
+ $( this).css( type, reduce( this, size, true, margin ) + "px" );
+ });
+ };
+ });
+}
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+ var nodeName = element.nodeName.toLowerCase();
+ if ( "area" === nodeName ) {
+ var map = element.parentNode,
+ mapName = map.name,
+ img;
+ if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+ return false;
+ }
+ img = $( "img[usemap=#" + mapName + "]" )[0];
+ return !!img && visible( img );
+ }
+ return ( /input|select|textarea|button|object/.test( nodeName )
+ ? !element.disabled
+ : "a" == nodeName
+ ? element.href || isTabIndexNotNaN
+ : isTabIndexNotNaN)
+ // the element and all of its ancestors must be visible
+ && visible( element );
+}
+
+function visible( element ) {
+ return !$( element ).parents().andSelf().filter(function() {
+ return $.curCSS( this, "visibility" ) === "hidden" ||
+ $.expr.filters.hidden( this );
+ }).length;
+}
+
+$.extend( $.expr[ ":" ], {
+ data: $.expr.createPseudo ?
+ $.expr.createPseudo(function( dataName ) {
+ return function( elem ) {
+ return !!$.data( elem, dataName );
+ };
+ }) :
+ // support: jQuery <1.8
+ function( elem, i, match ) {
+ return !!$.data( elem, match[ 3 ] );
+ },
+
+ focusable: function( element ) {
+ return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+ },
+
+ tabbable: function( element ) {
+ var tabIndex = $.attr( element, "tabindex" ),
+ isTabIndexNaN = isNaN( tabIndex );
+ return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+ }
+});
+
+// support
+$(function() {
+ var body = document.body,
+ div = body.appendChild( div = document.createElement( "div" ) );
+
+ // access offsetHeight before setting the style to prevent a layout bug
+ // in IE 9 which causes the elemnt to continue to take up space even
+ // after it is removed from the DOM (#8026)
+ div.offsetHeight;
+
+ $.extend( div.style, {
+ minHeight: "100px",
+ height: "auto",
+ padding: 0,
+ borderWidth: 0
+ });
+
+ $.support.minHeight = div.offsetHeight === 100;
+ $.support.selectstart = "onselectstart" in div;
+
+ // set display to none to avoid a layout bug in IE
+ // http://dev.jquery.com/ticket/4014
+ body.removeChild( div ).style.display = "none";
+});
+
+// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css
+if ( !$.curCSS ) {
+ $.curCSS = $.css;
+}
+
+
+
+
+
+// deprecated
+$.extend( $.ui, {
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
+ plugin: {
+ add: function( module, option, set ) {
+ var proto = $.ui[ module ].prototype;
+ for ( var i in set ) {
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
+ }
+ },
+ call: function( instance, name, args ) {
+ var set = instance.plugins[ name ];
+ if ( !set || !instance.element[ 0 ].parentNode ) {
+ return;
+ }
+
+ for ( var i = 0; i < set.length; i++ ) {
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
+ set[ i ][ 1 ].apply( instance.element, args );
+ }
+ }
+ }
+ },
+
+ // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
+ contains: function( a, b ) {
+ return document.compareDocumentPosition ?
+ a.compareDocumentPosition( b ) & 16 :
+ a !== b && a.contains( b );
+ },
+
+ // only used by resizable
+ hasScroll: function( el, a ) {
+
+ //If overflow is hidden, the element might have extra content, but the user wants to hide it
+ if ( $( el ).css( "overflow" ) === "hidden") {
+ return false;
+ }
+
+ var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+ has = false;
+
+ if ( el[ scroll ] > 0 ) {
+ return true;
+ }
+
+ // TODO: determine which cases actually cause this to happen
+ // if the element doesn't have the scroll set, see if it's possible to
+ // set the scroll
+ el[ scroll ] = 1;
+ has = ( el[ scroll ] > 0 );
+ el[ scroll ] = 0;
+ return has;
+ },
+
+ // these are odd functions, fix the API or move into individual plugins
+ isOverAxis: function( x, reference, size ) {
+ //Determines when x coordinate is over "b" element axis
+ return ( x > reference ) && ( x < ( reference + size ) );
+ },
+ isOver: function( y, x, top, left, height, width ) {
+ //Determines when x, y coordinates is over "b" element
+ return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
+ }
+});
+
+})( jQuery );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/datepicker.js b/module/web/static/js/libs/jqueryui/datepicker.js
new file mode 100644
index 000000000..331cbd60e
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/datepicker.js
@@ -0,0 +1,1857 @@
+define(['jquery','./core'], function (jQuery) {
+/*!
+ * jQuery UI Datepicker 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker
+ *
+ * Depends:
+ * jquery.ui.core.js
+ */
+(function( $, undefined ) {
+
+$.extend($.ui, { datepicker: { version: "1.8.23" } });
+
+var PROP_NAME = 'datepicker';
+var dpuuid = new Date().getTime();
+var instActive;
+
+/* Date picker manager.
+ Use the singleton instance of this class, $.datepicker, to interact with the date picker.
+ Settings for (groups of) date pickers are maintained in an instance object,
+ allowing multiple different settings on the same page. */
+
+function Datepicker() {
+ this.debug = false; // Change this to true to start debugging
+ this._curInst = null; // The current instance in use
+ this._keyEvent = false; // If the last event was a key event
+ this._disabledInputs = []; // List of date picker inputs that have been disabled
+ this._datepickerShowing = false; // True if the popup picker is showing , false if not
+ this._inDialog = false; // True if showing within a "dialog", false if not
+ this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division
+ this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class
+ this._appendClass = 'ui-datepicker-append'; // The name of the append marker class
+ this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class
+ this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class
+ this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class
+ this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class
+ this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class
+ this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class
+ this.regional = []; // Available regional settings, indexed by language code
+ this.regional[''] = { // Default regional settings
+ closeText: 'Done', // Display text for close link
+ prevText: 'Prev', // Display text for previous month link
+ nextText: 'Next', // Display text for next month link
+ currentText: 'Today', // Display text for current month link
+ monthNames: ['January','February','March','April','May','June',
+ 'July','August','September','October','November','December'], // Names of months for drop-down and formatting
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
+ dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting
+ dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday
+ weekHeader: 'Wk', // Column header for week of the year
+ dateFormat: 'mm/dd/yy', // See format options on parseDate
+ firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
+ isRTL: false, // True if right-to-left language, false if left-to-right
+ showMonthAfterYear: false, // True if the year select precedes month, false for month then year
+ yearSuffix: '' // Additional text to append to the year in the month headers
+ };
+ this._defaults = { // Global defaults for all the date picker instances
+ showOn: 'focus', // 'focus' for popup on focus,
+ // 'button' for trigger button, or 'both' for either
+ showAnim: 'fadeIn', // Name of jQuery animation for popup
+ showOptions: {}, // Options for enhanced animations
+ defaultDate: null, // Used when field is blank: actual date,
+ // +/-number for offset from today, null for today
+ appendText: '', // Display text following the input box, e.g. showing the format
+ buttonText: '...', // Text for trigger button
+ buttonImage: '', // URL for trigger button image
+ buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
+ hideIfNoPrevNext: false, // True to hide next/previous month links
+ // if not applicable, false to just disable them
+ navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
+ gotoCurrent: false, // True if today link goes back to current selection instead
+ changeMonth: false, // True if month can be selected directly, false if only prev/next
+ changeYear: false, // True if year can be selected directly, false if only prev/next
+ yearRange: 'c-10:c+10', // Range of years to display in drop-down,
+ // either relative to today's year (-nn:+nn), relative to currently displayed year
+ // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
+ showOtherMonths: false, // True to show dates in other months, false to leave blank
+ selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
+ showWeek: false, // True to show week of the year, false to not show it
+ calculateWeek: this.iso8601Week, // How to calculate the week of the year,
+ // takes a Date and returns the number of the week for it
+ shortYearCutoff: '+10', // Short year values < this are in the current century,
+ // > this are in the previous century,
+ // string value starting with '+' for current year + value
+ minDate: null, // The earliest selectable date, or null for no limit
+ maxDate: null, // The latest selectable date, or null for no limit
+ duration: 'fast', // Duration of display/closure
+ beforeShowDay: null, // Function that takes a date and returns an array with
+ // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '',
+ // [2] = cell title (optional), e.g. $.datepicker.noWeekends
+ beforeShow: null, // Function that takes an input field and
+ // returns a set of custom settings for the date picker
+ onSelect: null, // Define a callback function when a date is selected
+ onChangeMonthYear: null, // Define a callback function when the month or year is changed
+ onClose: null, // Define a callback function when the datepicker is closed
+ numberOfMonths: 1, // Number of months to show at a time
+ showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
+ stepMonths: 1, // Number of months to step back/forward
+ stepBigMonths: 12, // Number of months to step back/forward for the big links
+ altField: '', // Selector for an alternate field to store selected dates into
+ altFormat: '', // The date format to use for the alternate field
+ constrainInput: true, // The input is constrained by the current date format
+ showButtonPanel: false, // True to show button panel, false to not show it
+ autoSize: false, // True to size the input for the date format, false to leave as is
+ disabled: false // The initial disabled state
+ };
+ $.extend(this._defaults, this.regional['']);
+ this.dpDiv = bindHover($('<div id="' + this._mainDivId + '" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'));
+}
+
+$.extend(Datepicker.prototype, {
+ /* Class name added to elements to indicate already configured with a date picker. */
+ markerClassName: 'hasDatepicker',
+
+ //Keep track of the maximum number of rows displayed (see #7043)
+ maxRows: 4,
+
+ /* Debug logging (if enabled). */
+ log: function () {
+ if (this.debug)
+ console.log.apply('', arguments);
+ },
+
+ // TODO rename to "widget" when switching to widget factory
+ _widgetDatepicker: function() {
+ return this.dpDiv;
+ },
+
+ /* Override the default settings for all instances of the date picker.
+ @param settings object - the new settings to use as defaults (anonymous object)
+ @return the manager object */
+ setDefaults: function(settings) {
+ extendRemove(this._defaults, settings || {});
+ return this;
+ },
+
+ /* Attach the date picker to a jQuery selection.
+ @param target element - the target input field or division or span
+ @param settings object - the new settings to use for this date picker instance (anonymous) */
+ _attachDatepicker: function(target, settings) {
+ // check for settings on the control itself - in namespace 'date:'
+ var inlineSettings = null;
+ for (var attrName in this._defaults) {
+ var attrValue = target.getAttribute('date:' + attrName);
+ if (attrValue) {
+ inlineSettings = inlineSettings || {};
+ try {
+ inlineSettings[attrName] = eval(attrValue);
+ } catch (err) {
+ inlineSettings[attrName] = attrValue;
+ }
+ }
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ var inline = (nodeName == 'div' || nodeName == 'span');
+ if (!target.id) {
+ this.uuid += 1;
+ target.id = 'dp' + this.uuid;
+ }
+ var inst = this._newInst($(target), inline);
+ inst.settings = $.extend({}, settings || {}, inlineSettings || {});
+ if (nodeName == 'input') {
+ this._connectDatepicker(target, inst);
+ } else if (inline) {
+ this._inlineDatepicker(target, inst);
+ }
+ },
+
+ /* Create a new instance object. */
+ _newInst: function(target, inline) {
+ var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
+ return {id: id, input: target, // associated target
+ selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
+ drawMonth: 0, drawYear: 0, // month being drawn
+ inline: inline, // is datepicker inline or not
+ dpDiv: (!inline ? this.dpDiv : // presentation div
+ bindHover($('<div class="' + this._inlineClass + ' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')))};
+ },
+
+ /* Attach the date picker to an input field. */
+ _connectDatepicker: function(target, inst) {
+ var input = $(target);
+ inst.append = $([]);
+ inst.trigger = $([]);
+ if (input.hasClass(this.markerClassName))
+ return;
+ this._attachments(input, inst);
+ input.addClass(this.markerClassName).keydown(this._doKeyDown).
+ keypress(this._doKeyPress).keyup(this._doKeyUp).
+ bind("setData.datepicker", function(event, key, value) {
+ inst.settings[key] = value;
+ }).bind("getData.datepicker", function(event, key) {
+ return this._get(inst, key);
+ });
+ this._autoSize(inst);
+ $.data(target, PROP_NAME, inst);
+ //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ },
+
+ /* Make attachments based on settings. */
+ _attachments: function(input, inst) {
+ var appendText = this._get(inst, 'appendText');
+ var isRTL = this._get(inst, 'isRTL');
+ if (inst.append)
+ inst.append.remove();
+ if (appendText) {
+ inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
+ input[isRTL ? 'before' : 'after'](inst.append);
+ }
+ input.unbind('focus', this._showDatepicker);
+ if (inst.trigger)
+ inst.trigger.remove();
+ var showOn = this._get(inst, 'showOn');
+ if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field
+ input.focus(this._showDatepicker);
+ if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked
+ var buttonText = this._get(inst, 'buttonText');
+ var buttonImage = this._get(inst, 'buttonImage');
+ inst.trigger = $(this._get(inst, 'buttonImageOnly') ?
+ $('<img/>').addClass(this._triggerClass).
+ attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
+ $('<button type="button"></button>').addClass(this._triggerClass).
+ html(buttonImage == '' ? buttonText : $('<img/>').attr(
+ { src:buttonImage, alt:buttonText, title:buttonText })));
+ input[isRTL ? 'before' : 'after'](inst.trigger);
+ inst.trigger.click(function() {
+ if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0])
+ $.datepicker._hideDatepicker();
+ else if ($.datepicker._datepickerShowing && $.datepicker._lastInput != input[0]) {
+ $.datepicker._hideDatepicker();
+ $.datepicker._showDatepicker(input[0]);
+ } else
+ $.datepicker._showDatepicker(input[0]);
+ return false;
+ });
+ }
+ },
+
+ /* Apply the maximum length for the date format. */
+ _autoSize: function(inst) {
+ if (this._get(inst, 'autoSize') && !inst.inline) {
+ var date = new Date(2009, 12 - 1, 20); // Ensure double digits
+ var dateFormat = this._get(inst, 'dateFormat');
+ if (dateFormat.match(/[DM]/)) {
+ var findMax = function(names) {
+ var max = 0;
+ var maxI = 0;
+ for (var i = 0; i < names.length; i++) {
+ if (names[i].length > max) {
+ max = names[i].length;
+ maxI = i;
+ }
+ }
+ return maxI;
+ };
+ date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
+ 'monthNames' : 'monthNamesShort'))));
+ date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
+ 'dayNames' : 'dayNamesShort'))) + 20 - date.getDay());
+ }
+ inst.input.attr('size', this._formatDate(inst, date).length);
+ }
+ },
+
+ /* Attach an inline date picker to a div. */
+ _inlineDatepicker: function(target, inst) {
+ var divSpan = $(target);
+ if (divSpan.hasClass(this.markerClassName))
+ return;
+ divSpan.addClass(this.markerClassName).append(inst.dpDiv).
+ bind("setData.datepicker", function(event, key, value){
+ inst.settings[key] = value;
+ }).bind("getData.datepicker", function(event, key){
+ return this._get(inst, key);
+ });
+ $.data(target, PROP_NAME, inst);
+ this._setDate(inst, this._getDefaultDate(inst), true);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ //If disabled option is true, disable the datepicker before showing it (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
+ // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
+ inst.dpDiv.css( "display", "block" );
+ },
+
+ /* Pop-up the date picker in a "dialog" box.
+ @param input element - ignored
+ @param date string or Date - the initial date to display
+ @param onSelect function - the function to call when a date is selected
+ @param settings object - update the dialog date picker instance's settings (anonymous object)
+ @param pos int[2] - coordinates for the dialog's position within the screen or
+ event - with x/y coordinates or
+ leave empty for default (screen centre)
+ @return the manager object */
+ _dialogDatepicker: function(input, date, onSelect, settings, pos) {
+ var inst = this._dialogInst; // internal instance
+ if (!inst) {
+ this.uuid += 1;
+ var id = 'dp' + this.uuid;
+ this._dialogInput = $('<input type="text" id="' + id +
+ '" style="position: absolute; top: -100px; width: 0px;"/>');
+ this._dialogInput.keydown(this._doKeyDown);
+ $('body').append(this._dialogInput);
+ inst = this._dialogInst = this._newInst(this._dialogInput, false);
+ inst.settings = {};
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ }
+ extendRemove(inst.settings, settings || {});
+ date = (date && date.constructor == Date ? this._formatDate(inst, date) : date);
+ this._dialogInput.val(date);
+
+ this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
+ if (!this._pos) {
+ var browserWidth = document.documentElement.clientWidth;
+ var browserHeight = document.documentElement.clientHeight;
+ var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+ var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+ this._pos = // should use actual width/height below
+ [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
+ }
+
+ // move input on screen for focus, but hidden behind dialog
+ this._dialogInput.css('left', (this._pos[0] + 20) + 'px').css('top', this._pos[1] + 'px');
+ inst.settings.onSelect = onSelect;
+ this._inDialog = true;
+ this.dpDiv.addClass(this._dialogClass);
+ this._showDatepicker(this._dialogInput[0]);
+ if ($.blockUI)
+ $.blockUI(this.dpDiv);
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ return this;
+ },
+
+ /* Detach a datepicker from its control.
+ @param target element - the target input field or division or span */
+ _destroyDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ $.removeData(target, PROP_NAME);
+ if (nodeName == 'input') {
+ inst.append.remove();
+ inst.trigger.remove();
+ $target.removeClass(this.markerClassName).
+ unbind('focus', this._showDatepicker).
+ unbind('keydown', this._doKeyDown).
+ unbind('keypress', this._doKeyPress).
+ unbind('keyup', this._doKeyUp);
+ } else if (nodeName == 'div' || nodeName == 'span')
+ $target.removeClass(this.markerClassName).empty();
+ },
+
+ /* Enable the date picker to a jQuery selection.
+ @param target element - the target input field or division or span */
+ _enableDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ if (nodeName == 'input') {
+ target.disabled = false;
+ inst.trigger.filter('button').
+ each(function() { this.disabled = false; }).end().
+ filter('img').css({opacity: '1.0', cursor: ''});
+ }
+ else if (nodeName == 'div' || nodeName == 'span') {
+ var inline = $target.children('.' + this._inlineClass);
+ inline.children().removeClass('ui-state-disabled');
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ removeAttr("disabled");
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value == target ? null : value); }); // delete entry
+ },
+
+ /* Disable the date picker to a jQuery selection.
+ @param target element - the target input field or division or span */
+ _disableDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ if (nodeName == 'input') {
+ target.disabled = true;
+ inst.trigger.filter('button').
+ each(function() { this.disabled = true; }).end().
+ filter('img').css({opacity: '0.5', cursor: 'default'});
+ }
+ else if (nodeName == 'div' || nodeName == 'span') {
+ var inline = $target.children('.' + this._inlineClass);
+ inline.children().addClass('ui-state-disabled');
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ attr("disabled", "disabled");
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value == target ? null : value); }); // delete entry
+ this._disabledInputs[this._disabledInputs.length] = target;
+ },
+
+ /* Is the first field in a jQuery collection disabled as a datepicker?
+ @param target element - the target input field or division or span
+ @return boolean - true if disabled, false if enabled */
+ _isDisabledDatepicker: function(target) {
+ if (!target) {
+ return false;
+ }
+ for (var i = 0; i < this._disabledInputs.length; i++) {
+ if (this._disabledInputs[i] == target)
+ return true;
+ }
+ return false;
+ },
+
+ /* Retrieve the instance data for the target control.
+ @param target element - the target input field or division or span
+ @return object - the associated instance data
+ @throws error if a jQuery problem getting data */
+ _getInst: function(target) {
+ try {
+ return $.data(target, PROP_NAME);
+ }
+ catch (err) {
+ throw 'Missing instance data for this datepicker';
+ }
+ },
+
+ /* Update or retrieve the settings for a date picker attached to an input field or division.
+ @param target element - the target input field or division or span
+ @param name object - the new settings to update or
+ string - the name of the setting to change or retrieve,
+ when retrieving also 'all' for all instance settings or
+ 'defaults' for all global defaults
+ @param value any - the new value for the setting
+ (omit if above is an object or to retrieve a value) */
+ _optionDatepicker: function(target, name, value) {
+ var inst = this._getInst(target);
+ if (arguments.length == 2 && typeof name == 'string') {
+ return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) :
+ (inst ? (name == 'all' ? $.extend({}, inst.settings) :
+ this._get(inst, name)) : null));
+ }
+ var settings = name || {};
+ if (typeof name == 'string') {
+ settings = {};
+ settings[name] = value;
+ }
+ if (inst) {
+ if (this._curInst == inst) {
+ this._hideDatepicker();
+ }
+ var date = this._getDateDatepicker(target, true);
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ extendRemove(inst.settings, settings);
+ // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
+ if (minDate !== null && settings['dateFormat'] !== undefined && settings['minDate'] === undefined)
+ inst.settings.minDate = this._formatDate(inst, minDate);
+ if (maxDate !== null && settings['dateFormat'] !== undefined && settings['maxDate'] === undefined)
+ inst.settings.maxDate = this._formatDate(inst, maxDate);
+ this._attachments($(target), inst);
+ this._autoSize(inst);
+ this._setDate(inst, date);
+ this._updateAlternate(inst);
+ this._updateDatepicker(inst);
+ }
+ },
+
+ // change method deprecated
+ _changeDatepicker: function(target, name, value) {
+ this._optionDatepicker(target, name, value);
+ },
+
+ /* Redraw the date picker attached to an input field or division.
+ @param target element - the target input field or division or span */
+ _refreshDatepicker: function(target) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._updateDatepicker(inst);
+ }
+ },
+
+ /* Set the dates for a jQuery selection.
+ @param target element - the target input field or division or span
+ @param date Date - the new date */
+ _setDateDatepicker: function(target, date) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._setDate(inst, date);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ }
+ },
+
+ /* Get the date(s) for the first entry in a jQuery selection.
+ @param target element - the target input field or division or span
+ @param noDefault boolean - true if no default date is to be used
+ @return Date - the current date */
+ _getDateDatepicker: function(target, noDefault) {
+ var inst = this._getInst(target);
+ if (inst && !inst.inline)
+ this._setDateFromField(inst, noDefault);
+ return (inst ? this._getDate(inst) : null);
+ },
+
+ /* Handle keystrokes. */
+ _doKeyDown: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ var handled = true;
+ var isRTL = inst.dpDiv.is('.ui-datepicker-rtl');
+ inst._keyEvent = true;
+ if ($.datepicker._datepickerShowing)
+ switch (event.keyCode) {
+ case 9: $.datepicker._hideDatepicker();
+ handled = false;
+ break; // hide on tab out
+ case 13: var sel = $('td.' + $.datepicker._dayOverClass + ':not(.' +
+ $.datepicker._currentClass + ')', inst.dpDiv);
+ if (sel[0])
+ $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
+ var onSelect = $.datepicker._get(inst, 'onSelect');
+ if (onSelect) {
+ var dateStr = $.datepicker._formatDate(inst);
+
+ // trigger custom callback
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
+ }
+ else
+ $.datepicker._hideDatepicker();
+ return false; // don't submit the form
+ break; // select the value on enter
+ case 27: $.datepicker._hideDatepicker();
+ break; // hide on escape
+ case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, 'stepBigMonths') :
+ -$.datepicker._get(inst, 'stepMonths')), 'M');
+ break; // previous month/year on page up/+ ctrl
+ case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, 'stepBigMonths') :
+ +$.datepicker._get(inst, 'stepMonths')), 'M');
+ break; // next month/year on page down/+ ctrl
+ case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target);
+ handled = event.ctrlKey || event.metaKey;
+ break; // clear on ctrl or command +end
+ case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target);
+ handled = event.ctrlKey || event.metaKey;
+ break; // current on ctrl or command +home
+ case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D');
+ handled = event.ctrlKey || event.metaKey;
+ // -1 day on ctrl or command +left
+ if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, 'stepBigMonths') :
+ -$.datepicker._get(inst, 'stepMonths')), 'M');
+ // next month/year on alt +left on Mac
+ break;
+ case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D');
+ handled = event.ctrlKey || event.metaKey;
+ break; // -1 week on ctrl or command +up
+ case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D');
+ handled = event.ctrlKey || event.metaKey;
+ // +1 day on ctrl or command +right
+ if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, 'stepBigMonths') :
+ +$.datepicker._get(inst, 'stepMonths')), 'M');
+ // next month/year on alt +right
+ break;
+ case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D');
+ handled = event.ctrlKey || event.metaKey;
+ break; // +1 week on ctrl or command +down
+ default: handled = false;
+ }
+ else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home
+ $.datepicker._showDatepicker(this);
+ else {
+ handled = false;
+ }
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
+
+ /* Filter entered characters - based on date format. */
+ _doKeyPress: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ if ($.datepicker._get(inst, 'constrainInput')) {
+ var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat'));
+ var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
+ return event.ctrlKey || event.metaKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1);
+ }
+ },
+
+ /* Synchronise manual entry and field/alternate field. */
+ _doKeyUp: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ if (inst.input.val() != inst.lastVal) {
+ try {
+ var date = $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
+ (inst.input ? inst.input.val() : null),
+ $.datepicker._getFormatConfig(inst));
+ if (date) { // only if valid
+ $.datepicker._setDateFromField(inst);
+ $.datepicker._updateAlternate(inst);
+ $.datepicker._updateDatepicker(inst);
+ }
+ }
+ catch (err) {
+ $.datepicker.log(err);
+ }
+ }
+ return true;
+ },
+
+ /* Pop-up the date picker for a given input field.
+ If false returned from beforeShow event handler do not show.
+ @param input element - the input field attached to the date picker or
+ event - if triggered by focus */
+ _showDatepicker: function(input) {
+ input = input.target || input;
+ if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
+ input = $('input', input.parentNode)[0];
+ if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here
+ return;
+ var inst = $.datepicker._getInst(input);
+ if ($.datepicker._curInst && $.datepicker._curInst != inst) {
+ $.datepicker._curInst.dpDiv.stop(true, true);
+ if ( inst && $.datepicker._datepickerShowing ) {
+ $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
+ }
+ }
+ var beforeShow = $.datepicker._get(inst, 'beforeShow');
+ var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
+ if(beforeShowSettings === false){
+ //false
+ return;
+ }
+ extendRemove(inst.settings, beforeShowSettings);
+ inst.lastVal = null;
+ $.datepicker._lastInput = input;
+ $.datepicker._setDateFromField(inst);
+ if ($.datepicker._inDialog) // hide cursor
+ input.value = '';
+ if (!$.datepicker._pos) { // position below input
+ $.datepicker._pos = $.datepicker._findPos(input);
+ $.datepicker._pos[1] += input.offsetHeight; // add the height
+ }
+ var isFixed = false;
+ $(input).parents().each(function() {
+ isFixed |= $(this).css('position') == 'fixed';
+ return !isFixed;
+ });
+ if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
+ $.datepicker._pos[0] -= document.documentElement.scrollLeft;
+ $.datepicker._pos[1] -= document.documentElement.scrollTop;
+ }
+ var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
+ $.datepicker._pos = null;
+ //to avoid flashes on Firefox
+ inst.dpDiv.empty();
+ // determine sizing offscreen
+ inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
+ $.datepicker._updateDatepicker(inst);
+ // fix width for dynamic number of date pickers
+ // and adjust position before showing
+ offset = $.datepicker._checkOffset(inst, offset, isFixed);
+ inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
+ 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
+ left: offset.left + 'px', top: offset.top + 'px'});
+ if (!inst.inline) {
+ var showAnim = $.datepicker._get(inst, 'showAnim');
+ var duration = $.datepicker._get(inst, 'duration');
+ var postProcess = function() {
+ var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
+ if( !! cover.length ){
+ var borders = $.datepicker._getBorders(inst.dpDiv);
+ cover.css({left: -borders[0], top: -borders[1],
+ width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()});
+ }
+ };
+ inst.dpDiv.zIndex($(input).zIndex()+1);
+ $.datepicker._datepickerShowing = true;
+ if ($.effects && $.effects[showAnim])
+ inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
+ else
+ inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess);
+ if (!showAnim || !duration)
+ postProcess();
+ if (inst.input.is(':visible') && !inst.input.is(':disabled'))
+ inst.input.focus();
+ $.datepicker._curInst = inst;
+ }
+ },
+
+ /* Generate the date picker content. */
+ _updateDatepicker: function(inst) {
+ var self = this;
+ self.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
+ var borders = $.datepicker._getBorders(inst.dpDiv);
+ instActive = inst; // for delegate hover events
+ inst.dpDiv.empty().append(this._generateHTML(inst));
+ this._attachHandlers(inst);
+ var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
+ if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6
+ cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()})
+ }
+ inst.dpDiv.find('.' + this._dayOverClass + ' a').mouseover();
+ var numMonths = this._getNumberOfMonths(inst);
+ var cols = numMonths[1];
+ var width = 17;
+ inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width('');
+ if (cols > 1)
+ inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em');
+ inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') +
+ 'Class']('ui-datepicker-multi');
+ inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') +
+ 'Class']('ui-datepicker-rtl');
+ if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input &&
+ // #6694 - don't focus the input if it's already focused
+ // this breaks the change event in IE
+ inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement)
+ inst.input.focus();
+ // deffered render of the years select (to avoid flashes on Firefox)
+ if( inst.yearshtml ){
+ var origyearshtml = inst.yearshtml;
+ setTimeout(function(){
+ //assure that inst.yearshtml didn't change.
+ if( origyearshtml === inst.yearshtml && inst.yearshtml ){
+ inst.dpDiv.find('select.ui-datepicker-year:first').replaceWith(inst.yearshtml);
+ }
+ origyearshtml = inst.yearshtml = null;
+ }, 0);
+ }
+ },
+
+ /* Retrieve the size of left and top borders for an element.
+ @param elem (jQuery object) the element of interest
+ @return (number[2]) the left and top borders */
+ _getBorders: function(elem) {
+ var convert = function(value) {
+ return {thin: 1, medium: 2, thick: 3}[value] || value;
+ };
+ return [parseFloat(convert(elem.css('border-left-width'))),
+ parseFloat(convert(elem.css('border-top-width')))];
+ },
+
+ /* Check positioning to remain on screen. */
+ _checkOffset: function(inst, offset, isFixed) {
+ var dpWidth = inst.dpDiv.outerWidth();
+ var dpHeight = inst.dpDiv.outerHeight();
+ var inputWidth = inst.input ? inst.input.outerWidth() : 0;
+ var inputHeight = inst.input ? inst.input.outerHeight() : 0;
+ var viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft());
+ var viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());
+
+ offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0);
+ offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
+ offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
+
+ // now check if datepicker is showing outside window viewport - move to a better place if so.
+ offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
+ offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+ Math.abs(dpHeight + inputHeight) : 0);
+
+ return offset;
+ },
+
+ /* Find an object's position on the screen. */
+ _findPos: function(obj) {
+ var inst = this._getInst(obj);
+ var isRTL = this._get(inst, 'isRTL');
+ while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) {
+ obj = obj[isRTL ? 'previousSibling' : 'nextSibling'];
+ }
+ var position = $(obj).offset();
+ return [position.left, position.top];
+ },
+
+ /* Hide the date picker from view.
+ @param input element - the input field attached to the date picker */
+ _hideDatepicker: function(input) {
+ var inst = this._curInst;
+ if (!inst || (input && inst != $.data(input, PROP_NAME)))
+ return;
+ if (this._datepickerShowing) {
+ var showAnim = this._get(inst, 'showAnim');
+ var duration = this._get(inst, 'duration');
+ var postProcess = function() {
+ $.datepicker._tidyDialog(inst);
+ };
+ if ($.effects && $.effects[showAnim])
+ inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
+ else
+ inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' :
+ (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
+ if (!showAnim)
+ postProcess();
+ this._datepickerShowing = false;
+ var onClose = this._get(inst, 'onClose');
+ if (onClose)
+ onClose.apply((inst.input ? inst.input[0] : null),
+ [(inst.input ? inst.input.val() : ''), inst]);
+ this._lastInput = null;
+ if (this._inDialog) {
+ this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
+ if ($.blockUI) {
+ $.unblockUI();
+ $('body').append(this.dpDiv);
+ }
+ }
+ this._inDialog = false;
+ }
+ },
+
+ /* Tidy up after a dialog display. */
+ _tidyDialog: function(inst) {
+ inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar');
+ },
+
+ /* Close date picker if clicked elsewhere. */
+ _checkExternalClick: function(event) {
+ if (!$.datepicker._curInst)
+ return;
+
+ var $target = $(event.target),
+ inst = $.datepicker._getInst($target[0]);
+
+ if ( ( ( $target[0].id != $.datepicker._mainDivId &&
+ $target.parents('#' + $.datepicker._mainDivId).length == 0 &&
+ !$target.hasClass($.datepicker.markerClassName) &&
+ !$target.closest("." + $.datepicker._triggerClass).length &&
+ $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
+ ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) )
+ $.datepicker._hideDatepicker();
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustDate: function(id, offset, period) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ if (this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+ this._adjustInstDate(inst, offset +
+ (period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning
+ period);
+ this._updateDatepicker(inst);
+ },
+
+ /* Action for current link. */
+ _gotoToday: function(id) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
+ inst.selectedDay = inst.currentDay;
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth;
+ inst.drawYear = inst.selectedYear = inst.currentYear;
+ }
+ else {
+ var date = new Date();
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ }
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a new month/year. */
+ _selectMonthYear: function(id, select, period) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
+ inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
+ parseInt(select.options[select.selectedIndex].value,10);
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a day. */
+ _selectDay: function(id, month, year, td) {
+ var target = $(id);
+ if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+ var inst = this._getInst(target[0]);
+ inst.selectedDay = inst.currentDay = $('a', td).html();
+ inst.selectedMonth = inst.currentMonth = month;
+ inst.selectedYear = inst.currentYear = year;
+ this._selectDate(id, this._formatDate(inst,
+ inst.currentDay, inst.currentMonth, inst.currentYear));
+ },
+
+ /* Erase the input field and hide the date picker. */
+ _clearDate: function(id) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ this._selectDate(target, '');
+ },
+
+ /* Update the input field with the selected date. */
+ _selectDate: function(id, dateStr) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
+ if (inst.input)
+ inst.input.val(dateStr);
+ this._updateAlternate(inst);
+ var onSelect = this._get(inst, 'onSelect');
+ if (onSelect)
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
+ else if (inst.input)
+ inst.input.trigger('change'); // fire the change event
+ if (inst.inline)
+ this._updateDatepicker(inst);
+ else {
+ this._hideDatepicker();
+ this._lastInput = inst.input[0];
+ if (typeof(inst.input[0]) != 'object')
+ inst.input.focus(); // restore focus
+ this._lastInput = null;
+ }
+ },
+
+ /* Update any alternate field to synchronise with the main field. */
+ _updateAlternate: function(inst) {
+ var altField = this._get(inst, 'altField');
+ if (altField) { // update alternate field too
+ var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
+ var date = this._getDate(inst);
+ var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
+ $(altField).each(function() { $(this).val(dateStr); });
+ }
+ },
+
+ /* Set as beforeShowDay function to prevent selection of weekends.
+ @param date Date - the date to customise
+ @return [boolean, string] - is this date selectable?, what is its CSS class? */
+ noWeekends: function(date) {
+ var day = date.getDay();
+ return [(day > 0 && day < 6), ''];
+ },
+
+ /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+ @param date Date - the date to get the week for
+ @return number - the number of the week within the year that contains this date */
+ iso8601Week: function(date) {
+ var checkDate = new Date(date.getTime());
+ // Find Thursday of this week starting on Monday
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+ var time = checkDate.getTime();
+ checkDate.setMonth(0); // Compare with Jan 1
+ checkDate.setDate(1);
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+ },
+
+ /* Parse a string value into a date object.
+ See formatDate below for the possible formats.
+
+ @param format string - the expected format of the date
+ @param value string - the date in the above format
+ @param settings Object - attributes include:
+ shortYearCutoff number - the cutoff year for determining the century (optional)
+ dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ dayNames string[7] - names of the days from Sunday (optional)
+ monthNamesShort string[12] - abbreviated names of the months (optional)
+ monthNames string[12] - names of the months (optional)
+ @return Date - the extracted date value or null if value is blank */
+ parseDate: function (format, value, settings) {
+ if (format == null || value == null)
+ throw 'Invalid arguments';
+ value = (typeof value == 'object' ? value.toString() : value + '');
+ if (value == '')
+ return null;
+ var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+ var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
+ var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
+ var year = -1;
+ var month = -1;
+ var day = -1;
+ var doy = -1;
+ var literal = false;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ // Extract a number from the string value
+ var getNumber = function(match) {
+ var isDoubled = lookAhead(match);
+ var size = (match == '@' ? 14 : (match == '!' ? 20 :
+ (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
+ var digits = new RegExp('^\\d{1,' + size + '}');
+ var num = value.substring(iValue).match(digits);
+ if (!num)
+ throw 'Missing number at position ' + iValue;
+ iValue += num[0].length;
+ return parseInt(num[0], 10);
+ };
+ // Extract a name from the string value and convert to an index
+ var getName = function(match, shortNames, longNames) {
+ var names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
+ return [ [k, v] ];
+ }).sort(function (a, b) {
+ return -(a[1].length - b[1].length);
+ });
+ var index = -1;
+ $.each(names, function (i, pair) {
+ var name = pair[1];
+ if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
+ index = pair[0];
+ iValue += name.length;
+ return false;
+ }
+ });
+ if (index != -1)
+ return index + 1;
+ else
+ throw 'Unknown name at position ' + iValue;
+ };
+ // Confirm that a literal character matches the string value
+ var checkLiteral = function() {
+ if (value.charAt(iValue) != format.charAt(iFormat))
+ throw 'Unexpected literal at position ' + iValue;
+ iValue++;
+ };
+ var iValue = 0;
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ checkLiteral();
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd':
+ day = getNumber('d');
+ break;
+ case 'D':
+ getName('D', dayNamesShort, dayNames);
+ break;
+ case 'o':
+ doy = getNumber('o');
+ break;
+ case 'm':
+ month = getNumber('m');
+ break;
+ case 'M':
+ month = getName('M', monthNamesShort, monthNames);
+ break;
+ case 'y':
+ year = getNumber('y');
+ break;
+ case '@':
+ var date = new Date(getNumber('@'));
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case '!':
+ var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "'":
+ if (lookAhead("'"))
+ checkLiteral();
+ else
+ literal = true;
+ break;
+ default:
+ checkLiteral();
+ }
+ }
+ if (iValue < value.length){
+ throw "Extra/unparsed characters found in date: " + value.substring(iValue);
+ }
+ if (year == -1)
+ year = new Date().getFullYear();
+ else if (year < 100)
+ year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+ (year <= shortYearCutoff ? 0 : -100);
+ if (doy > -1) {
+ month = 1;
+ day = doy;
+ do {
+ var dim = this._getDaysInMonth(year, month - 1);
+ if (day <= dim)
+ break;
+ month++;
+ day -= dim;
+ } while (true);
+ }
+ var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
+ if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day)
+ throw 'Invalid date'; // E.g. 31/02/00
+ return date;
+ },
+
+ /* Standard date formats. */
+ ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601)
+ COOKIE: 'D, dd M yy',
+ ISO_8601: 'yy-mm-dd',
+ RFC_822: 'D, d M y',
+ RFC_850: 'DD, dd-M-y',
+ RFC_1036: 'D, d M y',
+ RFC_1123: 'D, d M yy',
+ RFC_2822: 'D, d M yy',
+ RSS: 'D, d M y', // RFC 822
+ TICKS: '!',
+ TIMESTAMP: '@',
+ W3C: 'yy-mm-dd', // ISO 8601
+
+ _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
+ Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
+
+ /* Format a date object into a string value.
+ The format can be combinations of the following:
+ d - day of month (no leading zero)
+ dd - day of month (two digit)
+ o - day of year (no leading zeros)
+ oo - day of year (three digit)
+ D - day name short
+ DD - day name long
+ m - month of year (no leading zero)
+ mm - month of year (two digit)
+ M - month name short
+ MM - month name long
+ y - year (two digit)
+ yy - year (four digit)
+ @ - Unix timestamp (ms since 01/01/1970)
+ ! - Windows ticks (100ns since 01/01/0001)
+ '...' - literal text
+ '' - single quote
+
+ @param format string - the desired format of the date
+ @param date Date - the date value to format
+ @param settings Object - attributes include:
+ dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ dayNames string[7] - names of the days from Sunday (optional)
+ monthNamesShort string[12] - abbreviated names of the months (optional)
+ monthNames string[12] - names of the months (optional)
+ @return string - the date in the above format */
+ formatDate: function (format, date, settings) {
+ if (!date)
+ return '';
+ var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
+ var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ // Format a number, with leading zero if necessary
+ var formatNumber = function(match, value, len) {
+ var num = '' + value;
+ if (lookAhead(match))
+ while (num.length < len)
+ num = '0' + num;
+ return num;
+ };
+ // Format a name, short or long as requested
+ var formatName = function(match, value, shortNames, longNames) {
+ return (lookAhead(match) ? longNames[value] : shortNames[value]);
+ };
+ var output = '';
+ var literal = false;
+ if (date)
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ output += format.charAt(iFormat);
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd':
+ output += formatNumber('d', date.getDate(), 2);
+ break;
+ case 'D':
+ output += formatName('D', date.getDay(), dayNamesShort, dayNames);
+ break;
+ case 'o':
+ output += formatNumber('o',
+ Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
+ break;
+ case 'm':
+ output += formatNumber('m', date.getMonth() + 1, 2);
+ break;
+ case 'M':
+ output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
+ break;
+ case 'y':
+ output += (lookAhead('y') ? date.getFullYear() :
+ (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
+ break;
+ case '@':
+ output += date.getTime();
+ break;
+ case '!':
+ output += date.getTime() * 10000 + this._ticksTo1970;
+ break;
+ case "'":
+ if (lookAhead("'"))
+ output += "'";
+ else
+ literal = true;
+ break;
+ default:
+ output += format.charAt(iFormat);
+ }
+ }
+ return output;
+ },
+
+ /* Extract all possible characters from the date format. */
+ _possibleChars: function (format) {
+ var chars = '';
+ var literal = false;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ for (var iFormat = 0; iFormat < format.length; iFormat++)
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ chars += format.charAt(iFormat);
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd': case 'm': case 'y': case '@':
+ chars += '0123456789';
+ break;
+ case 'D': case 'M':
+ return null; // Accept anything
+ case "'":
+ if (lookAhead("'"))
+ chars += "'";
+ else
+ literal = true;
+ break;
+ default:
+ chars += format.charAt(iFormat);
+ }
+ return chars;
+ },
+
+ /* Get a setting value, defaulting if necessary. */
+ _get: function(inst, name) {
+ return inst.settings[name] !== undefined ?
+ inst.settings[name] : this._defaults[name];
+ },
+
+ /* Parse existing date and initialise date picker. */
+ _setDateFromField: function(inst, noDefault) {
+ if (inst.input.val() == inst.lastVal) {
+ return;
+ }
+ var dateFormat = this._get(inst, 'dateFormat');
+ var dates = inst.lastVal = inst.input ? inst.input.val() : null;
+ var date, defaultDate;
+ date = defaultDate = this._getDefaultDate(inst);
+ var settings = this._getFormatConfig(inst);
+ try {
+ date = this.parseDate(dateFormat, dates, settings) || defaultDate;
+ } catch (event) {
+ this.log(event);
+ dates = (noDefault ? '' : dates);
+ }
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ inst.currentDay = (dates ? date.getDate() : 0);
+ inst.currentMonth = (dates ? date.getMonth() : 0);
+ inst.currentYear = (dates ? date.getFullYear() : 0);
+ this._adjustInstDate(inst);
+ },
+
+ /* Retrieve the default date shown on opening. */
+ _getDefaultDate: function(inst) {
+ return this._restrictMinMax(inst,
+ this._determineDate(inst, this._get(inst, 'defaultDate'), new Date()));
+ },
+
+ /* A date may be specified as an exact value or a relative one. */
+ _determineDate: function(inst, date, defaultDate) {
+ var offsetNumeric = function(offset) {
+ var date = new Date();
+ date.setDate(date.getDate() + offset);
+ return date;
+ };
+ var offsetString = function(offset) {
+ try {
+ return $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
+ offset, $.datepicker._getFormatConfig(inst));
+ }
+ catch (e) {
+ // Ignore
+ }
+ var date = (offset.toLowerCase().match(/^c/) ?
+ $.datepicker._getDate(inst) : null) || new Date();
+ var year = date.getFullYear();
+ var month = date.getMonth();
+ var day = date.getDate();
+ var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;
+ var matches = pattern.exec(offset);
+ while (matches) {
+ switch (matches[2] || 'd') {
+ case 'd' : case 'D' :
+ day += parseInt(matches[1],10); break;
+ case 'w' : case 'W' :
+ day += parseInt(matches[1],10) * 7; break;
+ case 'm' : case 'M' :
+ month += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ case 'y': case 'Y' :
+ year += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ }
+ matches = pattern.exec(offset);
+ }
+ return new Date(year, month, day);
+ };
+ var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) :
+ (typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
+ newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate);
+ if (newDate) {
+ newDate.setHours(0);
+ newDate.setMinutes(0);
+ newDate.setSeconds(0);
+ newDate.setMilliseconds(0);
+ }
+ return this._daylightSavingAdjust(newDate);
+ },
+
+ /* Handle switch to/from daylight saving.
+ Hours may be non-zero on daylight saving cut-over:
+ > 12 when midnight changeover, but then cannot generate
+ midnight datetime, so jump to 1AM, otherwise reset.
+ @param date (Date) the date to check
+ @return (Date) the corrected date */
+ _daylightSavingAdjust: function(date) {
+ if (!date) return null;
+ date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
+ return date;
+ },
+
+ /* Set the date(s) directly. */
+ _setDate: function(inst, date, noChange) {
+ var clear = !date;
+ var origMonth = inst.selectedMonth;
+ var origYear = inst.selectedYear;
+ var newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
+ inst.selectedDay = inst.currentDay = newDate.getDate();
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
+ inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
+ if ((origMonth != inst.selectedMonth || origYear != inst.selectedYear) && !noChange)
+ this._notifyChange(inst);
+ this._adjustInstDate(inst);
+ if (inst.input) {
+ inst.input.val(clear ? '' : this._formatDate(inst));
+ }
+ },
+
+ /* Retrieve the date(s) directly. */
+ _getDate: function(inst) {
+ var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null :
+ this._daylightSavingAdjust(new Date(
+ inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return startDate;
+ },
+
+ /* Attach the onxxx handlers. These are declared statically so
+ * they work with static code transformers like Caja.
+ */
+ _attachHandlers: function(inst) {
+ var stepMonths = this._get(inst, 'stepMonths');
+ var id = '#' + inst.id.replace( /\\\\/g, "\\" );
+ inst.dpDiv.find('[data-handler]').map(function () {
+ var handler = {
+ prev: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, -stepMonths, 'M');
+ },
+ next: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, +stepMonths, 'M');
+ },
+ hide: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._hideDatepicker();
+ },
+ today: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._gotoToday(id);
+ },
+ selectDay: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectDay(id, +this.getAttribute('data-month'), +this.getAttribute('data-year'), this);
+ return false;
+ },
+ selectMonth: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'M');
+ return false;
+ },
+ selectYear: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'Y');
+ return false;
+ }
+ };
+ $(this).bind(this.getAttribute('data-event'), handler[this.getAttribute('data-handler')]);
+ });
+ },
+
+ /* Generate the HTML for the current state of the date picker. */
+ _generateHTML: function(inst) {
+ var today = new Date();
+ today = this._daylightSavingAdjust(
+ new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time
+ var isRTL = this._get(inst, 'isRTL');
+ var showButtonPanel = this._get(inst, 'showButtonPanel');
+ var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext');
+ var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat');
+ var numMonths = this._getNumberOfMonths(inst);
+ var showCurrentAtPos = this._get(inst, 'showCurrentAtPos');
+ var stepMonths = this._get(inst, 'stepMonths');
+ var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1);
+ var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
+ new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ var drawMonth = inst.drawMonth - showCurrentAtPos;
+ var drawYear = inst.drawYear;
+ if (drawMonth < 0) {
+ drawMonth += 12;
+ drawYear--;
+ }
+ if (maxDate) {
+ var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
+ maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
+ maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
+ while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
+ drawMonth--;
+ if (drawMonth < 0) {
+ drawMonth = 11;
+ drawYear--;
+ }
+ }
+ }
+ inst.drawMonth = drawMonth;
+ inst.drawYear = drawYear;
+ var prevText = this._get(inst, 'prevText');
+ prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
+ this._getFormatConfig(inst)));
+ var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
+ '<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' +
+ ' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' :
+ (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>'));
+ var nextText = this._get(inst, 'nextText');
+ nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
+ this._getFormatConfig(inst)));
+ var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
+ '<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' +
+ ' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' :
+ (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'));
+ var currentText = this._get(inst, 'currentText');
+ var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today);
+ currentText = (!navigationAsDateFormat ? currentText :
+ this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
+ var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">' +
+ this._get(inst, 'closeText') + '</button>' : '');
+ var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
+ (this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' +
+ '>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
+ var firstDay = parseInt(this._get(inst, 'firstDay'),10);
+ firstDay = (isNaN(firstDay) ? 0 : firstDay);
+ var showWeek = this._get(inst, 'showWeek');
+ var dayNames = this._get(inst, 'dayNames');
+ var dayNamesShort = this._get(inst, 'dayNamesShort');
+ var dayNamesMin = this._get(inst, 'dayNamesMin');
+ var monthNames = this._get(inst, 'monthNames');
+ var monthNamesShort = this._get(inst, 'monthNamesShort');
+ var beforeShowDay = this._get(inst, 'beforeShowDay');
+ var showOtherMonths = this._get(inst, 'showOtherMonths');
+ var selectOtherMonths = this._get(inst, 'selectOtherMonths');
+ var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week;
+ var defaultDate = this._getDefaultDate(inst);
+ var html = '';
+ for (var row = 0; row < numMonths[0]; row++) {
+ var group = '';
+ this.maxRows = 4;
+ for (var col = 0; col < numMonths[1]; col++) {
+ var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
+ var cornerClass = ' ui-corner-all';
+ var calender = '';
+ if (isMultiMonth) {
+ calender += '<div class="ui-datepicker-group';
+ if (numMonths[1] > 1)
+ switch (col) {
+ case 0: calender += ' ui-datepicker-group-first';
+ cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break;
+ case numMonths[1]-1: calender += ' ui-datepicker-group-last';
+ cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break;
+ default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break;
+ }
+ calender += '">';
+ }
+ calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
+ (/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') +
+ (/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') +
+ this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
+ row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
+ '</div><table class="ui-datepicker-calendar"><thead>' +
+ '<tr>';
+ var thead = (showWeek ? '<th class="ui-datepicker-week-col">' + this._get(inst, 'weekHeader') + '</th>' : '');
+ for (var dow = 0; dow < 7; dow++) { // days of the week
+ var day = (dow + firstDay) % 7;
+ thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' +
+ '<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
+ }
+ calender += thead + '</tr></thead><tbody>';
+ var daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
+ if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth)
+ inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
+ var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
+ var curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
+ var numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
+ this.maxRows = numRows;
+ var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
+ for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows
+ calender += '<tr>';
+ var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' +
+ this._get(inst, 'calculateWeek')(printDate) + '</td>');
+ for (var dow = 0; dow < 7; dow++) { // create date picker days
+ var daySettings = (beforeShowDay ?
+ beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']);
+ var otherMonth = (printDate.getMonth() != drawMonth);
+ var unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
+ (minDate && printDate < minDate) || (maxDate && printDate > maxDate);
+ tbody += '<td class="' +
+ ((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
+ (otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
+ ((printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth && inst._keyEvent) || // user pressed key
+ (defaultDate.getTime() == printDate.getTime() && defaultDate.getTime() == selectedDate.getTime()) ?
+ // or defaultDate is current printedDate and defaultDate is selectedDate
+ ' ' + this._dayOverClass : '') + // highlight selected day
+ (unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled': '') + // highlight unselectable days
+ (otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
+ (printDate.getTime() == currentDate.getTime() ? ' ' + this._currentClass : '') + // highlight selected day
+ (printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different)
+ ((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title
+ (unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' + // actions
+ (otherMonth && !showOtherMonths ? '&#xa0;' : // display for other months
+ (unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' +
+ (printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') +
+ (printDate.getTime() == currentDate.getTime() ? ' ui-state-active' : '') + // highlight selected day
+ (otherMonth ? ' ui-priority-secondary' : '') + // distinguish dates from other months
+ '" href="#">' + printDate.getDate() + '</a>')) + '</td>'; // display selectable date
+ printDate.setDate(printDate.getDate() + 1);
+ printDate = this._daylightSavingAdjust(printDate);
+ }
+ calender += tbody + '</tr>';
+ }
+ drawMonth++;
+ if (drawMonth > 11) {
+ drawMonth = 0;
+ drawYear++;
+ }
+ calender += '</tbody></table>' + (isMultiMonth ? '</div>' +
+ ((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : '');
+ group += calender;
+ }
+ html += group;
+ }
+ html += buttonPanel + ($.browser.msie && parseInt($.browser.version,10) < 7 && !inst.inline ?
+ '<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
+ inst._keyEvent = false;
+ return html;
+ },
+
+ /* Generate the month and year header. */
+ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
+ secondary, monthNames, monthNamesShort) {
+ var changeMonth = this._get(inst, 'changeMonth');
+ var changeYear = this._get(inst, 'changeYear');
+ var showMonthAfterYear = this._get(inst, 'showMonthAfterYear');
+ var html = '<div class="ui-datepicker-title">';
+ var monthHtml = '';
+ // month selection
+ if (secondary || !changeMonth)
+ monthHtml += '<span class="ui-datepicker-month">' + monthNames[drawMonth] + '</span>';
+ else {
+ var inMinYear = (minDate && minDate.getFullYear() == drawYear);
+ var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear);
+ monthHtml += '<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';
+ for (var month = 0; month < 12; month++) {
+ if ((!inMinYear || month >= minDate.getMonth()) &&
+ (!inMaxYear || month <= maxDate.getMonth()))
+ monthHtml += '<option value="' + month + '"' +
+ (month == drawMonth ? ' selected="selected"' : '') +
+ '>' + monthNamesShort[month] + '</option>';
+ }
+ monthHtml += '</select>';
+ }
+ if (!showMonthAfterYear)
+ html += monthHtml + (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '');
+ // year selection
+ if ( !inst.yearshtml ) {
+ inst.yearshtml = '';
+ if (secondary || !changeYear)
+ html += '<span class="ui-datepicker-year">' + drawYear + '</span>';
+ else {
+ // determine range of years to display
+ var years = this._get(inst, 'yearRange').split(':');
+ var thisYear = new Date().getFullYear();
+ var determineYear = function(value) {
+ var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) :
+ (value.match(/[+-].*/) ? thisYear + parseInt(value, 10) :
+ parseInt(value, 10)));
+ return (isNaN(year) ? thisYear : year);
+ };
+ var year = determineYear(years[0]);
+ var endYear = Math.max(year, determineYear(years[1] || ''));
+ year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
+ endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
+ inst.yearshtml += '<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';
+ for (; year <= endYear; year++) {
+ inst.yearshtml += '<option value="' + year + '"' +
+ (year == drawYear ? ' selected="selected"' : '') +
+ '>' + year + '</option>';
+ }
+ inst.yearshtml += '</select>';
+
+ html += inst.yearshtml;
+ inst.yearshtml = null;
+ }
+ }
+ html += this._get(inst, 'yearSuffix');
+ if (showMonthAfterYear)
+ html += (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '') + monthHtml;
+ html += '</div>'; // Close datepicker_header
+ return html;
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustInstDate: function(inst, offset, period) {
+ var year = inst.drawYear + (period == 'Y' ? offset : 0);
+ var month = inst.drawMonth + (period == 'M' ? offset : 0);
+ var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) +
+ (period == 'D' ? offset : 0);
+ var date = this._restrictMinMax(inst,
+ this._daylightSavingAdjust(new Date(year, month, day)));
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ if (period == 'M' || period == 'Y')
+ this._notifyChange(inst);
+ },
+
+ /* Ensure a date is within any min/max bounds. */
+ _restrictMinMax: function(inst, date) {
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ var newDate = (minDate && date < minDate ? minDate : date);
+ newDate = (maxDate && newDate > maxDate ? maxDate : newDate);
+ return newDate;
+ },
+
+ /* Notify change of month/year. */
+ _notifyChange: function(inst) {
+ var onChange = this._get(inst, 'onChangeMonthYear');
+ if (onChange)
+ onChange.apply((inst.input ? inst.input[0] : null),
+ [inst.selectedYear, inst.selectedMonth + 1, inst]);
+ },
+
+ /* Determine the number of months to show. */
+ _getNumberOfMonths: function(inst) {
+ var numMonths = this._get(inst, 'numberOfMonths');
+ return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths));
+ },
+
+ /* Determine the current maximum date - ensure no time components are set. */
+ _getMinMaxDate: function(inst, minMax) {
+ return this._determineDate(inst, this._get(inst, minMax + 'Date'), null);
+ },
+
+ /* Find the number of days in a given month. */
+ _getDaysInMonth: function(year, month) {
+ return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
+ },
+
+ /* Find the day of the week of the first of a month. */
+ _getFirstDayOfMonth: function(year, month) {
+ return new Date(year, month, 1).getDay();
+ },
+
+ /* Determines if we should allow a "next/prev" month display change. */
+ _canAdjustMonth: function(inst, offset, curYear, curMonth) {
+ var numMonths = this._getNumberOfMonths(inst);
+ var date = this._daylightSavingAdjust(new Date(curYear,
+ curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
+ if (offset < 0)
+ date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
+ return this._isInRange(inst, date);
+ },
+
+ /* Is the given date in the accepted range? */
+ _isInRange: function(inst, date) {
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ return ((!minDate || date.getTime() >= minDate.getTime()) &&
+ (!maxDate || date.getTime() <= maxDate.getTime()));
+ },
+
+ /* Provide the configuration settings for formatting/parsing. */
+ _getFormatConfig: function(inst) {
+ var shortYearCutoff = this._get(inst, 'shortYearCutoff');
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+ return {shortYearCutoff: shortYearCutoff,
+ dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
+ monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
+ },
+
+ /* Format the given date for display. */
+ _formatDate: function(inst, day, month, year) {
+ if (!day) {
+ inst.currentDay = inst.selectedDay;
+ inst.currentMonth = inst.selectedMonth;
+ inst.currentYear = inst.selectedYear;
+ }
+ var date = (day ? (typeof day == 'object' ? day :
+ this._daylightSavingAdjust(new Date(year, month, day))) :
+ this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
+ }
+});
+
+/*
+ * Bind hover events for datepicker elements.
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
+ * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
+ */
+function bindHover(dpDiv) {
+ var selector = 'button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a';
+ return dpDiv.bind('mouseout', function(event) {
+ var elem = $( event.target ).closest( selector );
+ if ( !elem.length ) {
+ return;
+ }
+ elem.removeClass( "ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover" );
+ })
+ .bind('mouseover', function(event) {
+ var elem = $( event.target ).closest( selector );
+ if ($.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0]) ||
+ !elem.length ) {
+ return;
+ }
+ elem.parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
+ elem.addClass('ui-state-hover');
+ if (elem.hasClass('ui-datepicker-prev')) elem.addClass('ui-datepicker-prev-hover');
+ if (elem.hasClass('ui-datepicker-next')) elem.addClass('ui-datepicker-next-hover');
+ });
+}
+
+/* jQuery extend now ignores nulls! */
+function extendRemove(target, props) {
+ $.extend(target, props);
+ for (var name in props)
+ if (props[name] == null || props[name] == undefined)
+ target[name] = props[name];
+ return target;
+};
+
+/* Determine whether an object is an array. */
+function isArray(a) {
+ return (a && (($.browser.safari && typeof a == 'object' && a.length) ||
+ (a.constructor && a.constructor.toString().match(/\Array\(\)/))));
+};
+
+/* Invoke the datepicker functionality.
+ @param options string - a command, optionally followed by additional parameters or
+ Object - settings for attaching new datepicker functionality
+ @return jQuery object */
+$.fn.datepicker = function(options){
+
+ /* Verify an empty collection wasn't passed - Fixes #6976 */
+ if ( !this.length ) {
+ return this;
+ }
+
+ /* Initialise the date picker. */
+ if (!$.datepicker.initialized) {
+ $(document).mousedown($.datepicker._checkExternalClick).
+ find('body').append($.datepicker.dpDiv);
+ $.datepicker.initialized = true;
+ }
+
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
+ if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget'))
+ return $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
+ return $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ return this.each(function() {
+ typeof options == 'string' ?
+ $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this].concat(otherArgs)) :
+ $.datepicker._attachDatepicker(this, options);
+ });
+};
+
+$.datepicker = new Datepicker(); // singleton instance
+$.datepicker.initialized = false;
+$.datepicker.uuid = new Date().getTime();
+$.datepicker.version = "1.8.23";
+
+// Workaround for #4055
+// Add another global to avoid noConflict issues with inline event handlers
+window['DP_jQuery_' + dpuuid] = $;
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/dialog.js b/module/web/static/js/libs/jqueryui/dialog.js
new file mode 100644
index 000000000..87e91c0a4
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/dialog.js
@@ -0,0 +1,869 @@
+define(['jquery','./core','./widget','./button','./draggable','./mouse','./position','./resizable'], function (jQuery) {
+/*!
+ * jQuery UI Dialog 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.button.js
+ * jquery.ui.draggable.js
+ * jquery.ui.mouse.js
+ * jquery.ui.position.js
+ * jquery.ui.resizable.js
+ */
+(function( $, undefined ) {
+
+var uiDialogClasses =
+ 'ui-dialog ' +
+ 'ui-widget ' +
+ 'ui-widget-content ' +
+ 'ui-corner-all ',
+ sizeRelatedOptions = {
+ buttons: true,
+ height: true,
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true,
+ width: true
+ },
+ resizableRelatedOptions = {
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true
+ };
+
+$.widget("ui.dialog", {
+ options: {
+ autoOpen: true,
+ buttons: {},
+ closeOnEscape: true,
+ closeText: 'close',
+ dialogClass: '',
+ draggable: true,
+ hide: null,
+ height: 'auto',
+ maxHeight: false,
+ maxWidth: false,
+ minHeight: 150,
+ minWidth: 150,
+ modal: false,
+ position: {
+ my: 'center',
+ at: 'center',
+ collision: 'fit',
+ // ensure that the titlebar is never outside the document
+ using: function(pos) {
+ var topOffset = $(this).css(pos).offset().top;
+ if (topOffset < 0) {
+ $(this).css('top', pos.top - topOffset);
+ }
+ }
+ },
+ resizable: true,
+ show: null,
+ stack: true,
+ title: '',
+ width: 300,
+ zIndex: 1000
+ },
+
+ _create: function() {
+ this.originalTitle = this.element.attr('title');
+ // #5742 - .attr() might return a DOMElement
+ if ( typeof this.originalTitle !== "string" ) {
+ this.originalTitle = "";
+ }
+
+ this.options.title = this.options.title || this.originalTitle;
+ var self = this,
+ options = self.options,
+
+ title = options.title || '&#160;',
+ titleId = $.ui.dialog.getTitleId(self.element),
+
+ uiDialog = (self.uiDialog = $('<div></div>'))
+ .appendTo(document.body)
+ .hide()
+ .addClass(uiDialogClasses + options.dialogClass)
+ .css({
+ zIndex: options.zIndex
+ })
+ // setting tabIndex makes the div focusable
+ // setting outline to 0 prevents a border on focus in Mozilla
+ .attr('tabIndex', -1).css('outline', 0).keydown(function(event) {
+ if (options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+ event.keyCode === $.ui.keyCode.ESCAPE) {
+
+ self.close(event);
+ event.preventDefault();
+ }
+ })
+ .attr({
+ role: 'dialog',
+ 'aria-labelledby': titleId
+ })
+ .mousedown(function(event) {
+ self.moveToTop(false, event);
+ }),
+
+ uiDialogContent = self.element
+ .show()
+ .removeAttr('title')
+ .addClass(
+ 'ui-dialog-content ' +
+ 'ui-widget-content')
+ .appendTo(uiDialog),
+
+ uiDialogTitlebar = (self.uiDialogTitlebar = $('<div></div>'))
+ .addClass(
+ 'ui-dialog-titlebar ' +
+ 'ui-widget-header ' +
+ 'ui-corner-all ' +
+ 'ui-helper-clearfix'
+ )
+ .prependTo(uiDialog),
+
+ uiDialogTitlebarClose = $('<a href="#"></a>')
+ .addClass(
+ 'ui-dialog-titlebar-close ' +
+ 'ui-corner-all'
+ )
+ .attr('role', 'button')
+ .hover(
+ function() {
+ uiDialogTitlebarClose.addClass('ui-state-hover');
+ },
+ function() {
+ uiDialogTitlebarClose.removeClass('ui-state-hover');
+ }
+ )
+ .focus(function() {
+ uiDialogTitlebarClose.addClass('ui-state-focus');
+ })
+ .blur(function() {
+ uiDialogTitlebarClose.removeClass('ui-state-focus');
+ })
+ .click(function(event) {
+ self.close(event);
+ return false;
+ })
+ .appendTo(uiDialogTitlebar),
+
+ uiDialogTitlebarCloseText = (self.uiDialogTitlebarCloseText = $('<span></span>'))
+ .addClass(
+ 'ui-icon ' +
+ 'ui-icon-closethick'
+ )
+ .text(options.closeText)
+ .appendTo(uiDialogTitlebarClose),
+
+ uiDialogTitle = $('<span></span>')
+ .addClass('ui-dialog-title')
+ .attr('id', titleId)
+ .html(title)
+ .prependTo(uiDialogTitlebar);
+
+ //handling of deprecated beforeclose (vs beforeClose) option
+ //Ticket #4669 http://dev.jqueryui.com/ticket/4669
+ //TODO: remove in 1.9pre
+ if ($.isFunction(options.beforeclose) && !$.isFunction(options.beforeClose)) {
+ options.beforeClose = options.beforeclose;
+ }
+
+ uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();
+
+ if (options.draggable && $.fn.draggable) {
+ self._makeDraggable();
+ }
+ if (options.resizable && $.fn.resizable) {
+ self._makeResizable();
+ }
+
+ self._createButtons(options.buttons);
+ self._isOpen = false;
+
+ if ($.fn.bgiframe) {
+ uiDialog.bgiframe();
+ }
+ },
+
+ _init: function() {
+ if ( this.options.autoOpen ) {
+ this.open();
+ }
+ },
+
+ destroy: function() {
+ var self = this;
+
+ if (self.overlay) {
+ self.overlay.destroy();
+ }
+ self.uiDialog.hide();
+ self.element
+ .unbind('.dialog')
+ .removeData('dialog')
+ .removeClass('ui-dialog-content ui-widget-content')
+ .hide().appendTo('body');
+ self.uiDialog.remove();
+
+ if (self.originalTitle) {
+ self.element.attr('title', self.originalTitle);
+ }
+
+ return self;
+ },
+
+ widget: function() {
+ return this.uiDialog;
+ },
+
+ close: function(event) {
+ var self = this,
+ maxZ, thisZ;
+
+ if (false === self._trigger('beforeClose', event)) {
+ return;
+ }
+
+ if (self.overlay) {
+ self.overlay.destroy();
+ }
+ self.uiDialog.unbind('keypress.ui-dialog');
+
+ self._isOpen = false;
+
+ if (self.options.hide) {
+ self.uiDialog.hide(self.options.hide, function() {
+ self._trigger('close', event);
+ });
+ } else {
+ self.uiDialog.hide();
+ self._trigger('close', event);
+ }
+
+ $.ui.dialog.overlay.resize();
+
+ // adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
+ if (self.options.modal) {
+ maxZ = 0;
+ $('.ui-dialog').each(function() {
+ if (this !== self.uiDialog[0]) {
+ thisZ = $(this).css('z-index');
+ if(!isNaN(thisZ)) {
+ maxZ = Math.max(maxZ, thisZ);
+ }
+ }
+ });
+ $.ui.dialog.maxZ = maxZ;
+ }
+
+ return self;
+ },
+
+ isOpen: function() {
+ return this._isOpen;
+ },
+
+ // the force parameter allows us to move modal dialogs to their correct
+ // position on open
+ moveToTop: function(force, event) {
+ var self = this,
+ options = self.options,
+ saveScroll;
+
+ if ((options.modal && !force) ||
+ (!options.stack && !options.modal)) {
+ return self._trigger('focus', event);
+ }
+
+ if (options.zIndex > $.ui.dialog.maxZ) {
+ $.ui.dialog.maxZ = options.zIndex;
+ }
+ if (self.overlay) {
+ $.ui.dialog.maxZ += 1;
+ self.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ);
+ }
+
+ //Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed.
+ // http://ui.jquery.com/bugs/ticket/3193
+ saveScroll = { scrollTop: self.element.scrollTop(), scrollLeft: self.element.scrollLeft() };
+ $.ui.dialog.maxZ += 1;
+ self.uiDialog.css('z-index', $.ui.dialog.maxZ);
+ self.element.attr(saveScroll);
+ self._trigger('focus', event);
+
+ return self;
+ },
+
+ open: function() {
+ if (this._isOpen) { return; }
+
+ var self = this,
+ options = self.options,
+ uiDialog = self.uiDialog;
+
+ self.overlay = options.modal ? new $.ui.dialog.overlay(self) : null;
+ self._size();
+ self._position(options.position);
+ uiDialog.show(options.show);
+ self.moveToTop(true);
+
+ // prevent tabbing out of modal dialogs
+ if ( options.modal ) {
+ uiDialog.bind( "keydown.ui-dialog", function( event ) {
+ if ( event.keyCode !== $.ui.keyCode.TAB ) {
+ return;
+ }
+
+ var tabbables = $(':tabbable', this),
+ first = tabbables.filter(':first'),
+ last = tabbables.filter(':last');
+
+ if (event.target === last[0] && !event.shiftKey) {
+ first.focus(1);
+ return false;
+ } else if (event.target === first[0] && event.shiftKey) {
+ last.focus(1);
+ return false;
+ }
+ });
+ }
+
+ // set focus to the first tabbable element in the content area or the first button
+ // if there are no tabbable elements, set focus on the dialog itself
+ $(self.element.find(':tabbable').get().concat(
+ uiDialog.find('.ui-dialog-buttonpane :tabbable').get().concat(
+ uiDialog.get()))).eq(0).focus();
+
+ self._isOpen = true;
+ self._trigger('open');
+
+ return self;
+ },
+
+ _createButtons: function(buttons) {
+ var self = this,
+ hasButtons = false,
+ uiDialogButtonPane = $('<div></div>')
+ .addClass(
+ 'ui-dialog-buttonpane ' +
+ 'ui-widget-content ' +
+ 'ui-helper-clearfix'
+ ),
+ uiButtonSet = $( "<div></div>" )
+ .addClass( "ui-dialog-buttonset" )
+ .appendTo( uiDialogButtonPane );
+
+ // if we already have a button pane, remove it
+ self.uiDialog.find('.ui-dialog-buttonpane').remove();
+
+ if (typeof buttons === 'object' && buttons !== null) {
+ $.each(buttons, function() {
+ return !(hasButtons = true);
+ });
+ }
+ if (hasButtons) {
+ $.each(buttons, function(name, props) {
+ props = $.isFunction( props ) ?
+ { click: props, text: name } :
+ props;
+ var button = $('<button type="button"></button>')
+ .click(function() {
+ props.click.apply(self.element[0], arguments);
+ })
+ .appendTo(uiButtonSet);
+ // can't use .attr( props, true ) with jQuery 1.3.2.
+ $.each( props, function( key, value ) {
+ if ( key === "click" ) {
+ return;
+ }
+ if ( key in button ) {
+ button[ key ]( value );
+ } else {
+ button.attr( key, value );
+ }
+ });
+ if ($.fn.button) {
+ button.button();
+ }
+ });
+ uiDialogButtonPane.appendTo(self.uiDialog);
+ }
+ },
+
+ _makeDraggable: function() {
+ var self = this,
+ options = self.options,
+ doc = $(document),
+ heightBeforeDrag;
+
+ function filteredUi(ui) {
+ return {
+ position: ui.position,
+ offset: ui.offset
+ };
+ }
+
+ self.uiDialog.draggable({
+ cancel: '.ui-dialog-content, .ui-dialog-titlebar-close',
+ handle: '.ui-dialog-titlebar',
+ containment: 'document',
+ start: function(event, ui) {
+ heightBeforeDrag = options.height === "auto" ? "auto" : $(this).height();
+ $(this).height($(this).height()).addClass("ui-dialog-dragging");
+ self._trigger('dragStart', event, filteredUi(ui));
+ },
+ drag: function(event, ui) {
+ self._trigger('drag', event, filteredUi(ui));
+ },
+ stop: function(event, ui) {
+ options.position = [ui.position.left - doc.scrollLeft(),
+ ui.position.top - doc.scrollTop()];
+ $(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag);
+ self._trigger('dragStop', event, filteredUi(ui));
+ $.ui.dialog.overlay.resize();
+ }
+ });
+ },
+
+ _makeResizable: function(handles) {
+ handles = (handles === undefined ? this.options.resizable : handles);
+ var self = this,
+ options = self.options,
+ // .ui-resizable has position: relative defined in the stylesheet
+ // but dialogs have to use absolute or fixed positioning
+ position = self.uiDialog.css('position'),
+ resizeHandles = (typeof handles === 'string' ?
+ handles :
+ 'n,e,s,w,se,sw,ne,nw'
+ );
+
+ function filteredUi(ui) {
+ return {
+ originalPosition: ui.originalPosition,
+ originalSize: ui.originalSize,
+ position: ui.position,
+ size: ui.size
+ };
+ }
+
+ self.uiDialog.resizable({
+ cancel: '.ui-dialog-content',
+ containment: 'document',
+ alsoResize: self.element,
+ maxWidth: options.maxWidth,
+ maxHeight: options.maxHeight,
+ minWidth: options.minWidth,
+ minHeight: self._minHeight(),
+ handles: resizeHandles,
+ start: function(event, ui) {
+ $(this).addClass("ui-dialog-resizing");
+ self._trigger('resizeStart', event, filteredUi(ui));
+ },
+ resize: function(event, ui) {
+ self._trigger('resize', event, filteredUi(ui));
+ },
+ stop: function(event, ui) {
+ $(this).removeClass("ui-dialog-resizing");
+ options.height = $(this).height();
+ options.width = $(this).width();
+ self._trigger('resizeStop', event, filteredUi(ui));
+ $.ui.dialog.overlay.resize();
+ }
+ })
+ .css('position', position)
+ .find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se');
+ },
+
+ _minHeight: function() {
+ var options = this.options;
+
+ if (options.height === 'auto') {
+ return options.minHeight;
+ } else {
+ return Math.min(options.minHeight, options.height);
+ }
+ },
+
+ _position: function(position) {
+ var myAt = [],
+ offset = [0, 0],
+ isVisible;
+
+ if (position) {
+ // deep extending converts arrays to objects in jQuery <= 1.3.2 :-(
+ // if (typeof position == 'string' || $.isArray(position)) {
+ // myAt = $.isArray(position) ? position : position.split(' ');
+
+ if (typeof position === 'string' || (typeof position === 'object' && '0' in position)) {
+ myAt = position.split ? position.split(' ') : [position[0], position[1]];
+ if (myAt.length === 1) {
+ myAt[1] = myAt[0];
+ }
+
+ $.each(['left', 'top'], function(i, offsetPosition) {
+ if (+myAt[i] === myAt[i]) {
+ offset[i] = myAt[i];
+ myAt[i] = offsetPosition;
+ }
+ });
+
+ position = {
+ my: myAt.join(" "),
+ at: myAt.join(" "),
+ offset: offset.join(" ")
+ };
+ }
+
+ position = $.extend({}, $.ui.dialog.prototype.options.position, position);
+ } else {
+ position = $.ui.dialog.prototype.options.position;
+ }
+
+ // need to show the dialog to get the actual offset in the position plugin
+ isVisible = this.uiDialog.is(':visible');
+ if (!isVisible) {
+ this.uiDialog.show();
+ }
+ this.uiDialog
+ // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
+ .css({ top: 0, left: 0 })
+ .position($.extend({ of: window }, position));
+ if (!isVisible) {
+ this.uiDialog.hide();
+ }
+ },
+
+ _setOptions: function( options ) {
+ var self = this,
+ resizableOptions = {},
+ resize = false;
+
+ $.each( options, function( key, value ) {
+ self._setOption( key, value );
+
+ if ( key in sizeRelatedOptions ) {
+ resize = true;
+ }
+ if ( key in resizableRelatedOptions ) {
+ resizableOptions[ key ] = value;
+ }
+ });
+
+ if ( resize ) {
+ this._size();
+ }
+ if ( this.uiDialog.is( ":data(resizable)" ) ) {
+ this.uiDialog.resizable( "option", resizableOptions );
+ }
+ },
+
+ _setOption: function(key, value){
+ var self = this,
+ uiDialog = self.uiDialog;
+
+ switch (key) {
+ //handling of deprecated beforeclose (vs beforeClose) option
+ //Ticket #4669 http://dev.jqueryui.com/ticket/4669
+ //TODO: remove in 1.9pre
+ case "beforeclose":
+ key = "beforeClose";
+ break;
+ case "buttons":
+ self._createButtons(value);
+ break;
+ case "closeText":
+ // ensure that we always pass a string
+ self.uiDialogTitlebarCloseText.text("" + value);
+ break;
+ case "dialogClass":
+ uiDialog
+ .removeClass(self.options.dialogClass)
+ .addClass(uiDialogClasses + value);
+ break;
+ case "disabled":
+ if (value) {
+ uiDialog.addClass('ui-dialog-disabled');
+ } else {
+ uiDialog.removeClass('ui-dialog-disabled');
+ }
+ break;
+ case "draggable":
+ var isDraggable = uiDialog.is( ":data(draggable)" );
+ if ( isDraggable && !value ) {
+ uiDialog.draggable( "destroy" );
+ }
+
+ if ( !isDraggable && value ) {
+ self._makeDraggable();
+ }
+ break;
+ case "position":
+ self._position(value);
+ break;
+ case "resizable":
+ // currently resizable, becoming non-resizable
+ var isResizable = uiDialog.is( ":data(resizable)" );
+ if (isResizable && !value) {
+ uiDialog.resizable('destroy');
+ }
+
+ // currently resizable, changing handles
+ if (isResizable && typeof value === 'string') {
+ uiDialog.resizable('option', 'handles', value);
+ }
+
+ // currently non-resizable, becoming resizable
+ if (!isResizable && value !== false) {
+ self._makeResizable(value);
+ }
+ break;
+ case "title":
+ // convert whatever was passed in o a string, for html() to not throw up
+ $(".ui-dialog-title", self.uiDialogTitlebar).html("" + (value || '&#160;'));
+ break;
+ }
+
+ $.Widget.prototype._setOption.apply(self, arguments);
+ },
+
+ _size: function() {
+ /* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+ * divs will both have width and height set, so we need to reset them
+ */
+ var options = this.options,
+ nonContentHeight,
+ minContentHeight,
+ isVisible = this.uiDialog.is( ":visible" );
+
+ // reset content sizing
+ this.element.show().css({
+ width: 'auto',
+ minHeight: 0,
+ height: 0
+ });
+
+ if (options.minWidth > options.width) {
+ options.width = options.minWidth;
+ }
+
+ // reset wrapper sizing
+ // determine the height of all the non-content elements
+ nonContentHeight = this.uiDialog.css({
+ height: 'auto',
+ width: options.width
+ })
+ .height();
+ minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
+
+ if ( options.height === "auto" ) {
+ // only needed for IE6 support
+ if ( $.support.minHeight ) {
+ this.element.css({
+ minHeight: minContentHeight,
+ height: "auto"
+ });
+ } else {
+ this.uiDialog.show();
+ var autoHeight = this.element.css( "height", "auto" ).height();
+ if ( !isVisible ) {
+ this.uiDialog.hide();
+ }
+ this.element.height( Math.max( autoHeight, minContentHeight ) );
+ }
+ } else {
+ this.element.height( Math.max( options.height - nonContentHeight, 0 ) );
+ }
+
+ if (this.uiDialog.is(':data(resizable)')) {
+ this.uiDialog.resizable('option', 'minHeight', this._minHeight());
+ }
+ }
+});
+
+$.extend($.ui.dialog, {
+ version: "1.8.23",
+
+ uuid: 0,
+ maxZ: 0,
+
+ getTitleId: function($el) {
+ var id = $el.attr('id');
+ if (!id) {
+ this.uuid += 1;
+ id = this.uuid;
+ }
+ return 'ui-dialog-title-' + id;
+ },
+
+ overlay: function(dialog) {
+ this.$el = $.ui.dialog.overlay.create(dialog);
+ }
+});
+
+$.extend($.ui.dialog.overlay, {
+ instances: [],
+ // reuse old instances due to IE memory leak with alpha transparency (see #5185)
+ oldInstances: [],
+ maxZ: 0,
+ events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),
+ function(event) { return event + '.dialog-overlay'; }).join(' '),
+ create: function(dialog) {
+ if (this.instances.length === 0) {
+ // prevent use of anchors and inputs
+ // we use a setTimeout in case the overlay is created from an
+ // event that we're going to be cancelling (see #2804)
+ setTimeout(function() {
+ // handle $(el).dialog().dialog('close') (see #4065)
+ if ($.ui.dialog.overlay.instances.length) {
+ $(document).bind($.ui.dialog.overlay.events, function(event) {
+ // stop events if the z-index of the target is < the z-index of the overlay
+ // we cannot return true when we don't want to cancel the event (#3523)
+ if ($(event.target).zIndex() < $.ui.dialog.overlay.maxZ) {
+ return false;
+ }
+ });
+ }
+ }, 1);
+
+ // allow closing by pressing the escape key
+ $(document).bind('keydown.dialog-overlay', function(event) {
+ if (dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+ event.keyCode === $.ui.keyCode.ESCAPE) {
+
+ dialog.close(event);
+ event.preventDefault();
+ }
+ });
+
+ // handle window resize
+ $(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize);
+ }
+
+ var $el = (this.oldInstances.pop() || $('<div></div>').addClass('ui-widget-overlay'))
+ .appendTo(document.body)
+ .css({
+ width: this.width(),
+ height: this.height()
+ });
+
+ if ($.fn.bgiframe) {
+ $el.bgiframe();
+ }
+
+ this.instances.push($el);
+ return $el;
+ },
+
+ destroy: function($el) {
+ var indexOf = $.inArray($el, this.instances);
+ if (indexOf != -1){
+ this.oldInstances.push(this.instances.splice(indexOf, 1)[0]);
+ }
+
+ if (this.instances.length === 0) {
+ $([document, window]).unbind('.dialog-overlay');
+ }
+
+ $el.remove();
+
+ // adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
+ var maxZ = 0;
+ $.each(this.instances, function() {
+ maxZ = Math.max(maxZ, this.css('z-index'));
+ });
+ this.maxZ = maxZ;
+ },
+
+ height: function() {
+ var scrollHeight,
+ offsetHeight;
+ // handle IE 6
+ if ($.browser.msie && $.browser.version < 7) {
+ scrollHeight = Math.max(
+ document.documentElement.scrollHeight,
+ document.body.scrollHeight
+ );
+ offsetHeight = Math.max(
+ document.documentElement.offsetHeight,
+ document.body.offsetHeight
+ );
+
+ if (scrollHeight < offsetHeight) {
+ return $(window).height() + 'px';
+ } else {
+ return scrollHeight + 'px';
+ }
+ // handle "good" browsers
+ } else {
+ return $(document).height() + 'px';
+ }
+ },
+
+ width: function() {
+ var scrollWidth,
+ offsetWidth;
+ // handle IE
+ if ( $.browser.msie ) {
+ scrollWidth = Math.max(
+ document.documentElement.scrollWidth,
+ document.body.scrollWidth
+ );
+ offsetWidth = Math.max(
+ document.documentElement.offsetWidth,
+ document.body.offsetWidth
+ );
+
+ if (scrollWidth < offsetWidth) {
+ return $(window).width() + 'px';
+ } else {
+ return scrollWidth + 'px';
+ }
+ // handle "good" browsers
+ } else {
+ return $(document).width() + 'px';
+ }
+ },
+
+ resize: function() {
+ /* If the dialog is draggable and the user drags it past the
+ * right edge of the window, the document becomes wider so we
+ * need to stretch the overlay. If the user then drags the
+ * dialog back to the left, the document will become narrower,
+ * so we need to shrink the overlay to the appropriate size.
+ * This is handled by shrinking the overlay before setting it
+ * to the full document size.
+ */
+ var $overlays = $([]);
+ $.each($.ui.dialog.overlay.instances, function() {
+ $overlays = $overlays.add(this);
+ });
+
+ $overlays.css({
+ width: 0,
+ height: 0
+ }).css({
+ width: $.ui.dialog.overlay.width(),
+ height: $.ui.dialog.overlay.height()
+ });
+ }
+});
+
+$.extend($.ui.dialog.overlay.prototype, {
+ destroy: function() {
+ $.ui.dialog.overlay.destroy(this.$el);
+ }
+});
+
+}(jQuery));
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/draggable.js b/module/web/static/js/libs/jqueryui/draggable.js
new file mode 100644
index 000000000..17163fe9e
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/draggable.js
@@ -0,0 +1,836 @@
+define(['jquery','./core','./mouse','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Draggable 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Draggables
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.draggable", $.ui.mouse, {
+ widgetEventPrefix: "drag",
+ options: {
+ addClasses: true,
+ appendTo: "parent",
+ axis: false,
+ connectToSortable: false,
+ containment: false,
+ cursor: "auto",
+ cursorAt: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ iframeFix: false,
+ opacity: false,
+ refreshPositions: false,
+ revert: false,
+ revertDuration: 500,
+ scope: "default",
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ snap: false,
+ snapMode: "both",
+ snapTolerance: 20,
+ stack: false,
+ zIndex: false
+ },
+ _create: function() {
+
+ if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
+ this.element[0].style.position = 'relative';
+
+ (this.options.addClasses && this.element.addClass("ui-draggable"));
+ (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
+
+ this._mouseInit();
+
+ },
+
+ destroy: function() {
+ if(!this.element.data('draggable')) return;
+ this.element
+ .removeData("draggable")
+ .unbind(".draggable")
+ .removeClass("ui-draggable"
+ + " ui-draggable-dragging"
+ + " ui-draggable-disabled");
+ this._mouseDestroy();
+
+ return this;
+ },
+
+ _mouseCapture: function(event) {
+
+ var o = this.options;
+
+ // among others, prevent a drag on a resizable-handle
+ if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
+ return false;
+
+ //Quit if we're not on a valid handle
+ this.handle = this._getHandle(event);
+ if (!this.handle)
+ return false;
+
+ if ( o.iframeFix ) {
+ $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
+ $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
+ .css({
+ width: this.offsetWidth+"px", height: this.offsetHeight+"px",
+ position: "absolute", opacity: "0.001", zIndex: 1000
+ })
+ .css($(this).offset())
+ .appendTo("body");
+ });
+ }
+
+ return true;
+
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options;
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ this.helper.addClass("ui-draggable-dragging");
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ //If ddmanager is used for droppables, set the global draggable
+ if($.ui.ddmanager)
+ $.ui.ddmanager.current = this;
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Store the helper's css position
+ this.cssPosition = this.helper.css("position");
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.positionAbs = this.element.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ //Generate the original position
+ this.originalPosition = this.position = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Set a containment if given in the options
+ if(o.containment)
+ this._setContainment();
+
+ //Trigger event + callbacks
+ if(this._trigger("start", event) === false) {
+ this._clear();
+ return false;
+ }
+
+ //Recache the helper size
+ this._cacheHelperProportions();
+
+ //Prepare the droppable offsets
+ if ($.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+
+
+ this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
+ if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
+
+ return true;
+ },
+
+ _mouseDrag: function(event, noPropagation) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Call plugins and callbacks and use the resulting position if something is returned
+ if (!noPropagation) {
+ var ui = this._uiHash();
+ if(this._trigger('drag', event, ui) === false) {
+ this._mouseUp({});
+ return false;
+ }
+ this.position = ui.position;
+ }
+
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ //If we are using droppables, inform the manager about the drop
+ var dropped = false;
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
+ dropped = $.ui.ddmanager.drop(this, event);
+
+ //if a drop comes from outside (a sortable)
+ if(this.dropped) {
+ dropped = this.dropped;
+ this.dropped = false;
+ }
+
+ //if the original element is no longer in the DOM don't bother to continue (see #8269)
+ var element = this.element[0], elementInDom = false;
+ while ( element && (element = element.parentNode) ) {
+ if (element == document ) {
+ elementInDom = true;
+ }
+ }
+ if ( !elementInDom && this.options.helper === "original" )
+ return false;
+
+ if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+ var self = this;
+ $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+ if(self._trigger("stop", event) !== false) {
+ self._clear();
+ }
+ });
+ } else {
+ if(this._trigger("stop", event) !== false) {
+ this._clear();
+ }
+ }
+
+ return false;
+ },
+
+ _mouseUp: function(event) {
+ if (this.options.iframeFix === true) {
+ $("div.ui-draggable-iframeFix").each(function() {
+ this.parentNode.removeChild(this);
+ }); //Remove frame helpers
+ }
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
+ if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
+
+ return $.ui.mouse.prototype._mouseUp.call(this, event);
+ },
+
+ cancel: function() {
+
+ if(this.helper.is(".ui-draggable-dragging")) {
+ this._mouseUp({});
+ } else {
+ this._clear();
+ }
+
+ return this;
+
+ },
+
+ _getHandle: function(event) {
+
+ var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
+ $(this.options.handle, this.element)
+ .find("*")
+ .andSelf()
+ .each(function() {
+ if(this == event.target) handle = true;
+ });
+
+ return handle;
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options;
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
+
+ if(!helper.parents('body').length)
+ helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
+
+ if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
+ helper.css("position", "absolute");
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj == 'string') {
+ obj = obj.split(' ');
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ('left' in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ('right' in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ('top' in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ('bottom' in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
+ po = { top: 0, left: 0 };
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition == "relative") {
+ var p = this.element.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.element.css("marginLeft"),10) || 0),
+ top: (parseInt(this.element.css("marginTop"),10) || 0),
+ right: (parseInt(this.element.css("marginRight"),10) || 0),
+ bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var o = this.options;
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
+ o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+ o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+ (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
+ (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+
+ if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
+ var c = $(o.containment);
+ var ce = c[0]; if(!ce) return;
+ var co = c.offset();
+ var over = ($(ce).css("overflow") != 'hidden');
+
+ this.containment = [
+ (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
+ (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
+ (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
+ (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
+ ];
+ this.relative_container = c;
+
+ } else if(o.containment.constructor == Array) {
+ this.containment = o.containment;
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) pos = this.position;
+ var mod = d == "absolute" ? 1 : -1;
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top // The absolute mouse position
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left // The absolute mouse position
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+ var pageX = event.pageX;
+ var pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+ var containment;
+ if(this.containment) {
+ if (this.relative_container){
+ var co = this.relative_container.offset();
+ containment = [ this.containment[0] + co.left,
+ this.containment[1] + co.top,
+ this.containment[2] + co.left,
+ this.containment[3] + co.top ];
+ }
+ else {
+ containment = this.containment;
+ }
+
+ if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
+ if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
+ }
+
+ if(o.grid) {
+ //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
+ var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
+ pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
+ pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY // The absolute mouse position
+ - this.offset.click.top // Click offset (relative to the element)
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX // The absolute mouse position
+ - this.offset.click.left // Click offset (relative to the element)
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _clear: function() {
+ this.helper.removeClass("ui-draggable-dragging");
+ if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
+ //if($.ui.ddmanager) $.ui.ddmanager.current = null;
+ this.helper = null;
+ this.cancelHelperRemoval = false;
+ },
+
+ // From now on bulk stuff - mainly helpers
+
+ _trigger: function(type, event, ui) {
+ ui = ui || this._uiHash();
+ $.ui.plugin.call(this, type, [event, ui]);
+ if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
+ return $.Widget.prototype._trigger.call(this, type, event, ui);
+ },
+
+ plugins: {},
+
+ _uiHash: function(event) {
+ return {
+ helper: this.helper,
+ position: this.position,
+ originalPosition: this.originalPosition,
+ offset: this.positionAbs
+ };
+ }
+
+});
+
+$.extend($.ui.draggable, {
+ version: "1.8.23"
+});
+
+$.ui.plugin.add("draggable", "connectToSortable", {
+ start: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options,
+ uiSortable = $.extend({}, ui, { item: inst.element });
+ inst.sortables = [];
+ $(o.connectToSortable).each(function() {
+ var sortable = $.data(this, 'sortable');
+ if (sortable && !sortable.options.disabled) {
+ inst.sortables.push({
+ instance: sortable,
+ shouldRevert: sortable.options.revert
+ });
+ sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
+ sortable._trigger("activate", event, uiSortable);
+ }
+ });
+
+ },
+ stop: function(event, ui) {
+
+ //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
+ var inst = $(this).data("draggable"),
+ uiSortable = $.extend({}, ui, { item: inst.element });
+
+ $.each(inst.sortables, function() {
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+
+ inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
+ this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
+
+ //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
+ if(this.shouldRevert) this.instance.options.revert = true;
+
+ //Trigger the stop of the sortable
+ this.instance._mouseStop(event);
+
+ this.instance.options.helper = this.instance.options._helper;
+
+ //If the helper has been the original item, restore properties in the sortable
+ if(inst.options.helper == 'original')
+ this.instance.currentItem.css({ top: 'auto', left: 'auto' });
+
+ } else {
+ this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
+ this.instance._trigger("deactivate", event, uiSortable);
+ }
+
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), self = this;
+
+ var checkPos = function(o) {
+ var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
+ var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
+ var itemHeight = o.height, itemWidth = o.width;
+ var itemTop = o.top, itemLeft = o.left;
+
+ return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
+ };
+
+ $.each(inst.sortables, function(i) {
+
+ //Copy over some variables to allow calling the sortable's native _intersectsWith
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+
+ if(this.instance._intersectsWith(this.instance.containerCache)) {
+
+ //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
+ if(!this.instance.isOver) {
+
+ this.instance.isOver = 1;
+ //Now we fake the start of dragging for the sortable instance,
+ //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
+ //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
+ this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
+ this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
+ this.instance.options.helper = function() { return ui.helper[0]; };
+
+ event.target = this.instance.currentItem[0];
+ this.instance._mouseCapture(event, true);
+ this.instance._mouseStart(event, true, true);
+
+ //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
+ this.instance.offset.click.top = inst.offset.click.top;
+ this.instance.offset.click.left = inst.offset.click.left;
+ this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
+ this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
+
+ inst._trigger("toSortable", event);
+ inst.dropped = this.instance.element; //draggable revert needs that
+ //hack so receive/update callbacks work (mostly)
+ inst.currentItem = inst.element;
+ this.instance.fromOutside = inst;
+
+ }
+
+ //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
+ if(this.instance.currentItem) this.instance._mouseDrag(event);
+
+ } else {
+
+ //If it doesn't intersect with the sortable, and it intersected before,
+ //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+ this.instance.cancelHelperRemoval = true;
+
+ //Prevent reverting on this forced stop
+ this.instance.options.revert = false;
+
+ // The out event needs to be triggered independently
+ this.instance._trigger('out', event, this.instance._uiHash(this.instance));
+
+ this.instance._mouseStop(event, true);
+ this.instance.options.helper = this.instance.options._helper;
+
+ //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
+ this.instance.currentItem.remove();
+ if(this.instance.placeholder) this.instance.placeholder.remove();
+
+ inst._trigger("fromSortable", event);
+ inst.dropped = false; //draggable revert needs that
+ }
+
+ };
+
+ });
+
+ }
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+ start: function(event, ui) {
+ var t = $('body'), o = $(this).data('draggable').options;
+ if (t.css("cursor")) o._cursor = t.css("cursor");
+ t.css("cursor", o.cursor);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if (o._cursor) $('body').css("cursor", o._cursor);
+ }
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data('draggable').options;
+ if(t.css("opacity")) o._opacity = t.css("opacity");
+ t.css('opacity', o.opacity);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if(o._opacity) $(ui.helper).css('opacity', o._opacity);
+ }
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+ start: function(event, ui) {
+ var i = $(this).data("draggable");
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
+ },
+ drag: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options, scrolled = false;
+
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
+
+ if(!o.axis || o.axis != 'x') {
+ if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
+ else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
+ else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
+ }
+
+ } else {
+
+ if(!o.axis || o.axis != 'x') {
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+ }
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(i, event);
+
+ }
+});
+
+$.ui.plugin.add("draggable", "snap", {
+ start: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options;
+ i.snapElements = [];
+
+ $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
+ var $t = $(this); var $o = $t.offset();
+ if(this != i.element[0]) i.snapElements.push({
+ item: this,
+ width: $t.outerWidth(), height: $t.outerHeight(),
+ top: $o.top, left: $o.left
+ });
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options;
+ var d = o.snapTolerance;
+
+ var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+ y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+ for (var i = inst.snapElements.length - 1; i >= 0; i--){
+
+ var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
+ t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
+
+ //Yes, I know, this is insane ;)
+ if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
+ if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = false;
+ continue;
+ }
+
+ if(o.snapMode != 'inner') {
+ var ts = Math.abs(t - y2) <= d;
+ var bs = Math.abs(b - y1) <= d;
+ var ls = Math.abs(l - x2) <= d;
+ var rs = Math.abs(r - x1) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
+ }
+
+ var first = (ts || bs || ls || rs);
+
+ if(o.snapMode != 'outer') {
+ var ts = Math.abs(t - y1) <= d;
+ var bs = Math.abs(b - y2) <= d;
+ var ls = Math.abs(l - x1) <= d;
+ var rs = Math.abs(r - x2) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
+ }
+
+ if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
+ (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+ };
+
+ }
+});
+
+$.ui.plugin.add("draggable", "stack", {
+ start: function(event, ui) {
+
+ var o = $(this).data("draggable").options;
+
+ var group = $.makeArray($(o.stack)).sort(function(a,b) {
+ return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
+ });
+ if (!group.length) { return; }
+
+ var min = parseInt(group[0].style.zIndex) || 0;
+ $(group).each(function(i) {
+ this.style.zIndex = min + i;
+ });
+
+ this[0].style.zIndex = min + group.length;
+
+ }
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data("draggable").options;
+ if(t.css("zIndex")) o._zIndex = t.css("zIndex");
+ t.css('zIndex', o.zIndex);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data("draggable").options;
+ if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
+ }
+});
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/droppable.js b/module/web/static/js/libs/jqueryui/droppable.js
new file mode 100644
index 000000000..e16574638
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/droppable.js
@@ -0,0 +1,299 @@
+define(['jquery','./core','./widget','./mouse','./draggable'], function (jQuery) {
+/*!
+ * jQuery UI Droppable 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Droppables
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.mouse.js
+ * jquery.ui.draggable.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.droppable", {
+ widgetEventPrefix: "drop",
+ options: {
+ accept: '*',
+ activeClass: false,
+ addClasses: true,
+ greedy: false,
+ hoverClass: false,
+ scope: 'default',
+ tolerance: 'intersect'
+ },
+ _create: function() {
+
+ var o = this.options, accept = o.accept;
+ this.isover = 0; this.isout = 1;
+
+ this.accept = $.isFunction(accept) ? accept : function(d) {
+ return d.is(accept);
+ };
+
+ //Store the droppable's proportions
+ this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
+
+ // Add the reference and positions to the manager
+ $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
+ $.ui.ddmanager.droppables[o.scope].push(this);
+
+ (o.addClasses && this.element.addClass("ui-droppable"));
+
+ },
+
+ destroy: function() {
+ var drop = $.ui.ddmanager.droppables[this.options.scope];
+ for ( var i = 0; i < drop.length; i++ )
+ if ( drop[i] == this )
+ drop.splice(i, 1);
+
+ this.element
+ .removeClass("ui-droppable ui-droppable-disabled")
+ .removeData("droppable")
+ .unbind(".droppable");
+
+ return this;
+ },
+
+ _setOption: function(key, value) {
+
+ if(key == 'accept') {
+ this.accept = $.isFunction(value) ? value : function(d) {
+ return d.is(value);
+ };
+ }
+ $.Widget.prototype._setOption.apply(this, arguments);
+ },
+
+ _activate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) this.element.addClass(this.options.activeClass);
+ (draggable && this._trigger('activate', event, this.ui(draggable)));
+ },
+
+ _deactivate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
+ (draggable && this._trigger('deactivate', event, this.ui(draggable)));
+ },
+
+ _over: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
+ this._trigger('over', event, this.ui(draggable));
+ }
+
+ },
+
+ _out: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
+ this._trigger('out', event, this.ui(draggable));
+ }
+
+ },
+
+ _drop: function(event,custom) {
+
+ var draggable = custom || $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
+
+ var childrenIntersection = false;
+ this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
+ var inst = $.data(this, 'droppable');
+ if(
+ inst.options.greedy
+ && !inst.options.disabled
+ && inst.options.scope == draggable.options.scope
+ && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
+ && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
+ ) { childrenIntersection = true; return false; }
+ });
+ if(childrenIntersection) return false;
+
+ if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
+ if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
+ this._trigger('drop', event, this.ui(draggable));
+ return this.element;
+ }
+
+ return false;
+
+ },
+
+ ui: function(c) {
+ return {
+ draggable: (c.currentItem || c.element),
+ helper: c.helper,
+ position: c.position,
+ offset: c.positionAbs
+ };
+ }
+
+});
+
+$.extend($.ui.droppable, {
+ version: "1.8.23"
+});
+
+$.ui.intersect = function(draggable, droppable, toleranceMode) {
+
+ if (!droppable.offset) return false;
+
+ var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
+ y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
+ var l = droppable.offset.left, r = l + droppable.proportions.width,
+ t = droppable.offset.top, b = t + droppable.proportions.height;
+
+ switch (toleranceMode) {
+ case 'fit':
+ return (l <= x1 && x2 <= r
+ && t <= y1 && y2 <= b);
+ break;
+ case 'intersect':
+ return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
+ && x2 - (draggable.helperProportions.width / 2) < r // Left Half
+ && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
+ && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
+ break;
+ case 'pointer':
+ var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
+ draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
+ isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
+ return isOver;
+ break;
+ case 'touch':
+ return (
+ (y1 >= t && y1 <= b) || // Top edge touching
+ (y2 >= t && y2 <= b) || // Bottom edge touching
+ (y1 < t && y2 > b) // Surrounded vertically
+ ) && (
+ (x1 >= l && x1 <= r) || // Left edge touching
+ (x2 >= l && x2 <= r) || // Right edge touching
+ (x1 < l && x2 > r) // Surrounded horizontally
+ );
+ break;
+ default:
+ return false;
+ break;
+ }
+
+};
+
+/*
+ This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+ current: null,
+ droppables: { 'default': [] },
+ prepareOffsets: function(t, event) {
+
+ var m = $.ui.ddmanager.droppables[t.options.scope] || [];
+ var type = event ? event.type : null; // workaround for #2317
+ var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
+
+ droppablesLoop: for (var i = 0; i < m.length; i++) {
+
+ if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
+ for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
+ m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
+
+ if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
+
+ m[i].offset = m[i].element.offset();
+ m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
+
+ }
+
+ },
+ drop: function(draggable, event) {
+
+ var dropped = false;
+ $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+ if(!this.options) return;
+ if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
+ dropped = this._drop.call(this, event) || dropped;
+
+ if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ this.isout = 1; this.isover = 0;
+ this._deactivate.call(this, event);
+ }
+
+ });
+ return dropped;
+
+ },
+ dragStart: function( draggable, event ) {
+ //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
+ draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() {
+ if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
+ });
+ },
+ drag: function(draggable, event) {
+
+ //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
+ if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
+
+ //Run through all droppables and check their positions based on specific tolerance options
+ $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+ if(this.options.disabled || this.greedyChild || !this.visible) return;
+ var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
+
+ var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
+ if(!c) return;
+
+ var parentInstance;
+ if (this.options.greedy) {
+ var parent = this.element.parents(':data(droppable):eq(0)');
+ if (parent.length) {
+ parentInstance = $.data(parent[0], 'droppable');
+ parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
+ }
+ }
+
+ // we just moved into a greedy child
+ if (parentInstance && c == 'isover') {
+ parentInstance['isover'] = 0;
+ parentInstance['isout'] = 1;
+ parentInstance._out.call(parentInstance, event);
+ }
+
+ this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
+ this[c == "isover" ? "_over" : "_out"].call(this, event);
+
+ // we just moved out of a greedy child
+ if (parentInstance && c == 'isout') {
+ parentInstance['isout'] = 0;
+ parentInstance['isover'] = 1;
+ parentInstance._over.call(parentInstance, event);
+ }
+ });
+
+ },
+ dragStop: function( draggable, event ) {
+ draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" );
+ //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
+ if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
+ }
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/blind.js b/module/web/static/js/libs/jqueryui/effects/blind.js
new file mode 100644
index 000000000..c00004566
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/blind.js
@@ -0,0 +1,52 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Blind 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Blind
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.blind = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
+ var direction = o.options.direction || 'vertical'; // Default direction
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
+ var ref = (direction == 'vertical') ? 'height' : 'width';
+ var distance = (direction == 'vertical') ? wrapper.height() : wrapper.width();
+ if(mode == 'show') wrapper.css(ref, 0); // Shift
+
+ // Animation
+ var animation = {};
+ animation[ref] = mode == 'show' ? distance : 0;
+
+ // Animate
+ wrapper.animate(animation, o.duration, o.options.easing, function() {
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(el[0], arguments); // Callback
+ el.dequeue();
+ });
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/bounce.js b/module/web/static/js/libs/jqueryui/effects/bounce.js
new file mode 100644
index 000000000..41d1c6885
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/bounce.js
@@ -0,0 +1,81 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Bounce 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Bounce
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.bounce = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
+ var direction = o.options.direction || 'up'; // Default direction
+ var distance = o.options.distance || 20; // Default distance
+ var times = o.options.times || 5; // Default # of times
+ var speed = o.duration || 250; // Default speed per bounce
+ if (/show|hide/.test(mode)) props.push('opacity'); // Avoid touching opacity to prevent clearType and PNG issues in IE
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ $.effects.createWrapper(el); // Create Wrapper
+ var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
+ var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
+ var distance = o.options.distance || (ref == 'top' ? el.outerHeight(true) / 3 : el.outerWidth(true) / 3);
+ if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift
+ if (mode == 'hide') distance = distance / (times * 2);
+ if (mode != 'hide') times--;
+
+ // Animate
+ if (mode == 'show') { // Show Bounce
+ var animation = {opacity: 1};
+ animation[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
+ el.animate(animation, speed / 2, o.options.easing);
+ distance = distance / 2;
+ times--;
+ };
+ for (var i = 0; i < times; i++) { // Bounces
+ var animation1 = {}, animation2 = {};
+ animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
+ animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
+ el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing);
+ distance = (mode == 'hide') ? distance * 2 : distance / 2;
+ };
+ if (mode == 'hide') { // Last Bounce
+ var animation = {opacity: 0};
+ animation[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
+ el.animate(animation, speed / 2, o.options.easing, function(){
+ el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ });
+ } else {
+ var animation1 = {}, animation2 = {};
+ animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
+ animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
+ el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing, function(){
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ });
+ };
+ el.queue('fx', function() { el.dequeue(); });
+ el.dequeue();
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/clip.js b/module/web/static/js/libs/jqueryui/effects/clip.js
new file mode 100644
index 000000000..7ff13d5d1
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/clip.js
@@ -0,0 +1,57 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Clip 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Clip
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.clip = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right','height','width'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
+ var direction = o.options.direction || 'vertical'; // Default direction
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
+ var animate = el[0].tagName == 'IMG' ? wrapper : el;
+ var ref = {
+ size: (direction == 'vertical') ? 'height' : 'width',
+ position: (direction == 'vertical') ? 'top' : 'left'
+ };
+ var distance = (direction == 'vertical') ? animate.height() : animate.width();
+ if(mode == 'show') { animate.css(ref.size, 0); animate.css(ref.position, distance / 2); } // Shift
+
+ // Animation
+ var animation = {};
+ animation[ref.size] = mode == 'show' ? distance : 0;
+ animation[ref.position] = mode == 'show' ? 0 : distance / 2;
+
+ // Animate
+ animate.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(el[0], arguments); // Callback
+ el.dequeue();
+ }});
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/core.js b/module/web/static/js/libs/jqueryui/effects/core.js
new file mode 100644
index 000000000..9900118aa
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/core.js
@@ -0,0 +1,615 @@
+define(['jquery'], function (jQuery) {
+/*!
+ * jQuery UI Effects 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/
+ */
+;jQuery.effects || (function($, undefined) {
+
+$.effects = {};
+
+
+
+/******************************************************************************/
+/****************************** COLOR ANIMATIONS ******************************/
+/******************************************************************************/
+
+// override the animation for color styles
+$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor',
+ 'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'],
+function(i, attr) {
+ $.fx.step[attr] = function(fx) {
+ if (!fx.colorInit) {
+ fx.start = getColor(fx.elem, attr);
+ fx.end = getRGB(fx.end);
+ fx.colorInit = true;
+ }
+
+ fx.elem.style[attr] = 'rgb(' +
+ Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' +
+ Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' +
+ Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')';
+ };
+});
+
+// Color Conversion functions from highlightFade
+// By Blair Mitchelmore
+// http://jquery.offput.ca/highlightFade/
+
+// Parse strings looking for color tuples [255,255,255]
+function getRGB(color) {
+ var result;
+
+ // Check if we're already dealing with an array of colors
+ if ( color && color.constructor == Array && color.length == 3 )
+ return color;
+
+ // Look for rgb(num,num,num)
+ if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
+ return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)];
+
+ // Look for rgb(num%,num%,num%)
+ if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
+ return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];
+
+ // Look for #a0b1c2
+ if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
+ return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];
+
+ // Look for #fff
+ if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
+ return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];
+
+ // Look for rgba(0, 0, 0, 0) == transparent in Safari 3
+ if (result = /rgba\(0, 0, 0, 0\)/.exec(color))
+ return colors['transparent'];
+
+ // Otherwise, we're most likely dealing with a named color
+ return colors[$.trim(color).toLowerCase()];
+}
+
+function getColor(elem, attr) {
+ var color;
+
+ do {
+ // jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css
+ color = ($.curCSS || $.css)(elem, attr);
+
+ // Keep going until we find an element that has color, or we hit the body
+ if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") )
+ break;
+
+ attr = "backgroundColor";
+ } while ( elem = elem.parentNode );
+
+ return getRGB(color);
+};
+
+// Some named colors to work with
+// From Interface by Stefan Petre
+// http://interface.eyecon.ro/
+
+var colors = {
+ aqua:[0,255,255],
+ azure:[240,255,255],
+ beige:[245,245,220],
+ black:[0,0,0],
+ blue:[0,0,255],
+ brown:[165,42,42],
+ cyan:[0,255,255],
+ darkblue:[0,0,139],
+ darkcyan:[0,139,139],
+ darkgrey:[169,169,169],
+ darkgreen:[0,100,0],
+ darkkhaki:[189,183,107],
+ darkmagenta:[139,0,139],
+ darkolivegreen:[85,107,47],
+ darkorange:[255,140,0],
+ darkorchid:[153,50,204],
+ darkred:[139,0,0],
+ darksalmon:[233,150,122],
+ darkviolet:[148,0,211],
+ fuchsia:[255,0,255],
+ gold:[255,215,0],
+ green:[0,128,0],
+ indigo:[75,0,130],
+ khaki:[240,230,140],
+ lightblue:[173,216,230],
+ lightcyan:[224,255,255],
+ lightgreen:[144,238,144],
+ lightgrey:[211,211,211],
+ lightpink:[255,182,193],
+ lightyellow:[255,255,224],
+ lime:[0,255,0],
+ magenta:[255,0,255],
+ maroon:[128,0,0],
+ navy:[0,0,128],
+ olive:[128,128,0],
+ orange:[255,165,0],
+ pink:[255,192,203],
+ purple:[128,0,128],
+ violet:[128,0,128],
+ red:[255,0,0],
+ silver:[192,192,192],
+ white:[255,255,255],
+ yellow:[255,255,0],
+ transparent: [255,255,255]
+};
+
+
+
+/******************************************************************************/
+/****************************** CLASS ANIMATIONS ******************************/
+/******************************************************************************/
+
+var classAnimationActions = ['add', 'remove', 'toggle'],
+ shorthandStyles = {
+ border: 1,
+ borderBottom: 1,
+ borderColor: 1,
+ borderLeft: 1,
+ borderRight: 1,
+ borderTop: 1,
+ borderWidth: 1,
+ margin: 1,
+ padding: 1
+ };
+
+function getElementStyles() {
+ var style = document.defaultView
+ ? document.defaultView.getComputedStyle(this, null)
+ : this.currentStyle,
+ newStyle = {},
+ key,
+ camelCase;
+
+ // webkit enumerates style porperties
+ if (style && style.length && style[0] && style[style[0]]) {
+ var len = style.length;
+ while (len--) {
+ key = style[len];
+ if (typeof style[key] == 'string') {
+ camelCase = key.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+ newStyle[camelCase] = style[key];
+ }
+ }
+ } else {
+ for (key in style) {
+ if (typeof style[key] === 'string') {
+ newStyle[key] = style[key];
+ }
+ }
+ }
+
+ return newStyle;
+}
+
+function filterStyles(styles) {
+ var name, value;
+ for (name in styles) {
+ value = styles[name];
+ if (
+ // ignore null and undefined values
+ value == null ||
+ // ignore functions (when does this occur?)
+ $.isFunction(value) ||
+ // shorthand styles that need to be expanded
+ name in shorthandStyles ||
+ // ignore scrollbars (break in IE)
+ (/scrollbar/).test(name) ||
+
+ // only colors or values that can be converted to numbers
+ (!(/color/i).test(name) && isNaN(parseFloat(value)))
+ ) {
+ delete styles[name];
+ }
+ }
+
+ return styles;
+}
+
+function styleDifference(oldStyle, newStyle) {
+ var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459
+ name;
+
+ for (name in newStyle) {
+ if (oldStyle[name] != newStyle[name]) {
+ diff[name] = newStyle[name];
+ }
+ }
+
+ return diff;
+}
+
+$.effects.animateClass = function(value, duration, easing, callback) {
+ if ($.isFunction(easing)) {
+ callback = easing;
+ easing = null;
+ }
+
+ return this.queue(function() {
+ var that = $(this),
+ originalStyleAttr = that.attr('style') || ' ',
+ originalStyle = filterStyles(getElementStyles.call(this)),
+ newStyle,
+ className = that.attr('class') || "";
+
+ $.each(classAnimationActions, function(i, action) {
+ if (value[action]) {
+ that[action + 'Class'](value[action]);
+ }
+ });
+ newStyle = filterStyles(getElementStyles.call(this));
+ that.attr('class', className);
+
+ that.animate(styleDifference(originalStyle, newStyle), {
+ queue: false,
+ duration: duration,
+ easing: easing,
+ complete: function() {
+ $.each(classAnimationActions, function(i, action) {
+ if (value[action]) { that[action + 'Class'](value[action]); }
+ });
+ // work around bug in IE by clearing the cssText before setting it
+ if (typeof that.attr('style') == 'object') {
+ that.attr('style').cssText = '';
+ that.attr('style').cssText = originalStyleAttr;
+ } else {
+ that.attr('style', originalStyleAttr);
+ }
+ if (callback) { callback.apply(this, arguments); }
+ $.dequeue( this );
+ }
+ });
+ });
+};
+
+$.fn.extend({
+ _addClass: $.fn.addClass,
+ addClass: function(classNames, speed, easing, callback) {
+ return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames);
+ },
+
+ _removeClass: $.fn.removeClass,
+ removeClass: function(classNames,speed,easing,callback) {
+ return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames);
+ },
+
+ _toggleClass: $.fn.toggleClass,
+ toggleClass: function(classNames, force, speed, easing, callback) {
+ if ( typeof force == "boolean" || force === undefined ) {
+ if ( !speed ) {
+ // without speed parameter;
+ return this._toggleClass(classNames, force);
+ } else {
+ return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]);
+ }
+ } else {
+ // without switch parameter;
+ return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]);
+ }
+ },
+
+ switchClass: function(remove,add,speed,easing,callback) {
+ return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]);
+ }
+});
+
+
+
+/******************************************************************************/
+/*********************************** EFFECTS **********************************/
+/******************************************************************************/
+
+$.extend($.effects, {
+ version: "1.8.23",
+
+ // Saves a set of properties in a data storage
+ save: function(element, set) {
+ for(var i=0; i < set.length; i++) {
+ if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]);
+ }
+ },
+
+ // Restores a set of previously saved properties from a data storage
+ restore: function(element, set) {
+ for(var i=0; i < set.length; i++) {
+ if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i]));
+ }
+ },
+
+ setMode: function(el, mode) {
+ if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle
+ return mode;
+ },
+
+ getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value
+ // this should be a little more flexible in the future to handle a string & hash
+ var y, x;
+ switch (origin[0]) {
+ case 'top': y = 0; break;
+ case 'middle': y = 0.5; break;
+ case 'bottom': y = 1; break;
+ default: y = origin[0] / original.height;
+ };
+ switch (origin[1]) {
+ case 'left': x = 0; break;
+ case 'center': x = 0.5; break;
+ case 'right': x = 1; break;
+ default: x = origin[1] / original.width;
+ };
+ return {x: x, y: y};
+ },
+
+ // Wraps the element around a wrapper that copies position properties
+ createWrapper: function(element) {
+
+ // if the element is already wrapped, return it
+ if (element.parent().is('.ui-effects-wrapper')) {
+ return element.parent();
+ }
+
+ // wrap the element
+ var props = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ 'float': element.css('float')
+ },
+ wrapper = $('<div></div>')
+ .addClass('ui-effects-wrapper')
+ .css({
+ fontSize: '100%',
+ background: 'transparent',
+ border: 'none',
+ margin: 0,
+ padding: 0
+ }),
+ active = document.activeElement;
+
+ // support: Firefox
+ // Firefox incorrectly exposes anonymous content
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=561664
+ try {
+ active.id;
+ } catch( e ) {
+ active = document.body;
+ }
+
+ element.wrap( wrapper );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+
+ wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
+
+ // transfer positioning properties to the wrapper
+ if (element.css('position') == 'static') {
+ wrapper.css({ position: 'relative' });
+ element.css({ position: 'relative' });
+ } else {
+ $.extend(props, {
+ position: element.css('position'),
+ zIndex: element.css('z-index')
+ });
+ $.each(['top', 'left', 'bottom', 'right'], function(i, pos) {
+ props[pos] = element.css(pos);
+ if (isNaN(parseInt(props[pos], 10))) {
+ props[pos] = 'auto';
+ }
+ });
+ element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' });
+ }
+
+ return wrapper.css(props).show();
+ },
+
+ removeWrapper: function(element) {
+ var parent,
+ active = document.activeElement;
+
+ if (element.parent().is('.ui-effects-wrapper')) {
+ parent = element.parent().replaceWith(element);
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+ return parent;
+ }
+
+ return element;
+ },
+
+ setTransition: function(element, list, factor, value) {
+ value = value || {};
+ $.each(list, function(i, x){
+ var unit = element.cssUnit(x);
+ if (unit[0] > 0) value[x] = unit[0] * factor + unit[1];
+ });
+ return value;
+ }
+});
+
+
+function _normalizeArguments(effect, options, speed, callback) {
+ // shift params for method overloading
+ if (typeof effect == 'object') {
+ callback = options;
+ speed = null;
+ options = effect;
+ effect = options.effect;
+ }
+ if ($.isFunction(options)) {
+ callback = options;
+ speed = null;
+ options = {};
+ }
+ if (typeof options == 'number' || $.fx.speeds[options]) {
+ callback = speed;
+ speed = options;
+ options = {};
+ }
+ if ($.isFunction(speed)) {
+ callback = speed;
+ speed = null;
+ }
+
+ options = options || {};
+
+ speed = speed || options.duration;
+ speed = $.fx.off ? 0 : typeof speed == 'number'
+ ? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default;
+
+ callback = callback || options.complete;
+
+ return [effect, options, speed, callback];
+}
+
+function standardSpeed( speed ) {
+ // valid standard speeds
+ if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
+ return true;
+ }
+
+ // invalid strings - treat as "normal" speed
+ if ( typeof speed === "string" && !$.effects[ speed ] ) {
+ return true;
+ }
+
+ return false;
+}
+
+$.fn.extend({
+ effect: function(effect, options, speed, callback) {
+ var args = _normalizeArguments.apply(this, arguments),
+ // TODO: make effects take actual parameters instead of a hash
+ args2 = {
+ options: args[1],
+ duration: args[2],
+ callback: args[3]
+ },
+ mode = args2.options.mode,
+ effectMethod = $.effects[effect];
+
+ if ( $.fx.off || !effectMethod ) {
+ // delegate to the original method (e.g., .show()) if possible
+ if ( mode ) {
+ return this[ mode ]( args2.duration, args2.callback );
+ } else {
+ return this.each(function() {
+ if ( args2.callback ) {
+ args2.callback.call( this );
+ }
+ });
+ }
+ }
+
+ return effectMethod.call(this, args2);
+ },
+
+ _show: $.fn.show,
+ show: function(speed) {
+ if ( standardSpeed( speed ) ) {
+ return this._show.apply(this, arguments);
+ } else {
+ var args = _normalizeArguments.apply(this, arguments);
+ args[1].mode = 'show';
+ return this.effect.apply(this, args);
+ }
+ },
+
+ _hide: $.fn.hide,
+ hide: function(speed) {
+ if ( standardSpeed( speed ) ) {
+ return this._hide.apply(this, arguments);
+ } else {
+ var args = _normalizeArguments.apply(this, arguments);
+ args[1].mode = 'hide';
+ return this.effect.apply(this, args);
+ }
+ },
+
+ // jQuery core overloads toggle and creates _toggle
+ __toggle: $.fn.toggle,
+ toggle: function(speed) {
+ if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
+ return this.__toggle.apply(this, arguments);
+ } else {
+ var args = _normalizeArguments.apply(this, arguments);
+ args[1].mode = 'toggle';
+ return this.effect.apply(this, args);
+ }
+ },
+
+ // helper functions
+ cssUnit: function(key) {
+ var style = this.css(key), val = [];
+ $.each( ['em','px','%','pt'], function(i, unit){
+ if(style.indexOf(unit) > 0)
+ val = [parseFloat(style), unit];
+ });
+ return val;
+ }
+});
+
+
+
+/******************************************************************************/
+/*********************************** EASING ***********************************/
+/******************************************************************************/
+
+// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
+
+var baseEasings = {};
+
+$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
+ baseEasings[ name ] = function( p ) {
+ return Math.pow( p, i + 2 );
+ };
+});
+
+$.extend( baseEasings, {
+ Sine: function ( p ) {
+ return 1 - Math.cos( p * Math.PI / 2 );
+ },
+ Circ: function ( p ) {
+ return 1 - Math.sqrt( 1 - p * p );
+ },
+ Elastic: function( p ) {
+ return p === 0 || p === 1 ? p :
+ -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
+ },
+ Back: function( p ) {
+ return p * p * ( 3 * p - 2 );
+ },
+ Bounce: function ( p ) {
+ var pow2,
+ bounce = 4;
+
+ while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
+ return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
+ }
+});
+
+$.each( baseEasings, function( name, easeIn ) {
+ $.easing[ "easeIn" + name ] = easeIn;
+ $.easing[ "easeOut" + name ] = function( p ) {
+ return 1 - easeIn( 1 - p );
+ };
+ $.easing[ "easeInOut" + name ] = function( p ) {
+ return p < .5 ?
+ easeIn( p * 2 ) / 2 :
+ easeIn( p * -2 + 2 ) / -2 + 1;
+ };
+});
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/drop.js b/module/web/static/js/libs/jqueryui/effects/drop.js
new file mode 100644
index 000000000..2ee4cf6d2
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/drop.js
@@ -0,0 +1,53 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Drop 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Drop
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.drop = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right','opacity'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
+ var direction = o.options.direction || 'left'; // Default Direction
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ $.effects.createWrapper(el); // Create Wrapper
+ var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
+ var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
+ var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) / 2 : el.outerWidth( true ) / 2);
+ if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift
+
+ // Animation
+ var animation = {opacity: mode == 'show' ? 1 : 0};
+ animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance;
+
+ // Animate
+ el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ el.dequeue();
+ }});
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/explode.js b/module/web/static/js/libs/jqueryui/effects/explode.js
new file mode 100644
index 000000000..e7f3024a7
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/explode.js
@@ -0,0 +1,82 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Explode 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Explode
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.explode = function(o) {
+
+ return this.queue(function() {
+
+ var rows = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3;
+ var cells = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3;
+
+ o.options.mode = o.options.mode == 'toggle' ? ($(this).is(':visible') ? 'hide' : 'show') : o.options.mode;
+ var el = $(this).show().css('visibility', 'hidden');
+ var offset = el.offset();
+
+ //Substract the margins - not fixing the problem yet.
+ offset.top -= parseInt(el.css("marginTop"),10) || 0;
+ offset.left -= parseInt(el.css("marginLeft"),10) || 0;
+
+ var width = el.outerWidth(true);
+ var height = el.outerHeight(true);
+
+ for(var i=0;i<rows;i++) { // =
+ for(var j=0;j<cells;j++) { // ||
+ el
+ .clone()
+ .appendTo('body')
+ .wrap('<div></div>')
+ .css({
+ position: 'absolute',
+ visibility: 'visible',
+ left: -j*(width/cells),
+ top: -i*(height/rows)
+ })
+ .parent()
+ .addClass('ui-effects-explode')
+ .css({
+ position: 'absolute',
+ overflow: 'hidden',
+ width: width/cells,
+ height: height/rows,
+ left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? (j-Math.floor(cells/2))*(width/cells) : 0),
+ top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? (i-Math.floor(rows/2))*(height/rows) : 0),
+ opacity: o.options.mode == 'show' ? 0 : 1
+ }).animate({
+ left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? 0 : (j-Math.floor(cells/2))*(width/cells)),
+ top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? 0 : (i-Math.floor(rows/2))*(height/rows)),
+ opacity: o.options.mode == 'show' ? 1 : 0
+ }, o.duration || 500);
+ }
+ }
+
+ // Set a timeout, to call the callback approx. when the other animations have finished
+ setTimeout(function() {
+
+ o.options.mode == 'show' ? el.css({ visibility: 'visible' }) : el.css({ visibility: 'visible' }).hide();
+ if(o.callback) o.callback.apply(el[0]); // Callback
+ el.dequeue();
+
+ $('div.ui-effects-explode').remove();
+
+ }, o.duration || 500);
+
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/fade.js b/module/web/static/js/libs/jqueryui/effects/fade.js
new file mode 100644
index 000000000..c31973c74
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/fade.js
@@ -0,0 +1,35 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Fade 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Fade
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.fade = function(o) {
+ return this.queue(function() {
+ var elem = $(this),
+ mode = $.effects.setMode(elem, o.options.mode || 'hide');
+
+ elem.animate({ opacity: mode }, {
+ queue: false,
+ duration: o.duration,
+ easing: o.options.easing,
+ complete: function() {
+ (o.callback && o.callback.apply(this, arguments));
+ elem.dequeue();
+ }
+ });
+ });
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/fold.js b/module/web/static/js/libs/jqueryui/effects/fold.js
new file mode 100644
index 000000000..540c77c16
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/fold.js
@@ -0,0 +1,59 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Fold 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Fold
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.fold = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
+ var size = o.options.size || 15; // Default fold size
+ var horizFirst = !(!o.options.horizFirst); // Ensure a boolean value
+ var duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2;
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
+ var widthFirst = ((mode == 'show') != horizFirst);
+ var ref = widthFirst ? ['width', 'height'] : ['height', 'width'];
+ var distance = widthFirst ? [wrapper.width(), wrapper.height()] : [wrapper.height(), wrapper.width()];
+ var percent = /([0-9]+)%/.exec(size);
+ if(percent) size = parseInt(percent[1],10) / 100 * distance[mode == 'hide' ? 0 : 1];
+ if(mode == 'show') wrapper.css(horizFirst ? {height: 0, width: size} : {height: size, width: 0}); // Shift
+
+ // Animation
+ var animation1 = {}, animation2 = {};
+ animation1[ref[0]] = mode == 'show' ? distance[0] : size;
+ animation2[ref[1]] = mode == 'show' ? distance[1] : 0;
+
+ // Animate
+ wrapper.animate(animation1, duration, o.options.easing)
+ .animate(animation2, duration, o.options.easing, function() {
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(el[0], arguments); // Callback
+ el.dequeue();
+ });
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/highlight.js b/module/web/static/js/libs/jqueryui/effects/highlight.js
new file mode 100644
index 000000000..f7a2f2bcd
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/highlight.js
@@ -0,0 +1,53 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Highlight 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Highlight
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.highlight = function(o) {
+ return this.queue(function() {
+ var elem = $(this),
+ props = ['backgroundImage', 'backgroundColor', 'opacity'],
+ mode = $.effects.setMode(elem, o.options.mode || 'show'),
+ animation = {
+ backgroundColor: elem.css('backgroundColor')
+ };
+
+ if (mode == 'hide') {
+ animation.opacity = 0;
+ }
+
+ $.effects.save(elem, props);
+ elem
+ .show()
+ .css({
+ backgroundImage: 'none',
+ backgroundColor: o.options.color || '#ffff99'
+ })
+ .animate(animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.options.easing,
+ complete: function() {
+ (mode == 'hide' && elem.hide());
+ $.effects.restore(elem, props);
+ (mode == 'show' && !$.support.opacity && this.style.removeAttribute('filter'));
+ (o.callback && o.callback.apply(this, arguments));
+ elem.dequeue();
+ }
+ });
+ });
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/pulsate.js b/module/web/static/js/libs/jqueryui/effects/pulsate.js
new file mode 100644
index 000000000..95105801c
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/pulsate.js
@@ -0,0 +1,54 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Pulsate 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Pulsate
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.pulsate = function(o) {
+ return this.queue(function() {
+ var elem = $(this),
+ mode = $.effects.setMode(elem, o.options.mode || 'show'),
+ times = ((o.options.times || 5) * 2) - 1,
+ duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2,
+ isVisible = elem.is(':visible'),
+ animateTo = 0;
+
+ if (!isVisible) {
+ elem.css('opacity', 0).show();
+ animateTo = 1;
+ }
+
+ if ((mode == 'hide' && isVisible) || (mode == 'show' && !isVisible)) {
+ times--;
+ }
+
+ for (var i = 0; i < times; i++) {
+ elem.animate({ opacity: animateTo }, duration, o.options.easing);
+ animateTo = (animateTo + 1) % 2;
+ }
+
+ elem.animate({ opacity: animateTo }, duration, o.options.easing, function() {
+ if (animateTo == 0) {
+ elem.hide();
+ }
+ (o.callback && o.callback.apply(this, arguments));
+ });
+
+ elem
+ .queue('fx', function() { elem.dequeue(); })
+ .dequeue();
+ });
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/scale.js b/module/web/static/js/libs/jqueryui/effects/scale.js
new file mode 100644
index 000000000..1f31ed36e
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/scale.js
@@ -0,0 +1,181 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Scale 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Scale
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.puff = function(o) {
+ return this.queue(function() {
+ var elem = $(this),
+ mode = $.effects.setMode(elem, o.options.mode || 'hide'),
+ percent = parseInt(o.options.percent, 10) || 150,
+ factor = percent / 100,
+ original = { height: elem.height(), width: elem.width() };
+
+ $.extend(o.options, {
+ fade: true,
+ mode: mode,
+ percent: mode == 'hide' ? percent : 100,
+ from: mode == 'hide'
+ ? original
+ : {
+ height: original.height * factor,
+ width: original.width * factor
+ }
+ });
+
+ elem.effect('scale', o.options, o.duration, o.callback);
+ elem.dequeue();
+ });
+};
+
+$.effects.scale = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this);
+
+ // Set options
+ var options = $.extend(true, {}, o.options);
+ var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
+ var percent = parseInt(o.options.percent,10) || (parseInt(o.options.percent,10) == 0 ? 0 : (mode == 'hide' ? 0 : 100)); // Set default scaling percent
+ var direction = o.options.direction || 'both'; // Set default axis
+ var origin = o.options.origin; // The origin of the scaling
+ if (mode != 'effect') { // Set default origin and restore for show/hide
+ options.origin = origin || ['middle','center'];
+ options.restore = true;
+ }
+ var original = {height: el.height(), width: el.width()}; // Save original
+ el.from = o.options.from || (mode == 'show' ? {height: 0, width: 0} : original); // Default from state
+
+ // Adjust
+ var factor = { // Set scaling factor
+ y: direction != 'horizontal' ? (percent / 100) : 1,
+ x: direction != 'vertical' ? (percent / 100) : 1
+ };
+ el.to = {height: original.height * factor.y, width: original.width * factor.x}; // Set to state
+
+ if (o.options.fade) { // Fade option to support puff
+ if (mode == 'show') {el.from.opacity = 0; el.to.opacity = 1;};
+ if (mode == 'hide') {el.from.opacity = 1; el.to.opacity = 0;};
+ };
+
+ // Animation
+ options.from = el.from; options.to = el.to; options.mode = mode;
+
+ // Animate
+ el.effect('size', options, o.duration, o.callback);
+ el.dequeue();
+ });
+
+};
+
+$.effects.size = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right','width','height','overflow','opacity'];
+ var props1 = ['position','top','bottom','left','right','overflow','opacity']; // Always restore
+ var props2 = ['width','height','overflow']; // Copy for children
+ var cProps = ['fontSize'];
+ var vProps = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom'];
+ var hProps = ['borderLeftWidth', 'borderRightWidth', 'paddingLeft', 'paddingRight'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
+ var restore = o.options.restore || false; // Default restore
+ var scale = o.options.scale || 'both'; // Default scale mode
+ var origin = o.options.origin; // The origin of the sizing
+ var original = {height: el.height(), width: el.width()}; // Save original
+ el.from = o.options.from || original; // Default from state
+ el.to = o.options.to || original; // Default to state
+ // Adjust
+ if (origin) { // Calculate baseline shifts
+ var baseline = $.effects.getBaseline(origin, original);
+ el.from.top = (original.height - el.from.height) * baseline.y;
+ el.from.left = (original.width - el.from.width) * baseline.x;
+ el.to.top = (original.height - el.to.height) * baseline.y;
+ el.to.left = (original.width - el.to.width) * baseline.x;
+ };
+ var factor = { // Set scaling factor
+ from: {y: el.from.height / original.height, x: el.from.width / original.width},
+ to: {y: el.to.height / original.height, x: el.to.width / original.width}
+ };
+ if (scale == 'box' || scale == 'both') { // Scale the css box
+ if (factor.from.y != factor.to.y) { // Vertical props scaling
+ props = props.concat(vProps);
+ el.from = $.effects.setTransition(el, vProps, factor.from.y, el.from);
+ el.to = $.effects.setTransition(el, vProps, factor.to.y, el.to);
+ };
+ if (factor.from.x != factor.to.x) { // Horizontal props scaling
+ props = props.concat(hProps);
+ el.from = $.effects.setTransition(el, hProps, factor.from.x, el.from);
+ el.to = $.effects.setTransition(el, hProps, factor.to.x, el.to);
+ };
+ };
+ if (scale == 'content' || scale == 'both') { // Scale the content
+ if (factor.from.y != factor.to.y) { // Vertical props scaling
+ props = props.concat(cProps);
+ el.from = $.effects.setTransition(el, cProps, factor.from.y, el.from);
+ el.to = $.effects.setTransition(el, cProps, factor.to.y, el.to);
+ };
+ };
+ $.effects.save(el, restore ? props : props1); el.show(); // Save & Show
+ $.effects.createWrapper(el); // Create Wrapper
+ el.css('overflow','hidden').css(el.from); // Shift
+
+ // Animate
+ if (scale == 'content' || scale == 'both') { // Scale the children
+ vProps = vProps.concat(['marginTop','marginBottom']).concat(cProps); // Add margins/font-size
+ hProps = hProps.concat(['marginLeft','marginRight']); // Add margins
+ props2 = props.concat(vProps).concat(hProps); // Concat
+ el.find("*[width]").each(function(){
+ var child = $(this);
+ if (restore) $.effects.save(child, props2);
+ var c_original = {height: child.height(), width: child.width()}; // Save original
+ child.from = {height: c_original.height * factor.from.y, width: c_original.width * factor.from.x};
+ child.to = {height: c_original.height * factor.to.y, width: c_original.width * factor.to.x};
+ if (factor.from.y != factor.to.y) { // Vertical props scaling
+ child.from = $.effects.setTransition(child, vProps, factor.from.y, child.from);
+ child.to = $.effects.setTransition(child, vProps, factor.to.y, child.to);
+ };
+ if (factor.from.x != factor.to.x) { // Horizontal props scaling
+ child.from = $.effects.setTransition(child, hProps, factor.from.x, child.from);
+ child.to = $.effects.setTransition(child, hProps, factor.to.x, child.to);
+ };
+ child.css(child.from); // Shift children
+ child.animate(child.to, o.duration, o.options.easing, function(){
+ if (restore) $.effects.restore(child, props2); // Restore children
+ }); // Animate children
+ });
+ };
+
+ // Animate
+ el.animate(el.to, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
+ if (el.to.opacity === 0) {
+ el.css('opacity', el.from.opacity);
+ }
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, restore ? props : props1); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ el.dequeue();
+ }});
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/shake.js b/module/web/static/js/libs/jqueryui/effects/shake.js
new file mode 100644
index 000000000..9f615050b
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/shake.js
@@ -0,0 +1,60 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Shake 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Shake
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.shake = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
+ var direction = o.options.direction || 'left'; // Default direction
+ var distance = o.options.distance || 20; // Default distance
+ var times = o.options.times || 3; // Default # of times
+ var speed = o.duration || o.options.duration || 140; // Default speed per shake
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ $.effects.createWrapper(el); // Create Wrapper
+ var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
+ var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
+
+ // Animation
+ var animation = {}, animation1 = {}, animation2 = {};
+ animation[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
+ animation1[ref] = (motion == 'pos' ? '+=' : '-=') + distance * 2;
+ animation2[ref] = (motion == 'pos' ? '-=' : '+=') + distance * 2;
+
+ // Animate
+ el.animate(animation, speed, o.options.easing);
+ for (var i = 1; i < times; i++) { // Shakes
+ el.animate(animation1, speed, o.options.easing).animate(animation2, speed, o.options.easing);
+ };
+ el.animate(animation1, speed, o.options.easing).
+ animate(animation, speed / 2, o.options.easing, function(){ // Last shake
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ });
+ el.queue('fx', function() { el.dequeue(); });
+ el.dequeue();
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/slide.js b/module/web/static/js/libs/jqueryui/effects/slide.js
new file mode 100644
index 000000000..d39877fa9
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/slide.js
@@ -0,0 +1,53 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Slide 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Slide
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.slide = function(o) {
+
+ return this.queue(function() {
+
+ // Create element
+ var el = $(this), props = ['position','top','bottom','left','right'];
+
+ // Set options
+ var mode = $.effects.setMode(el, o.options.mode || 'show'); // Set Mode
+ var direction = o.options.direction || 'left'; // Default Direction
+
+ // Adjust
+ $.effects.save(el, props); el.show(); // Save & Show
+ $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
+ var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
+ var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
+ var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) : el.outerWidth( true ));
+ if (mode == 'show') el.css(ref, motion == 'pos' ? (isNaN(distance) ? "-" + distance : -distance) : distance); // Shift
+
+ // Animation
+ var animation = {};
+ animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance;
+
+ // Animate
+ el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
+ if(mode == 'hide') el.hide(); // Hide
+ $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
+ if(o.callback) o.callback.apply(this, arguments); // Callback
+ el.dequeue();
+ }});
+
+ });
+
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/effects/transfer.js b/module/web/static/js/libs/jqueryui/effects/transfer.js
new file mode 100644
index 000000000..5704255ab
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/effects/transfer.js
@@ -0,0 +1,48 @@
+define(['jquery','./effects/core'], function (jQuery) {
+/*!
+ * jQuery UI Effects Transfer 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Transfer
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function( $, undefined ) {
+
+$.effects.transfer = function(o) {
+ return this.queue(function() {
+ var elem = $(this),
+ target = $(o.options.to),
+ endPosition = target.offset(),
+ animation = {
+ top: endPosition.top,
+ left: endPosition.left,
+ height: target.innerHeight(),
+ width: target.innerWidth()
+ },
+ startPosition = elem.offset(),
+ transfer = $('<div class="ui-effects-transfer"></div>')
+ .appendTo(document.body)
+ .addClass(o.options.className)
+ .css({
+ top: startPosition.top,
+ left: startPosition.left,
+ height: elem.innerHeight(),
+ width: elem.innerWidth(),
+ position: 'absolute'
+ })
+ .animate(animation, o.duration, o.options.easing, function() {
+ transfer.remove();
+ (o.callback && o.callback.apply(elem[0], arguments));
+ elem.dequeue();
+ });
+ });
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/mouse.js b/module/web/static/js/libs/jqueryui/mouse.js
new file mode 100644
index 000000000..e2f1695bc
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/mouse.js
@@ -0,0 +1,170 @@
+define(['jquery','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Mouse 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Mouse
+ *
+ * Depends:
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function( e ) {
+ mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+ options: {
+ cancel: ':input,option',
+ distance: 1,
+ delay: 0
+ },
+ _mouseInit: function() {
+ var self = this;
+
+ this.element
+ .bind('mousedown.'+this.widgetName, function(event) {
+ return self._mouseDown(event);
+ })
+ .bind('click.'+this.widgetName, function(event) {
+ if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, self.widgetName + '.preventClickEvent');
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+
+ this.started = false;
+ },
+
+ // TODO: make sure destroying one instance of mouse doesn't mess with
+ // other instances of mouse
+ _mouseDestroy: function() {
+ this.element.unbind('.'+this.widgetName);
+ if ( this._mouseMoveDelegate ) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+ }
+ },
+
+ _mouseDown: function(event) {
+ // don't let more than one widget handle mouseStart
+ if( mouseHandled ) { return };
+
+ // we may have missed mouseup (out of window)
+ (this._mouseStarted && this._mouseUp(event));
+
+ this._mouseDownEvent = event;
+
+ var self = this,
+ btnIsLeft = (event.which == 1),
+ // event.target.nodeName works around a bug in IE 8 with
+ // disabled inputs (#7620)
+ elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+ if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+ return true;
+ }
+
+ this.mouseDelayMet = !this.options.delay;
+ if (!this.mouseDelayMet) {
+ this._mouseDelayTimer = setTimeout(function() {
+ self.mouseDelayMet = true;
+ }, this.options.delay);
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted = (this._mouseStart(event) !== false);
+ if (!this._mouseStarted) {
+ event.preventDefault();
+ return true;
+ }
+ }
+
+ // Click event may never have fired (Gecko & Opera)
+ if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, this.widgetName + '.preventClickEvent');
+ }
+
+ // these delegates are required to keep context
+ this._mouseMoveDelegate = function(event) {
+ return self._mouseMove(event);
+ };
+ this._mouseUpDelegate = function(event) {
+ return self._mouseUp(event);
+ };
+ $(document)
+ .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ event.preventDefault();
+
+ mouseHandled = true;
+ return true;
+ },
+
+ _mouseMove: function(event) {
+ // IE mouseup check - mouseup happened when mouse was out of window
+ if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
+ return this._mouseUp(event);
+ }
+
+ if (this._mouseStarted) {
+ this._mouseDrag(event);
+ return event.preventDefault();
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted =
+ (this._mouseStart(this._mouseDownEvent, event) !== false);
+ (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+ }
+
+ return !this._mouseStarted;
+ },
+
+ _mouseUp: function(event) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ if (this._mouseStarted) {
+ this._mouseStarted = false;
+
+ if (event.target == this._mouseDownEvent.target) {
+ $.data(event.target, this.widgetName + '.preventClickEvent', true);
+ }
+
+ this._mouseStop(event);
+ }
+
+ return false;
+ },
+
+ _mouseDistanceMet: function(event) {
+ return (Math.max(
+ Math.abs(this._mouseDownEvent.pageX - event.pageX),
+ Math.abs(this._mouseDownEvent.pageY - event.pageY)
+ ) >= this.options.distance
+ );
+ },
+
+ _mouseDelayMet: function(event) {
+ return this.mouseDelayMet;
+ },
+
+ // These are placeholder methods, to be overriden by extending plugin
+ _mouseStart: function(event) {},
+ _mouseDrag: function(event) {},
+ _mouseStop: function(event) {},
+ _mouseCapture: function(event) { return true; }
+});
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/position.js b/module/web/static/js/libs/jqueryui/position.js
new file mode 100644
index 000000000..0b4b33656
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/position.js
@@ -0,0 +1,311 @@
+define(['jquery'], function (jQuery) {
+/*!
+ * jQuery UI Position 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var horizontalPositions = /left|center|right/,
+ verticalPositions = /top|center|bottom/,
+ center = "center",
+ support = {},
+ _position = $.fn.position,
+ _offset = $.fn.offset;
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var target = $( options.of ),
+ targetElem = target[0],
+ collision = ( options.collision || "flip" ).split( " " ),
+ offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
+ targetWidth,
+ targetHeight,
+ basePosition;
+
+ if ( targetElem.nodeType === 9 ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: 0, left: 0 };
+ // TODO: use $.isWindow() in 1.9
+ } else if ( targetElem.setTimeout ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
+ } else if ( targetElem.preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ targetWidth = targetHeight = 0;
+ basePosition = { top: options.of.pageY, left: options.of.pageX };
+ } else {
+ targetWidth = target.outerWidth();
+ targetHeight = target.outerHeight();
+ basePosition = target.offset();
+ }
+
+ // force my and at to have valid horizontal and veritcal positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[this] || "" ).split( " " );
+ if ( pos.length === 1) {
+ pos = horizontalPositions.test( pos[0] ) ?
+ pos.concat( [center] ) :
+ verticalPositions.test( pos[0] ) ?
+ [ center ].concat( pos ) :
+ [ center, center ];
+ }
+ pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
+ pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
+ options[ this ] = pos;
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ // normalize offset option
+ offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
+ if ( offset.length === 1 ) {
+ offset[ 1 ] = offset[ 0 ];
+ }
+ offset[ 1 ] = parseInt( offset[1], 10 ) || 0;
+
+ if ( options.at[0] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[0] === center ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[1] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[1] === center ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ basePosition.left += offset[ 0 ];
+ basePosition.top += offset[ 1 ];
+
+ return this.each(function() {
+ var elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
+ marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
+ collisionWidth = elemWidth + marginLeft +
+ ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
+ collisionHeight = elemHeight + marginTop +
+ ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
+ position = $.extend( {}, basePosition ),
+ collisionPosition;
+
+ if ( options.my[0] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[0] === center ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[1] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[1] === center ) {
+ position.top -= elemHeight / 2;
+ }
+
+ // prevent fractions if jQuery version doesn't support them (see #5280)
+ if ( !support.fractions ) {
+ position.left = Math.round( position.left );
+ position.top = Math.round( position.top );
+ }
+
+ collisionPosition = {
+ left: position.left - marginLeft,
+ top: position.top - marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[i] ] ) {
+ $.ui.position[ collision[i] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: offset,
+ my: options.my,
+ at: options.at
+ });
+ }
+ });
+
+ if ( $.fn.bgiframe ) {
+ elem.bgiframe();
+ }
+ elem.offset( $.extend( position, { using: options.using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
+ position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
+ },
+ top: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
+ position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
+ }
+ },
+
+ flip: {
+ left: function( position, data ) {
+ if ( data.at[0] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ -data.targetWidth,
+ offset = -2 * data.offset[ 0 ];
+ position.left += data.collisionPosition.left < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ },
+ top: function( position, data ) {
+ if ( data.at[1] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
+ myOffset = data.my[ 1 ] === "top" ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ -data.targetHeight,
+ offset = -2 * data.offset[ 1 ];
+ position.top += data.collisionPosition.top < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ }
+ }
+};
+
+// offset setter from jQuery 1.4
+if ( !$.offset.setOffset ) {
+ $.offset.setOffset = function( elem, options ) {
+ // set position first, in-case top/left are set even on static elem
+ if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
+ elem.style.position = "relative";
+ }
+ var curElem = $( elem ),
+ curOffset = curElem.offset(),
+ curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0,
+ curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0,
+ props = {
+ top: (options.top - curOffset.top) + curTop,
+ left: (options.left - curOffset.left) + curLeft
+ };
+
+ if ( 'using' in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ };
+
+ $.fn.offset = function( options ) {
+ var elem = this[ 0 ];
+ if ( !elem || !elem.ownerDocument ) { return null; }
+ if ( options ) {
+ if ( $.isFunction( options ) ) {
+ return this.each(function( i ) {
+ $( this ).offset( options.call( this, i, $( this ).offset() ) );
+ });
+ }
+ return this.each(function() {
+ $.offset.setOffset( this, options );
+ });
+ }
+ return _offset.call( this );
+ };
+}
+
+// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css
+if ( !$.curCSS ) {
+ $.curCSS = $.css;
+}
+
+// fraction support test (older versions of jQuery don't support fractions)
+(function () {
+ var body = document.getElementsByTagName( "body" )[ 0 ],
+ div = document.createElement( "div" ),
+ testElement, testElementParent, testElementStyle, offset, offsetTotal;
+
+ //Create a "fake body" for testing based on method used in jQuery.support
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ $.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( var i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || document.documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ div.style.cssText = "position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;";
+
+ offset = $( div ).offset( function( _, offset ) {
+ return offset;
+ }).offset();
+
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+
+ offsetTotal = offset.top + offset.left + ( body ? 2000 : 0 );
+ support.fractions = offsetTotal > 21 && offsetTotal < 22;
+})();
+
+}( jQuery ));
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/progressbar.js b/module/web/static/js/libs/jqueryui/progressbar.js
new file mode 100644
index 000000000..fceee99fa
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/progressbar.js
@@ -0,0 +1,112 @@
+define(['jquery','./core','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Progressbar 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget( "ui.progressbar", {
+ options: {
+ value: 0,
+ max: 100
+ },
+
+ min: 0,
+
+ _create: function() {
+ this.element
+ .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .attr({
+ role: "progressbar",
+ "aria-valuemin": this.min,
+ "aria-valuemax": this.options.max,
+ "aria-valuenow": this._value()
+ });
+
+ this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
+ .appendTo( this.element );
+
+ this.oldValue = this._value();
+ this._refreshValue();
+ },
+
+ destroy: function() {
+ this.element
+ .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-valuemin" )
+ .removeAttr( "aria-valuemax" )
+ .removeAttr( "aria-valuenow" );
+
+ this.valueDiv.remove();
+
+ $.Widget.prototype.destroy.apply( this, arguments );
+ },
+
+ value: function( newValue ) {
+ if ( newValue === undefined ) {
+ return this._value();
+ }
+
+ this._setOption( "value", newValue );
+ return this;
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "value" ) {
+ this.options.value = value;
+ this._refreshValue();
+ if ( this._value() === this.options.max ) {
+ this._trigger( "complete" );
+ }
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+ },
+
+ _value: function() {
+ var val = this.options.value;
+ // normalize invalid value
+ if ( typeof val !== "number" ) {
+ val = 0;
+ }
+ return Math.min( this.options.max, Math.max( this.min, val ) );
+ },
+
+ _percentage: function() {
+ return 100 * this._value() / this.options.max;
+ },
+
+ _refreshValue: function() {
+ var value = this.value();
+ var percentage = this._percentage();
+
+ if ( this.oldValue !== value ) {
+ this.oldValue = value;
+ this._trigger( "change" );
+ }
+
+ this.valueDiv
+ .toggle( value > this.min )
+ .toggleClass( "ui-corner-right", value === this.options.max )
+ .width( percentage.toFixed(0) + "%" );
+ this.element.attr( "aria-valuenow", value );
+ }
+});
+
+$.extend( $.ui.progressbar, {
+ version: "1.8.23"
+});
+
+})( jQuery );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/resizable.js b/module/web/static/js/libs/jqueryui/resizable.js
new file mode 100644
index 000000000..4b0900140
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/resizable.js
@@ -0,0 +1,810 @@
+define(['jquery','./core','./mouse','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Resizable 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizables
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.resizable", $.ui.mouse, {
+ widgetEventPrefix: "resize",
+ options: {
+ alsoResize: false,
+ animate: false,
+ animateDuration: "slow",
+ animateEasing: "swing",
+ aspectRatio: false,
+ autoHide: false,
+ containment: false,
+ ghost: false,
+ grid: false,
+ handles: "e,s,se",
+ helper: false,
+ maxHeight: null,
+ maxWidth: null,
+ minHeight: 10,
+ minWidth: 10,
+ zIndex: 1000
+ },
+ _create: function() {
+
+ var self = this, o = this.options;
+ this.element.addClass("ui-resizable");
+
+ $.extend(this, {
+ _aspectRatio: !!(o.aspectRatio),
+ aspectRatio: o.aspectRatio,
+ originalElement: this.element,
+ _proportionallyResizeElements: [],
+ _helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
+ });
+
+ //Wrap the element if it cannot hold child nodes
+ if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
+
+ //Create a wrapper element and set the wrapper to the new current internal element
+ this.element.wrap(
+ $('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
+ position: this.element.css('position'),
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight(),
+ top: this.element.css('top'),
+ left: this.element.css('left')
+ })
+ );
+
+ //Overwrite the original this.element
+ this.element = this.element.parent().data(
+ "resizable", this.element.data('resizable')
+ );
+
+ this.elementIsWrapper = true;
+
+ //Move margins to the wrapper
+ this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
+ this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
+
+ //Prevent Safari textarea resize
+ this.originalResizeStyle = this.originalElement.css('resize');
+ this.originalElement.css('resize', 'none');
+
+ //Push the actual element to our proportionallyResize internal array
+ this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));
+
+ // avoid IE jump (hard set the margin)
+ this.originalElement.css({ margin: this.originalElement.css('margin') });
+
+ // fix handlers offset
+ this._proportionallyResize();
+
+ }
+
+ this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
+ if(this.handles.constructor == String) {
+
+ if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
+ var n = this.handles.split(","); this.handles = {};
+
+ for(var i = 0; i < n.length; i++) {
+
+ var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
+ var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');
+
+ // Apply zIndex to all handles - see #7960
+ axis.css({ zIndex: o.zIndex });
+
+ //TODO : What's going on here?
+ if ('se' == handle) {
+ axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
+ };
+
+ //Insert into internal handles object and append to element
+ this.handles[handle] = '.ui-resizable-'+handle;
+ this.element.append(axis);
+ }
+
+ }
+
+ this._renderAxis = function(target) {
+
+ target = target || this.element;
+
+ for(var i in this.handles) {
+
+ if(this.handles[i].constructor == String)
+ this.handles[i] = $(this.handles[i], this.element).show();
+
+ //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
+ if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
+
+ var axis = $(this.handles[i], this.element), padWrapper = 0;
+
+ //Checking the correct pad and border
+ padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+
+ //The padding type i have to apply...
+ var padPos = [ 'padding',
+ /ne|nw|n/.test(i) ? 'Top' :
+ /se|sw|s/.test(i) ? 'Bottom' :
+ /^e$/.test(i) ? 'Right' : 'Left' ].join("");
+
+ target.css(padPos, padWrapper);
+
+ this._proportionallyResize();
+
+ }
+
+ //TODO: What's that good for? There's not anything to be executed left
+ if(!$(this.handles[i]).length)
+ continue;
+
+ }
+ };
+
+ //TODO: make renderAxis a prototype function
+ this._renderAxis(this.element);
+
+ this._handles = $('.ui-resizable-handle', this.element)
+ .disableSelection();
+
+ //Matching axis name
+ this._handles.mouseover(function() {
+ if (!self.resizing) {
+ if (this.className)
+ var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+ //Axis, default = se
+ self.axis = axis && axis[1] ? axis[1] : 'se';
+ }
+ });
+
+ //If we want to auto hide the elements
+ if (o.autoHide) {
+ this._handles.hide();
+ $(this.element)
+ .addClass("ui-resizable-autohide")
+ .hover(function() {
+ if (o.disabled) return;
+ $(this).removeClass("ui-resizable-autohide");
+ self._handles.show();
+ },
+ function(){
+ if (o.disabled) return;
+ if (!self.resizing) {
+ $(this).addClass("ui-resizable-autohide");
+ self._handles.hide();
+ }
+ });
+ }
+
+ //Initialize the mouse interaction
+ this._mouseInit();
+
+ },
+
+ destroy: function() {
+
+ this._mouseDestroy();
+
+ var _destroy = function(exp) {
+ $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
+ .removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
+ };
+
+ //TODO: Unwrap at same DOM position
+ if (this.elementIsWrapper) {
+ _destroy(this.element);
+ var wrapper = this.element;
+ wrapper.after(
+ this.originalElement.css({
+ position: wrapper.css('position'),
+ width: wrapper.outerWidth(),
+ height: wrapper.outerHeight(),
+ top: wrapper.css('top'),
+ left: wrapper.css('left')
+ })
+ ).remove();
+ }
+
+ this.originalElement.css('resize', this.originalResizeStyle);
+ _destroy(this.originalElement);
+
+ return this;
+ },
+
+ _mouseCapture: function(event) {
+ var handle = false;
+ for (var i in this.handles) {
+ if ($(this.handles[i])[0] == event.target) {
+ handle = true;
+ }
+ }
+
+ return !this.options.disabled && handle;
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options, iniPos = this.element.position(), el = this.element;
+
+ this.resizing = true;
+ this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
+
+ // bugfix for http://dev.jquery.com/ticket/1749
+ if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
+ el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
+ }
+
+ this._renderProxy();
+
+ var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
+
+ if (o.containment) {
+ curleft += $(o.containment).scrollLeft() || 0;
+ curtop += $(o.containment).scrollTop() || 0;
+ }
+
+ //Store needed variables
+ this.offset = this.helper.offset();
+ this.position = { left: curleft, top: curtop };
+ this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalPosition = { left: curleft, top: curtop };
+ this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
+ this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+ //Aspect Ratio
+ this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
+
+ var cursor = $('.ui-resizable-' + this.axis).css('cursor');
+ $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);
+
+ el.addClass("ui-resizable-resizing");
+ this._propagate("start", event);
+ return true;
+ },
+
+ _mouseDrag: function(event) {
+
+ //Increase performance, avoid regex
+ var el = this.helper, o = this.options, props = {},
+ self = this, smp = this.originalMousePosition, a = this.axis;
+
+ var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
+ var trigger = this._change[a];
+ if (!trigger) return false;
+
+ // Calculate the attrs that will be change
+ var data = trigger.apply(this, [event, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff;
+
+ // Put this in the mouseDrag handler since the user can start pressing shift while resizing
+ this._updateVirtualBoundaries(event.shiftKey);
+ if (this._aspectRatio || event.shiftKey)
+ data = this._updateRatio(data, event);
+
+ data = this._respectSize(data, event);
+
+ // plugins callbacks need to be called first
+ this._propagate("resize", event);
+
+ el.css({
+ top: this.position.top + "px", left: this.position.left + "px",
+ width: this.size.width + "px", height: this.size.height + "px"
+ });
+
+ if (!this._helper && this._proportionallyResizeElements.length)
+ this._proportionallyResize();
+
+ this._updateCache(data);
+
+ // calling the user callback at the end
+ this._trigger('resize', event, this.ui());
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ this.resizing = false;
+ var o = this.options, self = this;
+
+ if(this._helper) {
+ var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+ soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
+ soffsetw = ista ? 0 : self.sizeDiff.width;
+
+ var s = { width: (self.helper.width() - soffsetw), height: (self.helper.height() - soffseth) },
+ left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
+ top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
+
+ if (!o.animate)
+ this.element.css($.extend(s, { top: top, left: left }));
+
+ self.helper.height(self.size.height);
+ self.helper.width(self.size.width);
+
+ if (this._helper && !o.animate) this._proportionallyResize();
+ }
+
+ $('body').css('cursor', 'auto');
+
+ this.element.removeClass("ui-resizable-resizing");
+
+ this._propagate("stop", event);
+
+ if (this._helper) this.helper.remove();
+ return false;
+
+ },
+
+ _updateVirtualBoundaries: function(forceAspectRatio) {
+ var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;
+
+ b = {
+ minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
+ maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
+ minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
+ maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
+ };
+
+ if(this._aspectRatio || forceAspectRatio) {
+ // We want to create an enclosing box whose aspect ration is the requested one
+ // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
+ pMinWidth = b.minHeight * this.aspectRatio;
+ pMinHeight = b.minWidth / this.aspectRatio;
+ pMaxWidth = b.maxHeight * this.aspectRatio;
+ pMaxHeight = b.maxWidth / this.aspectRatio;
+
+ if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
+ if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
+ if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
+ if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
+ }
+ this._vBoundaries = b;
+ },
+
+ _updateCache: function(data) {
+ var o = this.options;
+ this.offset = this.helper.offset();
+ if (isNumber(data.left)) this.position.left = data.left;
+ if (isNumber(data.top)) this.position.top = data.top;
+ if (isNumber(data.height)) this.size.height = data.height;
+ if (isNumber(data.width)) this.size.width = data.width;
+ },
+
+ _updateRatio: function(data, event) {
+
+ var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
+
+ if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
+ else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);
+
+ if (a == 'sw') {
+ data.left = cpos.left + (csize.width - data.width);
+ data.top = null;
+ }
+ if (a == 'nw') {
+ data.top = cpos.top + (csize.height - data.height);
+ data.left = cpos.left + (csize.width - data.width);
+ }
+
+ return data;
+ },
+
+ _respectSize: function(data, event) {
+
+ var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
+ ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
+ isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);
+
+ if (isminw) data.width = o.minWidth;
+ if (isminh) data.height = o.minHeight;
+ if (ismaxw) data.width = o.maxWidth;
+ if (ismaxh) data.height = o.maxHeight;
+
+ var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
+ var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
+
+ if (isminw && cw) data.left = dw - o.minWidth;
+ if (ismaxw && cw) data.left = dw - o.maxWidth;
+ if (isminh && ch) data.top = dh - o.minHeight;
+ if (ismaxh && ch) data.top = dh - o.maxHeight;
+
+ // fixing jump error on top/left - bug #2330
+ var isNotwh = !data.width && !data.height;
+ if (isNotwh && !data.left && data.top) data.top = null;
+ else if (isNotwh && !data.top && data.left) data.left = null;
+
+ return data;
+ },
+
+ _proportionallyResize: function() {
+
+ var o = this.options;
+ if (!this._proportionallyResizeElements.length) return;
+ var element = this.helper || this.element;
+
+ for (var i=0; i < this._proportionallyResizeElements.length; i++) {
+
+ var prel = this._proportionallyResizeElements[i];
+
+ if (!this.borderDif) {
+ var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
+ p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
+
+ this.borderDif = $.map(b, function(v, i) {
+ var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
+ return border + padding;
+ });
+ }
+
+ if ($.browser.msie && !(!($(element).is(':hidden') || $(element).parents(':hidden').length)))
+ continue;
+
+ prel.css({
+ height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
+ width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
+ });
+
+ };
+
+ },
+
+ _renderProxy: function() {
+
+ var el = this.element, o = this.options;
+ this.elementOffset = el.offset();
+
+ if(this._helper) {
+
+ this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
+
+ // fix ie6 offset TODO: This seems broken
+ var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
+ pxyoffset = ( ie6 ? 2 : -1 );
+
+ this.helper.addClass(this._helper).css({
+ width: this.element.outerWidth() + pxyoffset,
+ height: this.element.outerHeight() + pxyoffset,
+ position: 'absolute',
+ left: this.elementOffset.left - ie6offset +'px',
+ top: this.elementOffset.top - ie6offset +'px',
+ zIndex: ++o.zIndex //TODO: Don't modify option
+ });
+
+ this.helper
+ .appendTo("body")
+ .disableSelection();
+
+ } else {
+ this.helper = this.element;
+ }
+
+ },
+
+ _change: {
+ e: function(event, dx, dy) {
+ return { width: this.originalSize.width + dx };
+ },
+ w: function(event, dx, dy) {
+ var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+ return { left: sp.left + dx, width: cs.width - dx };
+ },
+ n: function(event, dx, dy) {
+ var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+ return { top: sp.top + dy, height: cs.height - dy };
+ },
+ s: function(event, dx, dy) {
+ return { height: this.originalSize.height + dy };
+ },
+ se: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ sw: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ },
+ ne: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ nw: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ }
+ },
+
+ _propagate: function(n, event) {
+ $.ui.plugin.call(this, n, [event, this.ui()]);
+ (n != "resize" && this._trigger(n, event, this.ui()));
+ },
+
+ plugins: {},
+
+ ui: function() {
+ return {
+ originalElement: this.originalElement,
+ element: this.element,
+ helper: this.helper,
+ position: this.position,
+ size: this.size,
+ originalSize: this.originalSize,
+ originalPosition: this.originalPosition
+ };
+ }
+
+});
+
+$.extend($.ui.resizable, {
+ version: "1.8.23"
+});
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add("resizable", "alsoResize", {
+
+ start: function (event, ui) {
+ var self = $(this).data("resizable"), o = self.options;
+
+ var _store = function (exp) {
+ $(exp).each(function() {
+ var el = $(this);
+ el.data("resizable-alsoresize", {
+ width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
+ left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10)
+ });
+ });
+ };
+
+ if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
+ if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
+ else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
+ }else{
+ _store(o.alsoResize);
+ }
+ },
+
+ resize: function (event, ui) {
+ var self = $(this).data("resizable"), o = self.options, os = self.originalSize, op = self.originalPosition;
+
+ var delta = {
+ height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0,
+ top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0
+ },
+
+ _alsoResize = function (exp, c) {
+ $(exp).each(function() {
+ var el = $(this), start = $(this).data("resizable-alsoresize"), style = {},
+ css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];
+
+ $.each(css, function (i, prop) {
+ var sum = (start[prop]||0) + (delta[prop]||0);
+ if (sum && sum >= 0)
+ style[prop] = sum || null;
+ });
+
+ el.css(style);
+ });
+ };
+
+ if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
+ $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
+ }else{
+ _alsoResize(o.alsoResize);
+ }
+ },
+
+ stop: function (event, ui) {
+ $(this).removeData("resizable-alsoresize");
+ }
+});
+
+$.ui.plugin.add("resizable", "animate", {
+
+ stop: function(event, ui) {
+ var self = $(this).data("resizable"), o = self.options;
+
+ var pr = self._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+ soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
+ soffsetw = ista ? 0 : self.sizeDiff.width;
+
+ var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
+ left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
+ top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
+
+ self.element.animate(
+ $.extend(style, top && left ? { top: top, left: left } : {}), {
+ duration: o.animateDuration,
+ easing: o.animateEasing,
+ step: function() {
+
+ var data = {
+ width: parseInt(self.element.css('width'), 10),
+ height: parseInt(self.element.css('height'), 10),
+ top: parseInt(self.element.css('top'), 10),
+ left: parseInt(self.element.css('left'), 10)
+ };
+
+ if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
+
+ // propagating resize, and updating values for each animation step
+ self._updateCache(data);
+ self._propagate("resize", event);
+
+ }
+ }
+ );
+ }
+
+});
+
+$.ui.plugin.add("resizable", "containment", {
+
+ start: function(event, ui) {
+ var self = $(this).data("resizable"), o = self.options, el = self.element;
+ var oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
+ if (!ce) return;
+
+ self.containerElement = $(ce);
+
+ if (/document/.test(oc) || oc == document) {
+ self.containerOffset = { left: 0, top: 0 };
+ self.containerPosition = { left: 0, top: 0 };
+
+ self.parentData = {
+ element: $(document), left: 0, top: 0,
+ width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
+ };
+ }
+
+ // i'm a node, so compute top, left, right, bottom
+ else {
+ var element = $(ce), p = [];
+ $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
+
+ self.containerOffset = element.offset();
+ self.containerPosition = element.position();
+ self.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
+
+ var co = self.containerOffset, ch = self.containerSize.height, cw = self.containerSize.width,
+ width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
+
+ self.parentData = {
+ element: ce, left: co.left, top: co.top, width: width, height: height
+ };
+ }
+ },
+
+ resize: function(event, ui) {
+ var self = $(this).data("resizable"), o = self.options,
+ ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position,
+ pRatio = self._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement;
+
+ if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;
+
+ if (cp.left < (self._helper ? co.left : 0)) {
+ self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left));
+ if (pRatio) self.size.height = self.size.width / self.aspectRatio;
+ self.position.left = o.helper ? co.left : 0;
+ }
+
+ if (cp.top < (self._helper ? co.top : 0)) {
+ self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top);
+ if (pRatio) self.size.width = self.size.height * self.aspectRatio;
+ self.position.top = self._helper ? co.top : 0;
+ }
+
+ self.offset.left = self.parentData.left+self.position.left;
+ self.offset.top = self.parentData.top+self.position.top;
+
+ var woset = Math.abs( (self._helper ? self.offset.left - cop.left : (self.offset.left - cop.left)) + self.sizeDiff.width ),
+ hoset = Math.abs( (self._helper ? self.offset.top - cop.top : (self.offset.top - co.top)) + self.sizeDiff.height );
+
+ var isParent = self.containerElement.get(0) == self.element.parent().get(0),
+ isOffsetRelative = /relative|absolute/.test(self.containerElement.css('position'));
+
+ if(isParent && isOffsetRelative) woset -= self.parentData.left;
+
+ if (woset + self.size.width >= self.parentData.width) {
+ self.size.width = self.parentData.width - woset;
+ if (pRatio) self.size.height = self.size.width / self.aspectRatio;
+ }
+
+ if (hoset + self.size.height >= self.parentData.height) {
+ self.size.height = self.parentData.height - hoset;
+ if (pRatio) self.size.width = self.size.height * self.aspectRatio;
+ }
+ },
+
+ stop: function(event, ui){
+ var self = $(this).data("resizable"), o = self.options, cp = self.position,
+ co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement;
+
+ var helper = $(self.helper), ho = helper.offset(), w = helper.outerWidth() - self.sizeDiff.width, h = helper.outerHeight() - self.sizeDiff.height;
+
+ if (self._helper && !o.animate && (/relative/).test(ce.css('position')))
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+ if (self._helper && !o.animate && (/static/).test(ce.css('position')))
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+ }
+});
+
+$.ui.plugin.add("resizable", "ghost", {
+
+ start: function(event, ui) {
+
+ var self = $(this).data("resizable"), o = self.options, cs = self.size;
+
+ self.ghost = self.originalElement.clone();
+ self.ghost
+ .css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
+ .addClass('ui-resizable-ghost')
+ .addClass(typeof o.ghost == 'string' ? o.ghost : '');
+
+ self.ghost.appendTo(self.helper);
+
+ },
+
+ resize: function(event, ui){
+ var self = $(this).data("resizable"), o = self.options;
+ if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width });
+ },
+
+ stop: function(event, ui){
+ var self = $(this).data("resizable"), o = self.options;
+ if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0));
+ }
+
+});
+
+$.ui.plugin.add("resizable", "grid", {
+
+ resize: function(event, ui) {
+ var self = $(this).data("resizable"), o = self.options, cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || event.shiftKey;
+ o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
+ var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
+
+ if (/^(se|s|e)$/.test(a)) {
+ self.size.width = os.width + ox;
+ self.size.height = os.height + oy;
+ }
+ else if (/^(ne)$/.test(a)) {
+ self.size.width = os.width + ox;
+ self.size.height = os.height + oy;
+ self.position.top = op.top - oy;
+ }
+ else if (/^(sw)$/.test(a)) {
+ self.size.width = os.width + ox;
+ self.size.height = os.height + oy;
+ self.position.left = op.left - ox;
+ }
+ else {
+ self.size.width = os.width + ox;
+ self.size.height = os.height + oy;
+ self.position.top = op.top - oy;
+ self.position.left = op.left - ox;
+ }
+ }
+
+});
+
+var num = function(v) {
+ return parseInt(v, 10) || 0;
+};
+
+var isNumber = function(value) {
+ return !isNaN(parseInt(value, 10));
+};
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/selectable.js b/module/web/static/js/libs/jqueryui/selectable.js
new file mode 100644
index 000000000..abefc9d2c
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/selectable.js
@@ -0,0 +1,270 @@
+define(['jquery','./core','./mouse','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Selectable 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectables
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.selectable", $.ui.mouse, {
+ options: {
+ appendTo: 'body',
+ autoRefresh: true,
+ distance: 0,
+ filter: '*',
+ tolerance: 'touch'
+ },
+ _create: function() {
+ var self = this;
+
+ this.element.addClass("ui-selectable");
+
+ this.dragged = false;
+
+ // cache selectee children based on filter
+ var selectees;
+ this.refresh = function() {
+ selectees = $(self.options.filter, self.element[0]);
+ selectees.addClass("ui-selectee");
+ selectees.each(function() {
+ var $this = $(this);
+ var pos = $this.offset();
+ $.data(this, "selectable-item", {
+ element: this,
+ $element: $this,
+ left: pos.left,
+ top: pos.top,
+ right: pos.left + $this.outerWidth(),
+ bottom: pos.top + $this.outerHeight(),
+ startselected: false,
+ selected: $this.hasClass('ui-selected'),
+ selecting: $this.hasClass('ui-selecting'),
+ unselecting: $this.hasClass('ui-unselecting')
+ });
+ });
+ };
+ this.refresh();
+
+ this.selectees = selectees.addClass("ui-selectee");
+
+ this._mouseInit();
+
+ this.helper = $("<div class='ui-selectable-helper'></div>");
+ },
+
+ destroy: function() {
+ this.selectees
+ .removeClass("ui-selectee")
+ .removeData("selectable-item");
+ this.element
+ .removeClass("ui-selectable ui-selectable-disabled")
+ .removeData("selectable")
+ .unbind(".selectable");
+ this._mouseDestroy();
+
+ return this;
+ },
+
+ _mouseStart: function(event) {
+ var self = this;
+
+ this.opos = [event.pageX, event.pageY];
+
+ if (this.options.disabled)
+ return;
+
+ var options = this.options;
+
+ this.selectees = $(options.filter, this.element[0]);
+
+ this._trigger("start", event);
+
+ $(options.appendTo).append(this.helper);
+ // position helper (lasso)
+ this.helper.css({
+ "left": event.clientX,
+ "top": event.clientY,
+ "width": 0,
+ "height": 0
+ });
+
+ if (options.autoRefresh) {
+ this.refresh();
+ }
+
+ this.selectees.filter('.ui-selected').each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.startselected = true;
+ if (!event.metaKey && !event.ctrlKey) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ self._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ });
+
+ $(event.target).parents().andSelf().each(function() {
+ var selectee = $.data(this, "selectable-item");
+ if (selectee) {
+ var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
+ selectee.$element
+ .removeClass(doSelect ? "ui-unselecting" : "ui-selected")
+ .addClass(doSelect ? "ui-selecting" : "ui-unselecting");
+ selectee.unselecting = !doSelect;
+ selectee.selecting = doSelect;
+ selectee.selected = doSelect;
+ // selectable (UN)SELECTING callback
+ if (doSelect) {
+ self._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ } else {
+ self._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ return false;
+ }
+ });
+
+ },
+
+ _mouseDrag: function(event) {
+ var self = this;
+ this.dragged = true;
+
+ if (this.options.disabled)
+ return;
+
+ var options = this.options;
+
+ var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
+ if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
+ if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
+ this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
+
+ this.selectees.each(function() {
+ var selectee = $.data(this, "selectable-item");
+ //prevent helper from being selected if appendTo: selectable
+ if (!selectee || selectee.element == self.element[0])
+ return;
+ var hit = false;
+ if (options.tolerance == 'touch') {
+ hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
+ } else if (options.tolerance == 'fit') {
+ hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
+ }
+
+ if (hit) {
+ // SELECT
+ if (selectee.selected) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+ }
+ if (selectee.unselecting) {
+ selectee.$element.removeClass('ui-unselecting');
+ selectee.unselecting = false;
+ }
+ if (!selectee.selecting) {
+ selectee.$element.addClass('ui-selecting');
+ selectee.selecting = true;
+ // selectable SELECTING callback
+ self._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ }
+ } else {
+ // UNSELECT
+ if (selectee.selecting) {
+ if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
+ selectee.$element.removeClass('ui-selecting');
+ selectee.selecting = false;
+ selectee.$element.addClass('ui-selected');
+ selectee.selected = true;
+ } else {
+ selectee.$element.removeClass('ui-selecting');
+ selectee.selecting = false;
+ if (selectee.startselected) {
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ }
+ // selectable UNSELECTING callback
+ self._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ if (selectee.selected) {
+ if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ self._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ }
+ });
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+ var self = this;
+
+ this.dragged = false;
+
+ var options = this.options;
+
+ $('.ui-unselecting', this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass('ui-unselecting');
+ selectee.unselecting = false;
+ selectee.startselected = false;
+ self._trigger("unselected", event, {
+ unselected: selectee.element
+ });
+ });
+ $('.ui-selecting', this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
+ selectee.selecting = false;
+ selectee.selected = true;
+ selectee.startselected = true;
+ self._trigger("selected", event, {
+ selected: selectee.element
+ });
+ });
+ this._trigger("stop", event);
+
+ this.helper.remove();
+
+ return false;
+ }
+
+});
+
+$.extend($.ui.selectable, {
+ version: "1.8.23"
+});
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/slider.js b/module/web/static/js/libs/jqueryui/slider.js
new file mode 100644
index 000000000..be11afcdf
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/slider.js
@@ -0,0 +1,665 @@
+define(['jquery','./core','./mouse','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Slider 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+
+ widgetEventPrefix: "slide",
+
+ options: {
+ animate: false,
+ distance: 0,
+ max: 100,
+ min: 0,
+ orientation: "horizontal",
+ range: false,
+ step: 1,
+ value: 0,
+ values: null
+ },
+
+ _create: function() {
+ var self = this,
+ o = this.options,
+ existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+ handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+ handleCount = ( o.values && o.values.length ) || 1,
+ handles = [];
+
+ this._keySliding = false;
+ this._mouseSliding = false;
+ this._animateOff = true;
+ this._handleIndex = null;
+ this._detectOrientation();
+ this._mouseInit();
+
+ this.element
+ .addClass( "ui-slider" +
+ " ui-slider-" + this.orientation +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" +
+ ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
+
+ this.range = $([]);
+
+ if ( o.range ) {
+ if ( o.range === true ) {
+ if ( !o.values ) {
+ o.values = [ this._valueMin(), this._valueMin() ];
+ }
+ if ( o.values.length && o.values.length !== 2 ) {
+ o.values = [ o.values[0], o.values[0] ];
+ }
+ }
+
+ this.range = $( "<div></div>" )
+ .appendTo( this.element )
+ .addClass( "ui-slider-range" +
+ // note: this isn't the most fittingly semantic framework class for this element,
+ // but worked best visually with a variety of themes
+ " ui-widget-header" +
+ ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
+ }
+
+ for ( var i = existingHandles.length; i < handleCount; i += 1 ) {
+ handles.push( handle );
+ }
+
+ this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( self.element ) );
+
+ this.handle = this.handles.eq( 0 );
+
+ this.handles.add( this.range ).filter( "a" )
+ .click(function( event ) {
+ event.preventDefault();
+ })
+ .hover(function() {
+ if ( !o.disabled ) {
+ $( this ).addClass( "ui-state-hover" );
+ }
+ }, function() {
+ $( this ).removeClass( "ui-state-hover" );
+ })
+ .focus(function() {
+ if ( !o.disabled ) {
+ $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
+ $( this ).addClass( "ui-state-focus" );
+ } else {
+ $( this ).blur();
+ }
+ })
+ .blur(function() {
+ $( this ).removeClass( "ui-state-focus" );
+ });
+
+ this.handles.each(function( i ) {
+ $( this ).data( "index.ui-slider-handle", i );
+ });
+
+ this.handles
+ .keydown(function( event ) {
+ var index = $( this ).data( "index.ui-slider-handle" ),
+ allowed,
+ curVal,
+ newVal,
+ step;
+
+ if ( self.options.disabled ) {
+ return;
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ case $.ui.keyCode.END:
+ case $.ui.keyCode.PAGE_UP:
+ case $.ui.keyCode.PAGE_DOWN:
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ event.preventDefault();
+ if ( !self._keySliding ) {
+ self._keySliding = true;
+ $( this ).addClass( "ui-state-active" );
+ allowed = self._start( event, index );
+ if ( allowed === false ) {
+ return;
+ }
+ }
+ break;
+ }
+
+ step = self.options.step;
+ if ( self.options.values && self.options.values.length ) {
+ curVal = newVal = self.values( index );
+ } else {
+ curVal = newVal = self.value();
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ newVal = self._valueMin();
+ break;
+ case $.ui.keyCode.END:
+ newVal = self._valueMax();
+ break;
+ case $.ui.keyCode.PAGE_UP:
+ newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ if ( curVal === self._valueMax() ) {
+ return;
+ }
+ newVal = self._trimAlignValue( curVal + step );
+ break;
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ if ( curVal === self._valueMin() ) {
+ return;
+ }
+ newVal = self._trimAlignValue( curVal - step );
+ break;
+ }
+
+ self._slide( event, index, newVal );
+ })
+ .keyup(function( event ) {
+ var index = $( this ).data( "index.ui-slider-handle" );
+
+ if ( self._keySliding ) {
+ self._keySliding = false;
+ self._stop( event, index );
+ self._change( event, index );
+ $( this ).removeClass( "ui-state-active" );
+ }
+
+ });
+
+ this._refreshValue();
+
+ this._animateOff = false;
+ },
+
+ destroy: function() {
+ this.handles.remove();
+ this.range.remove();
+
+ this.element
+ .removeClass( "ui-slider" +
+ " ui-slider-horizontal" +
+ " ui-slider-vertical" +
+ " ui-slider-disabled" +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" )
+ .removeData( "slider" )
+ .unbind( ".slider" );
+
+ this._mouseDestroy();
+
+ return this;
+ },
+
+ _mouseCapture: function( event ) {
+ var o = this.options,
+ position,
+ normValue,
+ distance,
+ closestHandle,
+ self,
+ index,
+ allowed,
+ offset,
+ mouseOverHandle;
+
+ if ( o.disabled ) {
+ return false;
+ }
+
+ this.elementSize = {
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight()
+ };
+ this.elementOffset = this.element.offset();
+
+ position = { x: event.pageX, y: event.pageY };
+ normValue = this._normValueFromMouse( position );
+ distance = this._valueMax() - this._valueMin() + 1;
+ self = this;
+ this.handles.each(function( i ) {
+ var thisDistance = Math.abs( normValue - self.values(i) );
+ if ( distance > thisDistance ) {
+ distance = thisDistance;
+ closestHandle = $( this );
+ index = i;
+ }
+ });
+
+ // workaround for bug #3736 (if both handles of a range are at 0,
+ // the first is always used as the one with least distance,
+ // and moving it is obviously prevented by preventing negative ranges)
+ if( o.range === true && this.values(1) === o.min ) {
+ index += 1;
+ closestHandle = $( this.handles[index] );
+ }
+
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return false;
+ }
+ this._mouseSliding = true;
+
+ self._handleIndex = index;
+
+ closestHandle
+ .addClass( "ui-state-active" )
+ .focus();
+
+ offset = closestHandle.offset();
+ mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
+ this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+ left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+ top: event.pageY - offset.top -
+ ( closestHandle.height() / 2 ) -
+ ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+ ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+ ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+ };
+
+ if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+ this._slide( event, index, normValue );
+ }
+ this._animateOff = true;
+ return true;
+ },
+
+ _mouseStart: function( event ) {
+ return true;
+ },
+
+ _mouseDrag: function( event ) {
+ var position = { x: event.pageX, y: event.pageY },
+ normValue = this._normValueFromMouse( position );
+
+ this._slide( event, this._handleIndex, normValue );
+
+ return false;
+ },
+
+ _mouseStop: function( event ) {
+ this.handles.removeClass( "ui-state-active" );
+ this._mouseSliding = false;
+
+ this._stop( event, this._handleIndex );
+ this._change( event, this._handleIndex );
+
+ this._handleIndex = null;
+ this._clickOffset = null;
+ this._animateOff = false;
+
+ return false;
+ },
+
+ _detectOrientation: function() {
+ this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+ },
+
+ _normValueFromMouse: function( position ) {
+ var pixelTotal,
+ pixelMouse,
+ percentMouse,
+ valueTotal,
+ valueMouse;
+
+ if ( this.orientation === "horizontal" ) {
+ pixelTotal = this.elementSize.width;
+ pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+ } else {
+ pixelTotal = this.elementSize.height;
+ pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+ }
+
+ percentMouse = ( pixelMouse / pixelTotal );
+ if ( percentMouse > 1 ) {
+ percentMouse = 1;
+ }
+ if ( percentMouse < 0 ) {
+ percentMouse = 0;
+ }
+ if ( this.orientation === "vertical" ) {
+ percentMouse = 1 - percentMouse;
+ }
+
+ valueTotal = this._valueMax() - this._valueMin();
+ valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+ return this._trimAlignValue( valueMouse );
+ },
+
+ _start: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+ return this._trigger( "start", event, uiHash );
+ },
+
+ _slide: function( event, index, newVal ) {
+ var otherVal,
+ newValues,
+ allowed;
+
+ if ( this.options.values && this.options.values.length ) {
+ otherVal = this.values( index ? 0 : 1 );
+
+ if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+ ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+ ) {
+ newVal = otherVal;
+ }
+
+ if ( newVal !== this.values( index ) ) {
+ newValues = this.values();
+ newValues[ index ] = newVal;
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal,
+ values: newValues
+ } );
+ otherVal = this.values( index ? 0 : 1 );
+ if ( allowed !== false ) {
+ this.values( index, newVal, true );
+ }
+ }
+ } else {
+ if ( newVal !== this.value() ) {
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal
+ } );
+ if ( allowed !== false ) {
+ this.value( newVal );
+ }
+ }
+ }
+ },
+
+ _stop: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "stop", event, uiHash );
+ },
+
+ _change: function( event, index ) {
+ if ( !this._keySliding && !this._mouseSliding ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "change", event, uiHash );
+ }
+ },
+
+ value: function( newValue ) {
+ if ( arguments.length ) {
+ this.options.value = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, 0 );
+ return;
+ }
+
+ return this._value();
+ },
+
+ values: function( index, newValue ) {
+ var vals,
+ newValues,
+ i;
+
+ if ( arguments.length > 1 ) {
+ this.options.values[ index ] = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, index );
+ return;
+ }
+
+ if ( arguments.length ) {
+ if ( $.isArray( arguments[ 0 ] ) ) {
+ vals = this.options.values;
+ newValues = arguments[ 0 ];
+ for ( i = 0; i < vals.length; i += 1 ) {
+ vals[ i ] = this._trimAlignValue( newValues[ i ] );
+ this._change( null, i );
+ }
+ this._refreshValue();
+ } else {
+ if ( this.options.values && this.options.values.length ) {
+ return this._values( index );
+ } else {
+ return this.value();
+ }
+ }
+ } else {
+ return this._values();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var i,
+ valsLength = 0;
+
+ if ( $.isArray( this.options.values ) ) {
+ valsLength = this.options.values.length;
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+
+ switch ( key ) {
+ case "disabled":
+ if ( value ) {
+ this.handles.filter( ".ui-state-focus" ).blur();
+ this.handles.removeClass( "ui-state-hover" );
+ this.handles.propAttr( "disabled", true );
+ this.element.addClass( "ui-disabled" );
+ } else {
+ this.handles.propAttr( "disabled", false );
+ this.element.removeClass( "ui-disabled" );
+ }
+ break;
+ case "orientation":
+ this._detectOrientation();
+ this.element
+ .removeClass( "ui-slider-horizontal ui-slider-vertical" )
+ .addClass( "ui-slider-" + this.orientation );
+ this._refreshValue();
+ break;
+ case "value":
+ this._animateOff = true;
+ this._refreshValue();
+ this._change( null, 0 );
+ this._animateOff = false;
+ break;
+ case "values":
+ this._animateOff = true;
+ this._refreshValue();
+ for ( i = 0; i < valsLength; i += 1 ) {
+ this._change( null, i );
+ }
+ this._animateOff = false;
+ break;
+ }
+ },
+
+ //internal value getter
+ // _value() returns value trimmed by min and max, aligned by step
+ _value: function() {
+ var val = this.options.value;
+ val = this._trimAlignValue( val );
+
+ return val;
+ },
+
+ //internal values getter
+ // _values() returns array of values trimmed by min and max, aligned by step
+ // _values( index ) returns single value trimmed by min and max, aligned by step
+ _values: function( index ) {
+ var val,
+ vals,
+ i;
+
+ if ( arguments.length ) {
+ val = this.options.values[ index ];
+ val = this._trimAlignValue( val );
+
+ return val;
+ } else {
+ // .slice() creates a copy of the array
+ // this copy gets trimmed by min and max and then returned
+ vals = this.options.values.slice();
+ for ( i = 0; i < vals.length; i+= 1) {
+ vals[ i ] = this._trimAlignValue( vals[ i ] );
+ }
+
+ return vals;
+ }
+ },
+
+ // returns the step-aligned value that val is closest to, between (inclusive) min and max
+ _trimAlignValue: function( val ) {
+ if ( val <= this._valueMin() ) {
+ return this._valueMin();
+ }
+ if ( val >= this._valueMax() ) {
+ return this._valueMax();
+ }
+ var step = ( this.options.step > 0 ) ? this.options.step : 1,
+ valModStep = (val - this._valueMin()) % step,
+ alignValue = val - valModStep;
+
+ if ( Math.abs(valModStep) * 2 >= step ) {
+ alignValue += ( valModStep > 0 ) ? step : ( -step );
+ }
+
+ // Since JavaScript has problems with large floats, round
+ // the final value to 5 digits after the decimal point (see #4124)
+ return parseFloat( alignValue.toFixed(5) );
+ },
+
+ _valueMin: function() {
+ return this.options.min;
+ },
+
+ _valueMax: function() {
+ return this.options.max;
+ },
+
+ _refreshValue: function() {
+ var oRange = this.options.range,
+ o = this.options,
+ self = this,
+ animate = ( !this._animateOff ) ? o.animate : false,
+ valPercent,
+ _set = {},
+ lastValPercent,
+ value,
+ valueMin,
+ valueMax;
+
+ if ( this.options.values && this.options.values.length ) {
+ this.handles.each(function( i, j ) {
+ valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100;
+ _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+ if ( self.options.range === true ) {
+ if ( self.orientation === "horizontal" ) {
+ if ( i === 0 ) {
+ self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ } else {
+ if ( i === 0 ) {
+ self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+ lastValPercent = valPercent;
+ });
+ } else {
+ value = this.value();
+ valueMin = this._valueMin();
+ valueMax = this._valueMax();
+ valPercent = ( valueMax !== valueMin ) ?
+ ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+ 0;
+ _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+ if ( oRange === "min" && this.orientation === "horizontal" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "horizontal" ) {
+ this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ if ( oRange === "min" && this.orientation === "vertical" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "vertical" ) {
+ this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+
+});
+
+$.extend( $.ui.slider, {
+ version: "1.8.23"
+});
+
+}(jQuery));
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/sortable.js b/module/web/static/js/libs/jqueryui/sortable.js
new file mode 100644
index 000000000..ae808d561
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/sortable.js
@@ -0,0 +1,1087 @@
+define(['jquery','./core','./mouse','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Sortable 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Sortables
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.sortable", $.ui.mouse, {
+ widgetEventPrefix: "sort",
+ ready: false,
+ options: {
+ appendTo: "parent",
+ axis: false,
+ connectWith: false,
+ containment: false,
+ cursor: 'auto',
+ cursorAt: false,
+ dropOnEmpty: true,
+ forcePlaceholderSize: false,
+ forceHelperSize: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ items: '> *',
+ opacity: false,
+ placeholder: false,
+ revert: false,
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ scope: "default",
+ tolerance: "intersect",
+ zIndex: 1000
+ },
+ _create: function() {
+
+ var o = this.options;
+ this.containerCache = {};
+ this.element.addClass("ui-sortable");
+
+ //Get the items
+ this.refresh();
+
+ //Let's determine if the items are being displayed horizontally
+ this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
+
+ //Let's determine the parent's offset
+ this.offset = this.element.offset();
+
+ //Initialize mouse events for interaction
+ this._mouseInit();
+
+ //We're ready to go
+ this.ready = true
+
+ },
+
+ destroy: function() {
+ $.Widget.prototype.destroy.call( this );
+ this.element
+ .removeClass("ui-sortable ui-sortable-disabled");
+ this._mouseDestroy();
+
+ for ( var i = this.items.length - 1; i >= 0; i-- )
+ this.items[i].item.removeData(this.widgetName + "-item");
+
+ return this;
+ },
+
+ _setOption: function(key, value){
+ if ( key === "disabled" ) {
+ this.options[ key ] = value;
+
+ this.widget()
+ [ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" );
+ } else {
+ // Don't call widget base _setOption for disable as it adds ui-state-disabled class
+ $.Widget.prototype._setOption.apply(this, arguments);
+ }
+ },
+
+ _mouseCapture: function(event, overrideHandle) {
+ var that = this;
+
+ if (this.reverting) {
+ return false;
+ }
+
+ if(this.options.disabled || this.options.type == 'static') return false;
+
+ //We have to refresh the items data once first
+ this._refreshItems(event);
+
+ //Find out if the clicked node (or one of its parents) is a actual item in this.items
+ var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
+ if($.data(this, that.widgetName + '-item') == self) {
+ currentItem = $(this);
+ return false;
+ }
+ });
+ if($.data(event.target, that.widgetName + '-item') == self) currentItem = $(event.target);
+
+ if(!currentItem) return false;
+ if(this.options.handle && !overrideHandle) {
+ var validHandle = false;
+
+ $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
+ if(!validHandle) return false;
+ }
+
+ this.currentItem = currentItem;
+ this._removeCurrentsFromItems();
+ return true;
+
+ },
+
+ _mouseStart: function(event, overrideHandle, noActivation) {
+
+ var o = this.options, self = this;
+ this.currentContainer = this;
+
+ //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
+ this.refreshPositions();
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Get the next scrolling parent
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.currentItem.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ // Only after we got the offset, we can change the helper's position to absolute
+ // TODO: Still need to figure out a way to make relative sorting possible
+ this.helper.css("position", "absolute");
+ this.cssPosition = this.helper.css("position");
+
+ //Generate the original position
+ this.originalPosition = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Cache the former DOM position
+ this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
+
+ //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
+ if(this.helper[0] != this.currentItem[0]) {
+ this.currentItem.hide();
+ }
+
+ //Create the placeholder
+ this._createPlaceholder();
+
+ //Set a containment if given in the options
+ if(o.containment)
+ this._setContainment();
+
+ if(o.cursor) { // cursor option
+ if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
+ $('body').css("cursor", o.cursor);
+ }
+
+ if(o.opacity) { // opacity option
+ if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
+ this.helper.css("opacity", o.opacity);
+ }
+
+ if(o.zIndex) { // zIndex option
+ if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
+ this.helper.css("zIndex", o.zIndex);
+ }
+
+ //Prepare scrolling
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
+ this.overflowOffset = this.scrollParent.offset();
+
+ //Call callbacks
+ this._trigger("start", event, this._uiHash());
+
+ //Recache the helper size
+ if(!this._preserveHelperProportions)
+ this._cacheHelperProportions();
+
+
+ //Post 'activate' events to possible containers
+ if(!noActivation) {
+ for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
+ }
+
+ //Prepare possible droppables
+ if($.ui.ddmanager)
+ $.ui.ddmanager.current = this;
+
+ if ($.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+
+ this.dragging = true;
+
+ this.helper.addClass("ui-sortable-helper");
+ this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+ return true;
+
+ },
+
+ _mouseDrag: function(event) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ if (!this.lastPositionAbs) {
+ this.lastPositionAbs = this.positionAbs;
+ }
+
+ //Do scrolling
+ if(this.options.scroll) {
+ var o = this.options, scrolled = false;
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
+
+ if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
+ else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
+
+ if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
+ else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
+
+ } else {
+
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+ }
+
+ //Regenerate the absolute position used for position checks
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Set the helper position
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
+
+ //Rearrange
+ for (var i = this.items.length - 1; i >= 0; i--) {
+
+ //Cache variables and intersection, continue if no intersection
+ var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
+ if (!intersection) continue;
+
+ if(itemElement != this.currentItem[0] //cannot intersect with itself
+ && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
+ && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
+ && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
+ //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
+ ) {
+
+ this.direction = intersection == 1 ? "down" : "up";
+
+ if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
+ this._rearrange(event, item);
+ } else {
+ break;
+ }
+
+ this._trigger("change", event, this._uiHash());
+ break;
+ }
+ }
+
+ //Post events to containers
+ this._contactContainers(event);
+
+ //Interconnect with droppables
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
+
+ //Call callbacks
+ this._trigger('sort', event, this._uiHash());
+
+ this.lastPositionAbs = this.positionAbs;
+ return false;
+
+ },
+
+ _mouseStop: function(event, noPropagation) {
+
+ if(!event) return;
+
+ //If we are using droppables, inform the manager about the drop
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
+ $.ui.ddmanager.drop(this, event);
+
+ if(this.options.revert) {
+ var self = this;
+ var cur = self.placeholder.offset();
+
+ self.reverting = true;
+
+ $(this.helper).animate({
+ left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
+ top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
+ }, parseInt(this.options.revert, 10) || 500, function() {
+ self._clear(event);
+ });
+ } else {
+ this._clear(event, noPropagation);
+ }
+
+ return false;
+
+ },
+
+ cancel: function() {
+
+ var self = this;
+
+ if(this.dragging) {
+
+ this._mouseUp({ target: null });
+
+ if(this.options.helper == "original")
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ else
+ this.currentItem.show();
+
+ //Post deactivating events to containers
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ this.containers[i]._trigger("deactivate", null, self._uiHash(this));
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", null, self._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ if (this.placeholder) {
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+ if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
+
+ $.extend(this, {
+ helper: null,
+ dragging: false,
+ reverting: false,
+ _noFinalSort: null
+ });
+
+ if(this.domPosition.prev) {
+ $(this.domPosition.prev).after(this.currentItem);
+ } else {
+ $(this.domPosition.parent).prepend(this.currentItem);
+ }
+ }
+
+ return this;
+
+ },
+
+ serialize: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected);
+ var str = []; o = o || {};
+
+ $(items).each(function() {
+ var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
+ if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
+ });
+
+ if(!str.length && o.key) {
+ str.push(o.key + '=');
+ }
+
+ return str.join('&');
+
+ },
+
+ toArray: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected);
+ var ret = []; o = o || {};
+
+ items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
+ return ret;
+
+ },
+
+ /* Be careful with the following core functions */
+ _intersectsWith: function(item) {
+
+ var x1 = this.positionAbs.left,
+ x2 = x1 + this.helperProportions.width,
+ y1 = this.positionAbs.top,
+ y2 = y1 + this.helperProportions.height;
+
+ var l = item.left,
+ r = l + item.width,
+ t = item.top,
+ b = t + item.height;
+
+ var dyClick = this.offset.click.top,
+ dxClick = this.offset.click.left;
+
+ var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
+
+ if( this.options.tolerance == "pointer"
+ || this.options.forcePointerForContainers
+ || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
+ ) {
+ return isOverElement;
+ } else {
+
+ return (l < x1 + (this.helperProportions.width / 2) // Right Half
+ && x2 - (this.helperProportions.width / 2) < r // Left Half
+ && t < y1 + (this.helperProportions.height / 2) // Bottom Half
+ && y2 - (this.helperProportions.height / 2) < b ); // Top Half
+
+ }
+ },
+
+ _intersectsWithPointer: function(item) {
+
+ var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
+ isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
+ isOverElement = isOverElementHeight && isOverElementWidth,
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (!isOverElement)
+ return false;
+
+ return this.floating ?
+ ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
+ : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
+
+ },
+
+ _intersectsWithSides: function(item) {
+
+ var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
+ isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (this.floating && horizontalDirection) {
+ return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
+ } else {
+ return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
+ }
+
+ },
+
+ _getDragVerticalDirection: function() {
+ var delta = this.positionAbs.top - this.lastPositionAbs.top;
+ return delta != 0 && (delta > 0 ? "down" : "up");
+ },
+
+ _getDragHorizontalDirection: function() {
+ var delta = this.positionAbs.left - this.lastPositionAbs.left;
+ return delta != 0 && (delta > 0 ? "right" : "left");
+ },
+
+ refresh: function(event) {
+ this._refreshItems(event);
+ this.refreshPositions();
+ return this;
+ },
+
+ _connectWith: function() {
+ var options = this.options;
+ return options.connectWith.constructor == String
+ ? [options.connectWith]
+ : options.connectWith;
+ },
+
+ _getItemsAsjQuery: function(connected) {
+
+ var self = this;
+ var items = [];
+ var queries = [];
+ var connectWith = this._connectWith();
+
+ if(connectWith && connected) {
+ for (var i = connectWith.length - 1; i >= 0; i--){
+ var cur = $(connectWith[i]);
+ for (var j = cur.length - 1; j >= 0; j--){
+ var inst = $.data(cur[j], this.widgetName);
+ if(inst && inst != this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
+ }
+ };
+ };
+ }
+
+ queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);
+
+ for (var i = queries.length - 1; i >= 0; i--){
+ queries[i][0].each(function() {
+ items.push(this);
+ });
+ };
+
+ return $(items);
+
+ },
+
+ _removeCurrentsFromItems: function() {
+
+ var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
+
+ for (var i=0; i < this.items.length; i++) {
+
+ for (var j=0; j < list.length; j++) {
+ if(list[j] == this.items[i].item[0])
+ this.items.splice(i,1);
+ };
+
+ };
+
+ },
+
+ _refreshItems: function(event) {
+
+ this.items = [];
+ this.containers = [this];
+ var items = this.items;
+ var self = this;
+ var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
+ var connectWith = this._connectWith();
+
+ if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
+ for (var i = connectWith.length - 1; i >= 0; i--){
+ var cur = $(connectWith[i]);
+ for (var j = cur.length - 1; j >= 0; j--){
+ var inst = $.data(cur[j], this.widgetName);
+ if(inst && inst != this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
+ this.containers.push(inst);
+ }
+ };
+ };
+ }
+
+ for (var i = queries.length - 1; i >= 0; i--) {
+ var targetData = queries[i][1];
+ var _queries = queries[i][0];
+
+ for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
+ var item = $(_queries[j]);
+
+ item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
+
+ items.push({
+ item: item,
+ instance: targetData,
+ width: 0, height: 0,
+ left: 0, top: 0
+ });
+ };
+ };
+
+ },
+
+ refreshPositions: function(fast) {
+
+ //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
+ if(this.offsetParent && this.helper) {
+ this.offset.parent = this._getParentOffset();
+ }
+
+ for (var i = this.items.length - 1; i >= 0; i--){
+ var item = this.items[i];
+
+ //We ignore calculating positions of all connected containers when we're not over them
+ if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
+ continue;
+
+ var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
+
+ if (!fast) {
+ item.width = t.outerWidth();
+ item.height = t.outerHeight();
+ }
+
+ var p = t.offset();
+ item.left = p.left;
+ item.top = p.top;
+ };
+
+ if(this.options.custom && this.options.custom.refreshContainers) {
+ this.options.custom.refreshContainers.call(this);
+ } else {
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ var p = this.containers[i].element.offset();
+ this.containers[i].containerCache.left = p.left;
+ this.containers[i].containerCache.top = p.top;
+ this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
+ this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
+ };
+ }
+
+ return this;
+ },
+
+ _createPlaceholder: function(that) {
+
+ var self = that || this, o = self.options;
+
+ if(!o.placeholder || o.placeholder.constructor == String) {
+ var className = o.placeholder;
+ o.placeholder = {
+ element: function() {
+
+ var el = $(document.createElement(self.currentItem[0].nodeName))
+ .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
+ .removeClass("ui-sortable-helper")[0];
+
+ if(!className)
+ el.style.visibility = "hidden";
+
+ return el;
+ },
+ update: function(container, p) {
+
+ // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
+ // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
+ if(className && !o.forcePlaceholderSize) return;
+
+ //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
+ if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
+ if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
+ }
+ };
+ }
+
+ //Create the placeholder
+ self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
+
+ //Append it after the actual current item
+ self.currentItem.after(self.placeholder);
+
+ //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+ o.placeholder.update(self, self.placeholder);
+
+ },
+
+ _contactContainers: function(event) {
+
+ // get innermost container that intersects with item
+ var innermostContainer = null, innermostIndex = null;
+
+
+ for (var i = this.containers.length - 1; i >= 0; i--){
+
+ // never consider a container that's located within the item itself
+ if($.ui.contains(this.currentItem[0], this.containers[i].element[0]))
+ continue;
+
+ if(this._intersectsWith(this.containers[i].containerCache)) {
+
+ // if we've already found a container and it's more "inner" than this, then continue
+ if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0]))
+ continue;
+
+ innermostContainer = this.containers[i];
+ innermostIndex = i;
+
+ } else {
+ // container doesn't intersect. trigger "out" event if necessary
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", event, this._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ // if no intersecting containers found, return
+ if(!innermostContainer) return;
+
+ // move the item into the container if it's not there already
+ if(this.containers.length === 1) {
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ } else if(this.currentContainer != this.containers[innermostIndex]) {
+
+ //When entering a new container, we will find the item with the least distance and append our item near it
+ var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top'];
+ for (var j = this.items.length - 1; j >= 0; j--) {
+ if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
+ var cur = this.containers[innermostIndex].floating ? this.items[j].item.offset().left : this.items[j].item.offset().top;
+ if(Math.abs(cur - base) < dist) {
+ dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
+ this.direction = (cur - base > 0) ? 'down' : 'up';
+ }
+ }
+
+ if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
+ return;
+
+ this.currentContainer = this.containers[innermostIndex];
+ itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
+ this._trigger("change", event, this._uiHash());
+ this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
+
+ //Update the placeholder
+ this.options.placeholder.update(this.currentContainer, this.placeholder);
+
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ }
+
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options;
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
+
+ if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
+ $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
+
+ if(helper[0] == this.currentItem[0])
+ this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
+
+ if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
+ if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj == 'string') {
+ obj = obj.split(' ');
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ('left' in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ('right' in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ('top' in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ('bottom' in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
+ po = { top: 0, left: 0 };
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition == "relative") {
+ var p = this.currentItem.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
+ top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var o = this.options;
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
+ 0 - this.offset.relative.left - this.offset.parent.left,
+ 0 - this.offset.relative.top - this.offset.parent.top,
+ $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
+ ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+
+ if(!(/^(document|window|parent)$/).test(o.containment)) {
+ var ce = $(o.containment)[0];
+ var co = $(o.containment).offset();
+ var over = ($(ce).css("overflow") != 'hidden');
+
+ this.containment = [
+ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+ co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+ co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+ co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+ ];
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) pos = this.position;
+ var mod = d == "absolute" ? 1 : -1;
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top // The absolute mouse position
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left // The absolute mouse position
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ // This is another very weird special case that only happens for relative elements:
+ // 1. If the css position is relative
+ // 2. and the scroll parent is the document or similar to the offset parent
+ // we have to refresh the relative offset during the scroll so there are no jumps
+ if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
+ this.offset.relative = this._getRelativeOffset();
+ }
+
+ var pageX = event.pageX;
+ var pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+ if(this.containment) {
+ if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
+ if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
+ }
+
+ if(o.grid) {
+ var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+ pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+ pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY // The absolute mouse position
+ - this.offset.click.top // Click offset (relative to the element)
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX // The absolute mouse position
+ - this.offset.click.left // Click offset (relative to the element)
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _rearrange: function(event, i, a, hardRefresh) {
+
+ a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
+
+ //Various things done here to improve the performance:
+ // 1. we create a setTimeout, that calls refreshPositions
+ // 2. on the instance, we have a counter variable, that get's higher after every append
+ // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
+ // 4. this lets only the last addition to the timeout stack through
+ this.counter = this.counter ? ++this.counter : 1;
+ var self = this, counter = this.counter;
+
+ window.setTimeout(function() {
+ if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+ },0);
+
+ },
+
+ _clear: function(event, noPropagation) {
+
+ this.reverting = false;
+ // We delay all events that have to be triggered to after the point where the placeholder has been removed and
+ // everything else normalized again
+ var delayedTriggers = [], self = this;
+
+ // We first have to update the dom position of the actual currentItem
+ // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
+ if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
+ this._noFinalSort = null;
+
+ if(this.helper[0] == this.currentItem[0]) {
+ for(var i in this._storedCSS) {
+ if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
+ }
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ } else {
+ this.currentItem.show();
+ }
+
+ if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
+ if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
+ if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
+ if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ }
+ };
+ };
+
+ //Post events to containers
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ if(this.containers[i].containerCache.over) {
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ //Do what was originally in plugins
+ if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
+ if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
+ if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
+
+ this.dragging = false;
+ if(this.cancelHelperRemoval) {
+ if(!noPropagation) {
+ this._trigger("beforeStop", event, this._uiHash());
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return false;
+ }
+
+ if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
+
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+
+ if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
+
+ if(!noPropagation) {
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return true;
+
+ },
+
+ _trigger: function() {
+ if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
+ this.cancel();
+ }
+ },
+
+ _uiHash: function(inst) {
+ var self = inst || this;
+ return {
+ helper: self.helper,
+ placeholder: self.placeholder || $([]),
+ position: self.position,
+ originalPosition: self.originalPosition,
+ offset: self.positionAbs,
+ item: self.currentItem,
+ sender: inst ? inst.element : null
+ };
+ }
+
+});
+
+$.extend($.ui.sortable, {
+ version: "1.8.23"
+});
+
+})(jQuery);
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/tabs.js b/module/web/static/js/libs/jqueryui/tabs.js
new file mode 100644
index 000000000..335aadf83
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/tabs.js
@@ -0,0 +1,760 @@
+define(['jquery','./core','./widget'], function (jQuery) {
+/*!
+ * jQuery UI Tabs 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var tabId = 0,
+ listId = 0;
+
+function getNextTabId() {
+ return ++tabId;
+}
+
+function getNextListId() {
+ return ++listId;
+}
+
+$.widget( "ui.tabs", {
+ options: {
+ add: null,
+ ajaxOptions: null,
+ cache: false,
+ cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
+ collapsible: false,
+ disable: null,
+ disabled: [],
+ enable: null,
+ event: "click",
+ fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
+ idPrefix: "ui-tabs-",
+ load: null,
+ panelTemplate: "<div></div>",
+ remove: null,
+ select: null,
+ show: null,
+ spinner: "<em>Loading&#8230;</em>",
+ tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
+ },
+
+ _create: function() {
+ this._tabify( true );
+ },
+
+ _setOption: function( key, value ) {
+ if ( key == "selected" ) {
+ if (this.options.collapsible && value == this.options.selected ) {
+ return;
+ }
+ this.select( value );
+ } else {
+ this.options[ key ] = value;
+ this._tabify();
+ }
+ },
+
+ _tabId: function( a ) {
+ return a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) ||
+ this.options.idPrefix + getNextTabId();
+ },
+
+ _sanitizeSelector: function( hash ) {
+ // we need this because an id may contain a ":"
+ return hash.replace( /:/g, "\\:" );
+ },
+
+ _cookie: function() {
+ var cookie = this.cookie ||
+ ( this.cookie = this.options.cookie.name || "ui-tabs-" + getNextListId() );
+ return $.cookie.apply( null, [ cookie ].concat( $.makeArray( arguments ) ) );
+ },
+
+ _ui: function( tab, panel ) {
+ return {
+ tab: tab,
+ panel: panel,
+ index: this.anchors.index( tab )
+ };
+ },
+
+ _cleanup: function() {
+ // restore all former loading tabs labels
+ this.lis.filter( ".ui-state-processing" )
+ .removeClass( "ui-state-processing" )
+ .find( "span:data(label.tabs)" )
+ .each(function() {
+ var el = $( this );
+ el.html( el.data( "label.tabs" ) ).removeData( "label.tabs" );
+ });
+ },
+
+ _tabify: function( init ) {
+ var self = this,
+ o = this.options,
+ fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
+
+ this.list = this.element.find( "ol,ul" ).eq( 0 );
+ this.lis = $( " > li:has(a[href])", this.list );
+ this.anchors = this.lis.map(function() {
+ return $( "a", this )[ 0 ];
+ });
+ this.panels = $( [] );
+
+ this.anchors.each(function( i, a ) {
+ var href = $( a ).attr( "href" );
+ // For dynamically created HTML that contains a hash as href IE < 8 expands
+ // such href to the full page url with hash and then misinterprets tab as ajax.
+ // Same consideration applies for an added tab with a fragment identifier
+ // since a[href=#fragment-identifier] does unexpectedly not match.
+ // Thus normalize href attribute...
+ var hrefBase = href.split( "#" )[ 0 ],
+ baseEl;
+ if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] ||
+ ( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) {
+ href = a.hash;
+ a.href = href;
+ }
+
+ // inline tab
+ if ( fragmentId.test( href ) ) {
+ self.panels = self.panels.add( self.element.find( self._sanitizeSelector( href ) ) );
+ // remote tab
+ // prevent loading the page itself if href is just "#"
+ } else if ( href && href !== "#" ) {
+ // required for restore on destroy
+ $.data( a, "href.tabs", href );
+
+ // TODO until #3808 is fixed strip fragment identifier from url
+ // (IE fails to load from such url)
+ $.data( a, "load.tabs", href.replace( /#.*$/, "" ) );
+
+ var id = self._tabId( a );
+ a.href = "#" + id;
+ var $panel = self.element.find( "#" + id );
+ if ( !$panel.length ) {
+ $panel = $( o.panelTemplate )
+ .attr( "id", id )
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .insertAfter( self.panels[ i - 1 ] || self.list );
+ $panel.data( "destroy.tabs", true );
+ }
+ self.panels = self.panels.add( $panel );
+ // invalid tab href
+ } else {
+ o.disabled.push( i );
+ }
+ });
+
+ // initialization from scratch
+ if ( init ) {
+ // attach necessary classes for styling
+ this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" );
+ this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );
+ this.lis.addClass( "ui-state-default ui-corner-top" );
+ this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" );
+
+ // Selected tab
+ // use "selected" option or try to retrieve:
+ // 1. from fragment identifier in url
+ // 2. from cookie
+ // 3. from selected class attribute on <li>
+ if ( o.selected === undefined ) {
+ if ( location.hash ) {
+ this.anchors.each(function( i, a ) {
+ if ( a.hash == location.hash ) {
+ o.selected = i;
+ return false;
+ }
+ });
+ }
+ if ( typeof o.selected !== "number" && o.cookie ) {
+ o.selected = parseInt( self._cookie(), 10 );
+ }
+ if ( typeof o.selected !== "number" && this.lis.filter( ".ui-tabs-selected" ).length ) {
+ o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
+ }
+ o.selected = o.selected || ( this.lis.length ? 0 : -1 );
+ } else if ( o.selected === null ) { // usage of null is deprecated, TODO remove in next release
+ o.selected = -1;
+ }
+
+ // sanity check - default to first tab...
+ o.selected = ( ( o.selected >= 0 && this.anchors[ o.selected ] ) || o.selected < 0 )
+ ? o.selected
+ : 0;
+
+ // Take disabling tabs via class attribute from HTML
+ // into account and update option properly.
+ // A selected tab cannot become disabled.
+ o.disabled = $.unique( o.disabled.concat(
+ $.map( this.lis.filter( ".ui-state-disabled" ), function( n, i ) {
+ return self.lis.index( n );
+ })
+ ) ).sort();
+
+ if ( $.inArray( o.selected, o.disabled ) != -1 ) {
+ o.disabled.splice( $.inArray( o.selected, o.disabled ), 1 );
+ }
+
+ // highlight selected tab
+ this.panels.addClass( "ui-tabs-hide" );
+ this.lis.removeClass( "ui-tabs-selected ui-state-active" );
+ // check for length avoids error when initializing empty list
+ if ( o.selected >= 0 && this.anchors.length ) {
+ self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) ).removeClass( "ui-tabs-hide" );
+ this.lis.eq( o.selected ).addClass( "ui-tabs-selected ui-state-active" );
+
+ // seems to be expected behavior that the show callback is fired
+ self.element.queue( "tabs", function() {
+ self._trigger( "show", null,
+ self._ui( self.anchors[ o.selected ], self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) )[ 0 ] ) );
+ });
+
+ this.load( o.selected );
+ }
+
+ // clean up to avoid memory leaks in certain versions of IE 6
+ // TODO: namespace this event
+ $( window ).bind( "unload", function() {
+ self.lis.add( self.anchors ).unbind( ".tabs" );
+ self.lis = self.anchors = self.panels = null;
+ });
+ // update selected after add/remove
+ } else {
+ o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
+ }
+
+ // update collapsible
+ // TODO: use .toggleClass()
+ this.element[ o.collapsible ? "addClass" : "removeClass" ]( "ui-tabs-collapsible" );
+
+ // set or update cookie after init and add/remove respectively
+ if ( o.cookie ) {
+ this._cookie( o.selected, o.cookie );
+ }
+
+ // disable tabs
+ for ( var i = 0, li; ( li = this.lis[ i ] ); i++ ) {
+ $( li )[ $.inArray( i, o.disabled ) != -1 &&
+ // TODO: use .toggleClass()
+ !$( li ).hasClass( "ui-tabs-selected" ) ? "addClass" : "removeClass" ]( "ui-state-disabled" );
+ }
+
+ // reset cache if switching from cached to not cached
+ if ( o.cache === false ) {
+ this.anchors.removeData( "cache.tabs" );
+ }
+
+ // remove all handlers before, tabify may run on existing tabs after add or option change
+ this.lis.add( this.anchors ).unbind( ".tabs" );
+
+ if ( o.event !== "mouseover" ) {
+ var addState = function( state, el ) {
+ if ( el.is( ":not(.ui-state-disabled)" ) ) {
+ el.addClass( "ui-state-" + state );
+ }
+ };
+ var removeState = function( state, el ) {
+ el.removeClass( "ui-state-" + state );
+ };
+ this.lis.bind( "mouseover.tabs" , function() {
+ addState( "hover", $( this ) );
+ });
+ this.lis.bind( "mouseout.tabs", function() {
+ removeState( "hover", $( this ) );
+ });
+ this.anchors.bind( "focus.tabs", function() {
+ addState( "focus", $( this ).closest( "li" ) );
+ });
+ this.anchors.bind( "blur.tabs", function() {
+ removeState( "focus", $( this ).closest( "li" ) );
+ });
+ }
+
+ // set up animations
+ var hideFx, showFx;
+ if ( o.fx ) {
+ if ( $.isArray( o.fx ) ) {
+ hideFx = o.fx[ 0 ];
+ showFx = o.fx[ 1 ];
+ } else {
+ hideFx = showFx = o.fx;
+ }
+ }
+
+ // Reset certain styles left over from animation
+ // and prevent IE's ClearType bug...
+ function resetStyle( $el, fx ) {
+ $el.css( "display", "" );
+ if ( !$.support.opacity && fx.opacity ) {
+ $el[ 0 ].style.removeAttribute( "filter" );
+ }
+ }
+
+ // Show a tab...
+ var showTab = showFx
+ ? function( clicked, $show ) {
+ $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
+ $show.hide().removeClass( "ui-tabs-hide" ) // avoid flicker that way
+ .animate( showFx, showFx.duration || "normal", function() {
+ resetStyle( $show, showFx );
+ self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
+ });
+ }
+ : function( clicked, $show ) {
+ $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
+ $show.removeClass( "ui-tabs-hide" );
+ self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
+ };
+
+ // Hide a tab, $show is optional...
+ var hideTab = hideFx
+ ? function( clicked, $hide ) {
+ $hide.animate( hideFx, hideFx.duration || "normal", function() {
+ self.lis.removeClass( "ui-tabs-selected ui-state-active" );
+ $hide.addClass( "ui-tabs-hide" );
+ resetStyle( $hide, hideFx );
+ self.element.dequeue( "tabs" );
+ });
+ }
+ : function( clicked, $hide, $show ) {
+ self.lis.removeClass( "ui-tabs-selected ui-state-active" );
+ $hide.addClass( "ui-tabs-hide" );
+ self.element.dequeue( "tabs" );
+ };
+
+ // attach tab event handler, unbind to avoid duplicates from former tabifying...
+ this.anchors.bind( o.event + ".tabs", function() {
+ var el = this,
+ $li = $(el).closest( "li" ),
+ $hide = self.panels.filter( ":not(.ui-tabs-hide)" ),
+ $show = self.element.find( self._sanitizeSelector( el.hash ) );
+
+ // If tab is already selected and not collapsible or tab disabled or
+ // or is already loading or click callback returns false stop here.
+ // Check if click handler returns false last so that it is not executed
+ // for a disabled or loading tab!
+ if ( ( $li.hasClass( "ui-tabs-selected" ) && !o.collapsible) ||
+ $li.hasClass( "ui-state-disabled" ) ||
+ $li.hasClass( "ui-state-processing" ) ||
+ self.panels.filter( ":animated" ).length ||
+ self._trigger( "select", null, self._ui( this, $show[ 0 ] ) ) === false ) {
+ this.blur();
+ return false;
+ }
+
+ o.selected = self.anchors.index( this );
+
+ self.abort();
+
+ // if tab may be closed
+ if ( o.collapsible ) {
+ if ( $li.hasClass( "ui-tabs-selected" ) ) {
+ o.selected = -1;
+
+ if ( o.cookie ) {
+ self._cookie( o.selected, o.cookie );
+ }
+
+ self.element.queue( "tabs", function() {
+ hideTab( el, $hide );
+ }).dequeue( "tabs" );
+
+ this.blur();
+ return false;
+ } else if ( !$hide.length ) {
+ if ( o.cookie ) {
+ self._cookie( o.selected, o.cookie );
+ }
+
+ self.element.queue( "tabs", function() {
+ showTab( el, $show );
+ });
+
+ // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171
+ self.load( self.anchors.index( this ) );
+
+ this.blur();
+ return false;
+ }
+ }
+
+ if ( o.cookie ) {
+ self._cookie( o.selected, o.cookie );
+ }
+
+ // show new tab
+ if ( $show.length ) {
+ if ( $hide.length ) {
+ self.element.queue( "tabs", function() {
+ hideTab( el, $hide );
+ });
+ }
+ self.element.queue( "tabs", function() {
+ showTab( el, $show );
+ });
+
+ self.load( self.anchors.index( this ) );
+ } else {
+ throw "jQuery UI Tabs: Mismatching fragment identifier.";
+ }
+
+ // Prevent IE from keeping other link focussed when using the back button
+ // and remove dotted border from clicked link. This is controlled via CSS
+ // in modern browsers; blur() removes focus from address bar in Firefox
+ // which can become a usability and annoying problem with tabs('rotate').
+ if ( $.browser.msie ) {
+ this.blur();
+ }
+ });
+
+ // disable click in any case
+ this.anchors.bind( "click.tabs", function(){
+ return false;
+ });
+ },
+
+ _getIndex: function( index ) {
+ // meta-function to give users option to provide a href string instead of a numerical index.
+ // also sanitizes numerical indexes to valid values.
+ if ( typeof index == "string" ) {
+ index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
+ }
+
+ return index;
+ },
+
+ destroy: function() {
+ var o = this.options;
+
+ this.abort();
+
+ this.element
+ .unbind( ".tabs" )
+ .removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" )
+ .removeData( "tabs" );
+
+ this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );
+
+ this.anchors.each(function() {
+ var href = $.data( this, "href.tabs" );
+ if ( href ) {
+ this.href = href;
+ }
+ var $this = $( this ).unbind( ".tabs" );
+ $.each( [ "href", "load", "cache" ], function( i, prefix ) {
+ $this.removeData( prefix + ".tabs" );
+ });
+ });
+
+ this.lis.unbind( ".tabs" ).add( this.panels ).each(function() {
+ if ( $.data( this, "destroy.tabs" ) ) {
+ $( this ).remove();
+ } else {
+ $( this ).removeClass([
+ "ui-state-default",
+ "ui-corner-top",
+ "ui-tabs-selected",
+ "ui-state-active",
+ "ui-state-hover",
+ "ui-state-focus",
+ "ui-state-disabled",
+ "ui-tabs-panel",
+ "ui-widget-content",
+ "ui-corner-bottom",
+ "ui-tabs-hide"
+ ].join( " " ) );
+ }
+ });
+
+ if ( o.cookie ) {
+ this._cookie( null, o.cookie );
+ }
+
+ return this;
+ },
+
+ add: function( url, label, index ) {
+ if ( index === undefined ) {
+ index = this.anchors.length;
+ }
+
+ var self = this,
+ o = this.options,
+ $li = $( o.tabTemplate.replace( /#\{href\}/g, url ).replace( /#\{label\}/g, label ) ),
+ id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : this._tabId( $( "a", $li )[ 0 ] );
+
+ $li.addClass( "ui-state-default ui-corner-top" ).data( "destroy.tabs", true );
+
+ // try to find an existing element before creating a new one
+ var $panel = self.element.find( "#" + id );
+ if ( !$panel.length ) {
+ $panel = $( o.panelTemplate )
+ .attr( "id", id )
+ .data( "destroy.tabs", true );
+ }
+ $panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" );
+
+ if ( index >= this.lis.length ) {
+ $li.appendTo( this.list );
+ $panel.appendTo( this.list[ 0 ].parentNode );
+ } else {
+ $li.insertBefore( this.lis[ index ] );
+ $panel.insertBefore( this.panels[ index ] );
+ }
+
+ o.disabled = $.map( o.disabled, function( n, i ) {
+ return n >= index ? ++n : n;
+ });
+
+ this._tabify();
+
+ if ( this.anchors.length == 1 ) {
+ o.selected = 0;
+ $li.addClass( "ui-tabs-selected ui-state-active" );
+ $panel.removeClass( "ui-tabs-hide" );
+ this.element.queue( "tabs", function() {
+ self._trigger( "show", null, self._ui( self.anchors[ 0 ], self.panels[ 0 ] ) );
+ });
+
+ this.load( 0 );
+ }
+
+ this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ return this;
+ },
+
+ remove: function( index ) {
+ index = this._getIndex( index );
+ var o = this.options,
+ $li = this.lis.eq( index ).remove(),
+ $panel = this.panels.eq( index ).remove();
+
+ // If selected tab was removed focus tab to the right or
+ // in case the last tab was removed the tab to the left.
+ if ( $li.hasClass( "ui-tabs-selected" ) && this.anchors.length > 1) {
+ this.select( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
+ }
+
+ o.disabled = $.map(
+ $.grep( o.disabled, function(n, i) {
+ return n != index;
+ }),
+ function( n, i ) {
+ return n >= index ? --n : n;
+ });
+
+ this._tabify();
+
+ this._trigger( "remove", null, this._ui( $li.find( "a" )[ 0 ], $panel[ 0 ] ) );
+ return this;
+ },
+
+ enable: function( index ) {
+ index = this._getIndex( index );
+ var o = this.options;
+ if ( $.inArray( index, o.disabled ) == -1 ) {
+ return;
+ }
+
+ this.lis.eq( index ).removeClass( "ui-state-disabled" );
+ o.disabled = $.grep( o.disabled, function( n, i ) {
+ return n != index;
+ });
+
+ this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ return this;
+ },
+
+ disable: function( index ) {
+ index = this._getIndex( index );
+ var self = this, o = this.options;
+ // cannot disable already selected tab
+ if ( index != o.selected ) {
+ this.lis.eq( index ).addClass( "ui-state-disabled" );
+
+ o.disabled.push( index );
+ o.disabled.sort();
+
+ this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ }
+
+ return this;
+ },
+
+ select: function( index ) {
+ index = this._getIndex( index );
+ if ( index == -1 ) {
+ if ( this.options.collapsible && this.options.selected != -1 ) {
+ index = this.options.selected;
+ } else {
+ return this;
+ }
+ }
+ this.anchors.eq( index ).trigger( this.options.event + ".tabs" );
+ return this;
+ },
+
+ load: function( index ) {
+ index = this._getIndex( index );
+ var self = this,
+ o = this.options,
+ a = this.anchors.eq( index )[ 0 ],
+ url = $.data( a, "load.tabs" );
+
+ this.abort();
+
+ // not remote or from cache
+ if ( !url || this.element.queue( "tabs" ).length !== 0 && $.data( a, "cache.tabs" ) ) {
+ this.element.dequeue( "tabs" );
+ return;
+ }
+
+ // load remote from here on
+ this.lis.eq( index ).addClass( "ui-state-processing" );
+
+ if ( o.spinner ) {
+ var span = $( "span", a );
+ span.data( "label.tabs", span.html() ).html( o.spinner );
+ }
+
+ this.xhr = $.ajax( $.extend( {}, o.ajaxOptions, {
+ url: url,
+ success: function( r, s ) {
+ self.element.find( self._sanitizeSelector( a.hash ) ).html( r );
+
+ // take care of tab labels
+ self._cleanup();
+
+ if ( o.cache ) {
+ $.data( a, "cache.tabs", true );
+ }
+
+ self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
+ try {
+ o.ajaxOptions.success( r, s );
+ }
+ catch ( e ) {}
+ },
+ error: function( xhr, s, e ) {
+ // take care of tab labels
+ self._cleanup();
+
+ self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
+ try {
+ // Passing index avoid a race condition when this method is
+ // called after the user has selected another tab.
+ // Pass the anchor that initiated this request allows
+ // loadError to manipulate the tab content panel via $(a.hash)
+ o.ajaxOptions.error( xhr, s, index, a );
+ }
+ catch ( e ) {}
+ }
+ } ) );
+
+ // last, so that load event is fired before show...
+ self.element.dequeue( "tabs" );
+
+ return this;
+ },
+
+ abort: function() {
+ // stop possibly running animations
+ this.element.queue( [] );
+ this.panels.stop( false, true );
+
+ // "tabs" queue must not contain more than two elements,
+ // which are the callbacks for the latest clicked tab...
+ this.element.queue( "tabs", this.element.queue( "tabs" ).splice( -2, 2 ) );
+
+ // terminate pending requests from other tabs
+ if ( this.xhr ) {
+ this.xhr.abort();
+ delete this.xhr;
+ }
+
+ // take care of tab labels
+ this._cleanup();
+ return this;
+ },
+
+ url: function( index, url ) {
+ this.anchors.eq( index ).removeData( "cache.tabs" ).data( "load.tabs", url );
+ return this;
+ },
+
+ length: function() {
+ return this.anchors.length;
+ }
+});
+
+$.extend( $.ui.tabs, {
+ version: "1.8.23"
+});
+
+/*
+ * Tabs Extensions
+ */
+
+/*
+ * Rotate
+ */
+$.extend( $.ui.tabs.prototype, {
+ rotation: null,
+ rotate: function( ms, continuing ) {
+ var self = this,
+ o = this.options;
+
+ var rotate = self._rotate || ( self._rotate = function( e ) {
+ clearTimeout( self.rotation );
+ self.rotation = setTimeout(function() {
+ var t = o.selected;
+ self.select( ++t < self.anchors.length ? t : 0 );
+ }, ms );
+
+ if ( e ) {
+ e.stopPropagation();
+ }
+ });
+
+ var stop = self._unrotate || ( self._unrotate = !continuing
+ ? function(e) {
+ if (e.clientX) { // in case of a true click
+ self.rotate(null);
+ }
+ }
+ : function( e ) {
+ rotate();
+ });
+
+ // start rotation
+ if ( ms ) {
+ this.element.bind( "tabsshow", rotate );
+ this.anchors.bind( o.event + ".tabs", stop );
+ rotate();
+ // stop rotation
+ } else {
+ clearTimeout( self.rotation );
+ this.element.unbind( "tabsshow", rotate );
+ this.anchors.unbind( o.event + ".tabs", stop );
+ delete this._rotate;
+ delete this._unrotate;
+ }
+
+ return this;
+ }
+});
+
+})( jQuery );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/jqueryui/widget.js b/module/web/static/js/libs/jqueryui/widget.js
new file mode 100644
index 000000000..9c6dc93a8
--- /dev/null
+++ b/module/web/static/js/libs/jqueryui/widget.js
@@ -0,0 +1,275 @@
+define(['jquery'], function (jQuery) {
+/*!
+ * jQuery UI Widget 1.8.23
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function( $, undefined ) {
+
+// jQuery 1.4+
+if ( $.cleanData ) {
+ var _cleanData = $.cleanData;
+ $.cleanData = function( elems ) {
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ try {
+ $( elem ).triggerHandler( "remove" );
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ }
+ _cleanData( elems );
+ };
+} else {
+ var _remove = $.fn.remove;
+ $.fn.remove = function( selector, keepData ) {
+ return this.each(function() {
+ if ( !keepData ) {
+ if ( !selector || $.filter( selector, [ this ] ).length ) {
+ $( "*", this ).add( [ this ] ).each(function() {
+ try {
+ $( this ).triggerHandler( "remove" );
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ });
+ }
+ }
+ return _remove.call( $(this), selector, keepData );
+ });
+ };
+}
+
+$.widget = function( name, base, prototype ) {
+ var namespace = name.split( "." )[ 0 ],
+ fullName;
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName ] = function( elem ) {
+ return !!$.data( elem, name );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without initializing for simple inheritance
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+
+ var basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+// $.each( basePrototype, function( key, val ) {
+// if ( $.isPlainObject(val) ) {
+// basePrototype[ key ] = $.extend( {}, val );
+// }
+// });
+ basePrototype.options = $.extend( true, {}, basePrototype.options );
+ $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
+ namespace: namespace,
+ widgetName: name,
+ widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
+ widgetBaseClass: fullName
+ }, prototype );
+
+ $.widget.bridge( name, $[ namespace ][ name ] );
+};
+
+$.widget.bridge = function( name, object ) {
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = Array.prototype.slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.extend.apply( null, [ true, options ].concat(args) ) :
+ options;
+
+ // prevent calls to internal methods
+ if ( isMethodCall && options.charAt( 0 ) === "_" ) {
+ return returnValue;
+ }
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var instance = $.data( this, name ),
+ methodValue = instance && $.isFunction( instance[options] ) ?
+ instance[ options ].apply( instance, args ) :
+ instance;
+ // TODO: add this back in 1.9 and use $.error() (see #5972)
+// if ( !instance ) {
+// throw "cannot call methods on " + name + " prior to initialization; " +
+// "attempted to call method '" + options + "'";
+// }
+// if ( !$.isFunction( instance[options] ) ) {
+// throw "no such method '" + options + "' for " + name + " widget instance";
+// }
+// var methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, name );
+ if ( instance ) {
+ instance.option( options || {} )._init();
+ } else {
+ $.data( this, name, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( options, element ) {
+ // allow instantiation without initializing for simple inheritance
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+};
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ options: {
+ disabled: false
+ },
+ _createWidget: function( options, element ) {
+ // $.widget.bridge stores the plugin instance, but we do it anyway
+ // so that it's stored even before the _create function runs
+ $.data( element, this.widgetName, this );
+ this.element = $( element );
+ this.options = $.extend( true, {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ var self = this;
+ this.element.bind( "remove." + this.widgetName, function() {
+ self.destroy();
+ });
+
+ this._create();
+ this._trigger( "create" );
+ this._init();
+ },
+ _getCreateOptions: function() {
+ return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+ },
+ _create: function() {},
+ _init: function() {},
+
+ destroy: function() {
+ this.element
+ .unbind( "." + this.widgetName )
+ .removeData( this.widgetName );
+ this.widget()
+ .unbind( "." + this.widgetName )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetBaseClass + "-disabled " +
+ "ui-state-disabled" );
+ },
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.extend( {}, this.options );
+ }
+
+ if (typeof key === "string" ) {
+ if ( value === undefined ) {
+ return this.options[ key ];
+ }
+ options = {};
+ options[ key ] = value;
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var self = this;
+ $.each( options, function( key, value ) {
+ self._setOption( key, value );
+ });
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ [ value ? "addClass" : "removeClass"](
+ this.widgetBaseClass + "-disabled" + " " +
+ "ui-state-disabled" )
+ .attr( "aria-disabled", value );
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+
+ return !( $.isFunction(callback) &&
+ callback.call( this.element[0], event, data ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+})( jQuery );
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/libs/less-1.3.0.min.js b/module/web/static/js/libs/less-1.3.0.min.js
new file mode 100644
index 000000000..309bf550d
--- /dev/null
+++ b/module/web/static/js/libs/less-1.3.0.min.js
@@ -0,0 +1,9 @@
+//
+// LESS - Leaner CSS v1.3.0
+// http://lesscss.org
+//
+// Copyright (c) 2009-2011, Alexis Sellier
+// Licensed under the Apache 2.0 License.
+//
+(function(a,b){function c(b){return a.less[b.split("/")[1]]}function l(){var a=document.getElementsByTagName("style");for(var b=0;b<a.length;b++)a[b].type.match(j)&&(new d.Parser).parse(a[b].innerHTML||"",function(c,d){var e=d.toCSS(),f=a[b];f.type="text/css",f.styleSheet?f.styleSheet.cssText=e:f.innerHTML=e})}function m(a,b){for(var c=0;c<d.sheets.length;c++)n(d.sheets[c],a,b,d.sheets.length-(c+1))}function n(b,c,e,f){var h=a.location.href.replace(/[#?].*$/,""),i=b.href.replace(/\?.*$/,""),j=g&&g.getItem(i),k=g&&g.getItem(i+":timestamp"),l={css:j,timestamp:k};/^(https?|file):/.test(i)||(i.charAt(0)=="/"?i=a.location.protocol+"//"+a.location.host+i:i=h.slice(0,h.lastIndexOf("/")+1)+i);var m=i.match(/([^\/]+)$/)[1];q(b.href,b.type,function(a,g){if(!e&&l&&g&&(new Date(g)).valueOf()===(new Date(l.timestamp)).valueOf())p(l.css,b),c(null,null,a,b,{local:!0,remaining:f});else try{(new d.Parser({optimization:d.optimization,paths:[i.replace(/[\w\.-]+$/,"")],mime:b.type,filename:m})).parse(a,function(d,e){if(d)return u(d,i);try{c(d,e,a,b,{local:!1,lastModified:g,remaining:f}),s(document.getElementById("less-error-message:"+o(i)))}catch(d){u(d,i)}})}catch(h){u(h,i)}},function(a,b){throw new Error("Couldn't load "+b+" ("+a+")")})}function o(a){return a.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\?.*$/,"").replace(/\.[^\.\/]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function p(a,b,c){var d,e=b.href?b.href.replace(/\?.*$/,""):"",f="less:"+(b.title||o(e));(d=document.getElementById(f))===null&&(d=document.createElement("style"),d.type="text/css",d.media=b.media||"screen",d.id=f,document.getElementsByTagName("head")[0].appendChild(d));if(d.styleSheet)try{d.styleSheet.cssText=a}catch(h){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(a){d.childNodes.length>0?d.firstChild.nodeValue!==a.nodeValue&&d.replaceChild(a,d.firstChild):d.appendChild(a)})(document.createTextNode(a));c&&g&&(t("saving "+e+" to cache."),g.setItem(e,a),g.setItem(e+":timestamp",c))}function q(a,b,c,e){function i(b,c,d){b.status>=200&&b.status<300?c(b.responseText,b.getResponseHeader("Last-Modified")):typeof d=="function"&&d(b.status,a)}var g=r(),h=f?!1:d.async;typeof g.overrideMimeType=="function"&&g.overrideMimeType("text/css"),g.open("GET",a,h),g.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5"),g.send(null),f?g.status===0||g.status>=200&&g.status<300?c(g.responseText):e(g.status,a):h?g.onreadystatechange=function(){g.readyState==4&&i(g,c,e)}:i(g,c,e)}function r(){if(a.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(b){return t("browser doesn't support AJAX."),null}}function s(a){return a&&a.parentNode.removeChild(a)}function t(a){d.env=="development"&&typeof console!="undefined"&&console.log("less: "+a)}function u(a,b){var c="less-error-message:"+o(b),e='<li><label>{line}</label><pre class="{class}">{content}</pre></li>',f=document.createElement("div"),g,h,i=[],j=a.filename||b;f.id=c,f.className="less-error-message",h="<h3>"+(a.message||"There is an error in your .less file")+"</h3>"+'<p>in <a href="'+j+'">'+j+"</a> ";var k=function(a,b,c){a.extract[b]&&i.push(e.replace(/\{line\}/,parseInt(a.line)+(b-1)).replace(/\{class\}/,c).replace(/\{content\}/,a.extract[b]))};a.stack?h+="<br/>"+a.stack.split("\n").slice(1).join("<br/>"):a.extract&&(k(a,0,""),k(a,1,"line"),k(a,2,""),h+="on line "+a.line+", column "+(a.column+1)+":</p>"+"<ul>"+i.join("")+"</ul>"),f.innerHTML=h,p([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),f.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),d.env=="development"&&(g=setInterval(function(){document.body&&(document.getElementById(c)?document.body.replaceChild(f,document.getElementById(c)):document.body.insertBefore(f,document.body.firstChild),clearInterval(g))},10))}typeof define=="function"&&define.amd&&define("less",[],function(){return d}),Array.isArray||(Array.isArray=function(a){return Object.prototype.toString.call(a)==="[object Array]"||a instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){var c=this.length>>>0;for(var d=0;d<c;d++)d in this&&a.call(b,this[d],d,this)}),Array.prototype.map||(Array.prototype.map=function(a){var b=this.length>>>0,c=new Array(b),d=arguments[1];for(var e=0;e<b;e++)e in this&&(c[e]=a.call(d,this[e],e,this));return c}),Array.prototype.filter||(Array.prototype.filter=function(a){var b=[],c=arguments[1];for(var d=0;d<this.length;d++)a.call(c,this[d])&&b.push(this[d]);return b}),Array.prototype.reduce||(Array.prototype.reduce=function(a){var b=this.length>>>0,c=0;if(b===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var d=arguments[1];else do{if(c in this){d=this[c++];break}if(++c>=b)throw new TypeError}while(!0);for(;c<b;c++)c in this&&(d=a.call(null,d,this[c],c,this));return d}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){var b=this.length,c=arguments[1]||0;if(!b)return-1;if(c>=b)return-1;c<0&&(c+=b);for(;c<b;c++){if(!Object.prototype.hasOwnProperty.call(this,c))continue;if(a===this[c])return c}return-1}),Object.keys||(Object.keys=function(a){var b=[];for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&b.push(c);return b}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")});var d,e;typeof environment=="object"&&{}.toString.call(environment)==="[object Environment]"?(typeof a=="undefined"?d={}:d=a.less={},e=d.tree={},d.mode="rhino"):typeof a=="undefined"?(d=exports,e=c("./tree"),d.mode="node"):(typeof a.less=="undefined"&&(a.less={}),d=a.less,e=a.less.tree={},d.mode="browser"),d.Parser=function v(a){function q(){h=k[g],i=f,l=f}function r(){k[g]=h,f=i,l=f}function s(){f>l&&(k[g]=k[g].slice(f-l),l=f)}function t(a){var c,d,e,h,i,j,n,o;if(a instanceof Function)return a.call(m.parsers);if(typeof a=="string")c=b.charAt(f)===a?a:null,e=1,s();else{s();if(c=a.exec(k[g]))e=c[0].length;else return null}if(c){o=f+=e,j=f+k[g].length-e;while(f<j){h=b.charCodeAt(f);if(h!==32&&h!==10&&h!==9)break;f++}return k[g]=k[g].slice(e+(f-o)),l=f,k[g].length===0&&g<k.length-1&&g++,typeof c=="string"?c:c.length===1?c[0]:c}}function u(a,c){var d=t(a);if(!d)v(c||(typeof a=="string"?"expected '"+a+"' got '"+b.charAt(f)+"'":"unexpected token"));else return d}function v(a,b){throw{index:f,type:b||"Syntax",message:a}}function w(a){return typeof a=="string"?b.charAt(f)===a:a.test(k[g])?!0:!1}function x(a){return d.mode==="node"?c("path").basename(a):a.match(/[^\/]+$/)[0]}function y(a,c){return a.filename&&c.filename&&a.filename!==c.filename?m.imports.contents[x(a.filename)]:b}function z(a,b){for(var c=a,d=-1;c>=0&&b.charAt(c)!=="\n";c--)d++;return{line:typeof a=="number"?(b.slice(0,a).match(/\n/g)||"").length:null,column:d}}function A(a,b){var c=y(a,b),d=z(a.index,c),e=d.line,f=d.column,g=c.split("\n");this.type=a.type||"Syntax",this.message=a.message,this.filename=a.filename||b.filename,this.index=a.index,this.line=typeof e=="number"?e+1:null,this.callLine=a.call&&z(a.call,c).line+1,this.callExtract=g[z(a.call,c).line],this.stack=a.stack,this.column=f,this.extract=[g[e-1],g[e],g[e+1]]}var b,f,g,h,i,j,k,l,m,n=this,o=function(){},p=this.imports={paths:a&&a.paths||[],queue:[],files:{},contents:{},mime:a&&a.mime,error:null,push:function(b,c){var e=this;this.queue.push(b),d.Parser.importer(b,this.paths,function(a,d,f){e.queue.splice(e.queue.indexOf(b),1),e.files[b]=d,e.contents[b]=f,a&&!e.error&&(e.error=a),c(a,d),e.queue.length===0&&o()},a)}};return this.env=a=a||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,m={imports:p,parse:function(h,i){var n,p,q,r,s,u,v=[],w,x=null;f=g=l=j=0,b=h.replace(/\r\n/g,"\n"),k=function(c){var d=0,e=/[^"'`\{\}\/\(\)\\]+/g,f=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,g=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,h=0,i,j=c[0],k;for(var l=0,m,n;l<b.length;l++){e.lastIndex=l,(i=e.exec(b))&&i.index===l&&(l+=i[0].length,j.push(i[0])),m=b.charAt(l),f.lastIndex=g.lastIndex=l,(i=g.exec(b))&&i.index===l&&(l+=i[0].length,j.push(i[0]),m=b.charAt(l)),!k&&m==="/"&&(n=b.charAt(l+1),(n==="/"||n==="*")&&(i=f.exec(b))&&i.index===l&&(l+=i[0].length,j.push(i[0]),m=b.charAt(l)));switch(m){case"{":if(!k){h++,j.push(m);break};case"}":if(!k){h--,j.push(m),c[++d]=j=[];break};case"(":if(!k){k=!0,j.push(m);break};case")":if(k){k=!1,j.push(m);break};default:j.push(m)}}return h>0&&(x=new A({index:l,type:"Parse",message:"missing closing `}`",filename:a.filename},a)),c.map(function(a){return a.join("")})}([[]]);if(x)return i(x);try{n=new e.Ruleset([],t(this.parsers.primary)),n.root=!0}catch(y){return i(new A(y,a))}n.toCSS=function(b){var f,g,h;return function(f,g){var h=[],i;f=f||{},typeof g=="object"&&!Array.isArray(g)&&(g=Object.keys(g).map(function(a){var b=g[a];return b instanceof e.Value||(b instanceof e.Expression||(b=new e.Expression([b])),b=new e.Value([b])),new e.Rule("@"+a,b,!1,0)}),h=[new e.Ruleset(null,g)]);try{var j=b.call(this,{frames:h}).toCSS([],{compress:f.compress||!1})}catch(k){throw new A(k,a)}if(i=m.imports.error)throw i instanceof A?i:new A(i,a);return f.yuicompress&&d.mode==="node"?c("./cssmin").compressor.cssmin(j):f.compress?j.replace(/(\s)+/g,"$1"):j}}(n.eval);if(f<b.length-1){f=j,u=b.split("\n"),s=(b.slice(0,f).match(/\n/g)||"").length+1;for(var z=f,B=-1;z>=0&&b.charAt(z)!=="\n";z--)B++;x={type:"Parse",message:"Syntax Error on line "+s,index:f,filename:a.filename,line:s,column:B,extract:[u[s-2],u[s-1],u[s]]}}this.imports.queue.length>0?o=function(){i(x,n)}:i(x,n)},parsers:{primary:function(){var a,b=[];while((a=t(this.mixin.definition)||t(this.rule)||t(this.ruleset)||t(this.mixin.call)||t(this.comment)||t(this.directive))||t(/^[\s\n]+/))a&&b.push(a);return b},comment:function(){var a;if(b.charAt(f)!=="/")return;if(b.charAt(f+1)==="/")return new e.Comment(t(/^\/\/.*/),!0);if(a=t(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new e.Comment(a)},entities:{quoted:function(){var a,c=f,d;b.charAt(c)==="~"&&(c++,d=!0);if(b.charAt(c)!=='"'&&b.charAt(c)!=="'")return;d&&t("~");if(a=t(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new e.Quoted(a[0],a[1]||a[2],d)},keyword:function(){var a;if(a=t(/^[_A-Za-z-][_A-Za-z0-9-]*/))return e.colors.hasOwnProperty(a)?new e.Color(e.colors[a].slice(1)):new e.Keyword(a)},call:function(){var b,c,d=f;if(!(b=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(k[g])))return;b=b[1].toLowerCase();if(b==="url")return null;f+=b.length;if(b==="alpha")return t(this.alpha);t("("),c=t(this.entities.arguments);if(!t(")"))return;if(b)return new e.Call(b,c,d,a.filename)},arguments:function(){var a=[],b;while(b=t(this.entities.assignment)||t(this.expression)){a.push(b);if(!t(","))break}return a},literal:function(){return t(this.entities.dimension)||t(this.entities.color)||t(this.entities.quoted)},assignment:function(){var a,b;if((a=t(/^\w+(?=\s?=)/i))&&t("=")&&(b=t(this.entity)))return new e.Assignment(a,b)},url:function(){var a;if(b.charAt(f)!=="u"||!t(/^url\(/))return;return a=t(this.entities.quoted)||t(this.entities.variable)||t(this.entities.dataURI)||t(/^[-\w%@$\/.&=:;#+?~]+/)||"",u(")"),new e.URL(a.value||a.data||a instanceof e.Variable?a:new e.Anonymous(a),p.paths)},dataURI:function(){var a;if(t(/^data:/)){a={},a.mime=t(/^[^\/]+\/[^,;)]+/)||"",a.charset=t(/^;\s*charset=[^,;)]+/)||"",a.base64=t(/^;\s*base64/)||"",a.data=t(/^,\s*[^)]+/);if(a.data)return a}},variable:function(){var c,d=f;if(b.charAt(f)==="@"&&(c=t(/^@@?[\w-]+/)))return new e.Variable(c,d,a.filename)},color:function(){var a;if(b.charAt(f)==="#"&&(a=t(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/)))return new e.Color(a[1])},dimension:function(){var a,c=b.charCodeAt(f);if(c>57||c<45||c===47)return;if(a=t(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/))return new e.Dimension(a[1],a[2])},javascript:function(){var a,c=f,d;b.charAt(c)==="~"&&(c++,d=!0);if(b.charAt(c)!=="`")return;d&&t("~");if(a=t(/^`([^`]*)`/))return new e.JavaScript(a[1],f,d)}},variable:function(){var a;if(b.charAt(f)==="@"&&(a=t(/^(@[\w-]+)\s*:/)))return a[1]},shorthand:function(){var a,b;if(!w(/^[@\w.%-]+\/[@\w.-]+/))return;if((a=t(this.entity))&&t("/")&&(b=t(this.entity)))return new e.Shorthand(a,b)},mixin:{call:function(){var c=[],d,g,h,i=f,j=b.charAt(f),k=!1;if(j!=="."&&j!=="#")return;while(d=t(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/))c.push(new e.Element(g,d,f)),g=t(">");t("(")&&(h=t(this.entities.arguments))&&t(")"),t(this.important)&&(k=!0);if(c.length>0&&(t(";")||w("}")))return new e.mixin.Call(c,h||[],i,a.filename,k)},definition:function(){var a,c=[],d,g,h,i,j,k=!1;if(b.charAt(f)!=="."&&b.charAt(f)!=="#"||w(/^[^{]*(;|})/))return;q();if(d=t(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)){a=d[1];do{if(b.charAt(f)==="."&&t(/^\.{3}/)){k=!0;break}if(!(h=t(this.entities.variable)||t(this.entities.literal)||t(this.entities.keyword)))break;if(h instanceof e.Variable)if(t(":"))i=u(this.expression,"expected expression"),c.push({name:h.name,value:i});else{if(t(/^\.{3}/)){c.push({name:h.name,variadic:!0}),k=!0;break}c.push({name:h.name})}else c.push({value:h})}while(t(","));u(")"),t(/^when/)&&(j=u(this.conditions,"expected condition")),g=t(this.block);if(g)return new e.mixin.Definition(a,c,g,j,k);r()}}},entity:function(){return t(this.entities.literal)||t(this.entities.variable)||t(this.entities.url)||t(this.entities.call)||t(this.entities.keyword)||t(this.entities.javascript)||t(this.comment)},end:function(){return t(";")||w("}")},alpha:function(){var a;if(!t(/^\(opacity=/i))return;if(a=t(/^\d+/)||t(this.entities.variable))return u(")"),new e.Alpha(a)},element:function(){var a,b,c,d;c=t(this.combinator),a=t(/^(?:\d+\.\d+|\d+)%/)||t(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)||t("*")||t(this.attribute)||t(/^\([^)@]+\)/),a||t("(")&&(d=t(this.entities.variable))&&t(")")&&(a=new e.Paren(d));if(a)return new e.Element(c,a,f);if(c.value&&c.value.charAt(0)==="&")return new e.Element(c,null,f)},combinator:function(){var a,c=b.charAt(f);if(c===">"||c==="+"||c==="~"){f++;while(b.charAt(f)===" ")f++;return new e.Combinator(c)}if(c==="&"){a="&",f++,b.charAt(f)===" "&&(a="& ");while(b.charAt(f)===" ")f++;return new e.Combinator(a)}return b.charAt(f-1)===" "?new e.Combinator(" "):new e.Combinator(null)},selector:function(){var a,c,d=[],g,h;if(t("("))return a=t(this.entity),u(")"),new e.Selector([new e.Element("",a,f)]);while(c=t(this.element)){g=b.charAt(f),d.push(c);if(g==="{"||g==="}"||g===";"||g===",")break}if(d.length>0)return new e.Selector(d)},tag:function(){return t(/^[a-zA-Z][a-zA-Z-]*[0-9]?/)||t("*")},attribute:function(){var a="",b,c,d;if(!t("["))return;if(b=t(/^[a-zA-Z-]+/)||t(this.entities.quoted))(d=t(/^[|~*$^]?=/))&&(c=t(this.entities.quoted)||t(/^[\w-]+/))?a=[b,d,c.toCSS?c.toCSS():c].join(""):a=b;if(!t("]"))return;if(a)return"["+a+"]"},block:function(){var a;if(t("{")&&(a=t(this.primary))&&t("}"))return a},ruleset:function(){var b=[],c,d,g;q();while(c=t(this.selector)){b.push(c),t(this.comment);if(!t(","))break;t(this.comment)}if(b.length>0&&(d=t(this.block)))return new e.Ruleset(b,d,a.strictImports);j=f,r()},rule:function(){var a,c,d=b.charAt(f),h,l;q();if(d==="."||d==="#"||d==="&")return;if(a=t(this.variable)||t(this.property)){a.charAt(0)!="@"&&(l=/^([^@+\/'"*`(;{}-]*);/.exec(k[g]))?(f+=l[0].length-1,c=new e.Anonymous(l[1])):a==="font"?c=t(this.font):c=t(this.value),h=t(this.important);if(c&&t(this.end))return new e.Rule(a,c,h,i);j=f,r()}},"import":function(){var a,b,c=f;if(t(/^@import\s+/)&&(a=t(this.entities.quoted)||t(this.entities.url))){b=t(this.mediaFeatures);if(t(";"))return new e.Import(a,p,b,c)}},mediaFeature:function(){var a,b,c=[];do if(a=t(this.entities.keyword))c.push(a);else if(t("(")){b=t(this.property),a=t(this.entity);if(!t(")"))return null;if(b&&a)c.push(new e.Paren(new e.Rule(b,a,null,f,!0)));else if(a)c.push(new e.Paren(a));else return null}while(a);if(c.length>0)return new e.Expression(c)},mediaFeatures:function(){var a,b=[];do if(a=t(this.mediaFeature)){b.push(a);if(!t(","))break}else if(a=t(this.entities.variable)){b.push(a);if(!t(","))break}while(a);return b.length>0?b:null},media:function(){var a,b;if(t(/^@media/)){a=t(this.mediaFeatures);if(b=t(this.block))return new e.Media(b,a)}},directive:function(){var a,c,d,g,h,i;if(b.charAt(f)!=="@")return;if(c=t(this["import"])||t(this.media))return c;if(a=t(/^@page|@keyframes/)||t(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)){g=(t(/^[^{]+/)||"").trim();if(d=t(this.block))return new e.Directive(a+" "+g,d)}else if(a=t(/^@[-a-z]+/))if(a==="@font-face"){if(d=t(this.block))return new e.Directive(a,d)}else if((c=t(this.entity))&&t(";"))return new e.Directive(a,c)},font:function(){var a=[],b=[],c,d,f,g;while(g=t(this.shorthand)||t(this.entity))b.push(g);a.push(new e.Expression(b));if(t(","))while(g=t(this.expression)){a.push(g);if(!t(","))break}return new e.Value(a)},value:function(){var a,b=[],c;while(a=t(this.expression)){b.push(a);if(!t(","))break}if(b.length>0)return new e.Value(b)},important:function(){if(b.charAt(f)==="!")return t(/^! *important/)},sub:function(){var a;if(t("(")&&(a=t(this.expression))&&t(")"))return a},multiplication:function(){var a,b,c,d;if(a=t(this.operand)){while(!w(/^\/\*/)&&(c=t("/")||t("*"))&&(b=t(this.operand)))d=new e.Operation(c,[d||a,b]);return d||a}},addition:function(){var a,c,d,g;if(a=t(this.multiplication)){while((d=t(/^[-+]\s+/)||b.charAt(f-1)!=" "&&(t("+")||t("-")))&&(c=t(this.multiplication)))g=new e.Operation(d,[g||a,c]);return g||a}},conditions:function(){var a,b,c=f,d;if(a=t(this.condition)){while(t(",")&&(b=t(this.condition)))d=new e.Condition("or",d||a,b,c);return d||a}},condition:function(){var a,b,c,d,g=f,h=!1;t(/^not/)&&(h=!0),u("(");if(a=t(this.addition)||t(this.entities.keyword)||t(this.entities.quoted))return(d=t(/^(?:>=|=<|[<=>])/))?(b=t(this.addition)||t(this.entities.keyword)||t(this.entities.quoted))?c=new e.Condition(d,a,b,g,h):v("expected expression"):c=new e.Condition("=",a,new e.Keyword("true"),g,h),u(")"),t(/^and/)?new e.Condition("and",c,t(this.condition)):c},operand:function(){var a,c=b.charAt(f+1);b.charAt(f)==="-"&&(c==="@"||c==="(")&&(a=t("-"));var d=t(this.sub)||t(this.entities.dimension)||t(this.entities.color)||t(this.entities.variable)||t(this.entities.call);return a?new e.Operation("*",[new e.Dimension(-1),d]):d},expression:function(){var a,b,c=[],d;while(a=t(this.addition)||t(this.entity))c.push(a);if(c.length>0)return new e.Expression(c)},property:function(){var a;if(a=t(/^(\*?-?[-a-z_0-9]+)\s*:/))return a[1]}}}};if(d.mode==="browser"||d.mode==="rhino")d.Parser.importer=function(a,b,c,d){!/^([a-z]+:)?\//.test(a)&&b.length>0&&(a=b[0]+a),n({href:a,title:a,type:d.mime},function(e){e&&typeof d.errback=="function"?d.errback.call(null,a,b,c,d):c.apply(null,arguments)},!0)};(function(a){function b(b){return a.functions.hsla(b.h,b.s,b.l,b.a)}function c(b){if(b instanceof a.Dimension)return parseFloat(b.unit=="%"?b.value/100:b.value);if(typeof b=="number")return b;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function d(a){return Math.min(1,Math.max(0,a))}a.functions={rgb:function(a,b,c){return this.rgba(a,b,c,1)},rgba:function(b,d,e,f){var g=[b,d,e].map(function(a){return c(a)}),f=c(f);return new a.Color(g,f)},hsl:function(a,b,c){return this.hsla(a,b,c,1)},hsla:function(a,b,d,e){function h(a){return a=a<0?a+1:a>1?a-1:a,a*6<1?g+(f-g)*a*6:a*2<1?f:a*3<2?g+(f-g)*(2/3-a)*6:g}a=c(a)%360/360,b=c(b),d=c(d),e=c(e);var f=d<=.5?d*(b+1):d+b-d*b,g=d*2-f;return this.rgba(h(a+1/3)*255,h(a)*255,h(a-1/3)*255,e)},hue:function(b){return new a.Dimension(Math.round(b.toHSL().h))},saturation:function(b){return new a.Dimension(Math.round(b.toHSL().s*100),"%")},lightness:function(b){return new a.Dimension(Math.round(b.toHSL().l*100),"%")},alpha:function(b){return new a.Dimension(b.toHSL().a)},saturate:function(a,c){var e=a.toHSL();return e.s+=c.value/100,e.s=d(e.s),b(e)},desaturate:function(a,c){var e=a.toHSL();return e.s-=c.value/100,e.s=d(e.s),b(e)},lighten:function(a,c){var e=a.toHSL();return e.l+=c.value/100,e.l=d(e.l),b(e)},darken:function(a,c){var e=a.toHSL();return e.l-=c.value/100,e.l=d(e.l),b(e)},fadein:function(a,c){var e=a.toHSL();return e.a+=c.value/100,e.a=d(e.a),b(e)},fadeout:function(a,c){var e=a.toHSL();return e.a-=c.value/100,e.a=d(e.a),b(e)},fade:function(a,c){var e=a.toHSL();return e.a=c.value/100,e.a=d(e.a),b(e)},spin:function(a,c){var d=a.toHSL(),e=(d.h+c.value)%360;return d.h=e<0?360+e:e,b(d)},mix:function(b,c,d){var e=d.value/100,f=e*2-1,g=b.toHSL().a-c.toHSL().a,h=((f*g==-1?f:(f+g)/(1+f*g))+1)/2,i=1-h,j=[b.rgb[0]*h+c.rgb[0]*i,b.rgb[1]*h+c.rgb[1]*i,b.rgb[2]*h+c.rgb[2]*i],k=b.alpha*e+c.alpha*(1-e);return new a.Color(j,k)},greyscale:function(b){return this.desaturate(b,new a.Dimension(100))},e:function(b){return new a.Anonymous(b instanceof a.JavaScript?b.evaluated:b)},escape:function(b){return new a.Anonymous(encodeURI(b.value).replace(/=/g,"%3D").replace(/:/g,"%3A").replace(/#/g,"%23").replace(/;/g,"%3B").replace(/\(/g,"%28").replace(/\)/g,"%29"))},"%":function(b){var c=Array.prototype.slice.call(arguments,1),d=b.value;for(var e=0;e<c.length;e++)d=d.replace(/%[sda]/i,function(a){var b=a.match(/s/i)?c[e].value:c[e].toCSS();return a.match(/[A-Z]$/)?encodeURIComponent(b):b});return d=d.replace(/%%/g,"%"),new a.Quoted('"'+d+'"',d)},round:function(a){return this._math("round",a)},ceil:function(a){return this._math("ceil",a)},floor:function(a){return this._math("floor",a)},_math:function(b,d){if(d instanceof a.Dimension)return new a.Dimension(Math[b](c(d)),d.unit);if(typeof d=="number")return Math[b](d);throw{type:"Argument",message:"argument must be a number"}},argb:function(b){return new a.Anonymous(b.toARGB())},percentage:function(b){return new a.Dimension(b.value*100,"%")},color:function(b){if(b instanceof a.Quoted)return new a.Color(b.value.slice(1));throw{type:"Argument",message:"argument must be a string"}},iscolor:function(b){return this._isa(b,a.Color)},isnumber:function(b){return this._isa(b,a.Dimension)},isstring:function(b){return this._isa(b,a.Quoted)},iskeyword:function(b){return this._isa(b,a.Keyword)},isurl:function(b){return this._isa(b,a.URL)},ispixel:function(b){return b instanceof a.Dimension&&b.unit==="px"?a.True:a.False},ispercentage:function(b){return b instanceof a.Dimension&&b.unit==="%"?a.True:a.False},isem:function(b){return b instanceof a.Dimension&&b.unit==="em"?a.True:a.False},_isa:function(b,c){return b instanceof c?a.True:a.False}}})(c("./tree")),function(a){a.colors={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",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",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:"#9370d8",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:"#d87093",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"}}(c("./tree")),function(a){a.Alpha=function(a){this.value=a},a.Alpha.prototype={toCSS:function(){return"alpha(opacity="+(this.value.toCSS?this.value.toCSS():this.value)+")"},eval:function(a){return this.value.eval&&(this.value=this.value.eval(a)),this}}}(c("../tree")),function(a){a.Anonymous=function(a){this.value=a.value||a},a.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this}}}(c("../tree")),function(a){a.Assignment=function(a,b){this.key=a,this.value=b},a.Assignment.prototype={toCSS:function(){return this.key+"="+(this.value.toCSS?this.value.toCSS():this.value)},eval:function(a){return this.value.eval&&(this.value=this.value.eval(a)),this}}}(c("../tree")),function(a){a.Call=function(a,b,c,d){this.name=a,this.args=b,this.index=c,this.filename=d},a.Call.prototype={eval:function(b){var c=this.args.map(function(a){return a.eval(b)});if(!(this.name in a.functions))return new a.Anonymous(this.name+"("+c.map(function(a){return a.toCSS()}).join(", ")+")");try{return a.functions[this.name].apply(a.functions,c)}catch(d){throw{type:d.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(d.message?": "+d.message:""),index:this.index,filename:this.filename}}},toCSS:function(a){return this.eval(a).toCSS()}}}(c("../tree")),function(a){a.Color=function(a,b){Array.isArray(a)?this.rgb=a:a.length==6?this.rgb=a.match(/.{2}/g).map(function(a){return parseInt(a,16)}):this.rgb=a.split("").map(function(a){return parseInt(a+a,16)}),this.alpha=typeof b=="number"?b:1},a.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(a){return Math.round(a)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(a){return a=Math.round(a),a=(a>255?255:a<0?0:a).toString(16),a.length===1?"0"+a:a}).join("")},operate:function(b,c){var d=[];c instanceof a.Color||(c=c.toColor());for(var e=0;e<3;e++)d[e]=a.operate(b,this.rgb[e],c.rgb[e]);return new a.Color(d,this.alpha+c.alpha)},toHSL:function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255,d=this.alpha,e=Math.max(a,b,c),f=Math.min(a,b,c),g,h,i=(e+f)/2,j=e-f;if(e===f)g=h=0;else{h=i>.5?j/(2-e-f):j/(e+f);switch(e){case a:g=(b-c)/j+(b<c?6:0);break;case b:g=(c-a)/j+2;break;case c:g=(a-b)/j+4}g/=6}return{h:g*360,s:h,l:i,a:d}},toARGB:function(){var a=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+a.map(function(a){return a=Math.round(a),a=(a>255?255:a<0?0:a).toString(16),a.length===1?"0"+a:a}).join("")}}}(c("../tree")),function(a){a.Comment=function(a,b){this.value=a,this.silent=!!b},a.Comment.prototype={toCSS:function(a){return a.compress?"":this.value},eval:function(){return this}}}(c("../tree")),function(a){a.Condition=function(a,b,c,d,e){this.op=a.trim(),this.lvalue=b,this.rvalue=c,this.index=d,this.negate=e},a.Condition.prototype.eval=function(a){var b=this.lvalue.eval(a),c=this.rvalue.eval(a),d=this.index,e,e=function(a){switch(a){case"and":return b&&c;case"or":return b||c;default:if(b.compare)e=b.compare(c);else if(c.compare)e=c.compare(b);else throw{type:"Type",message:"Unable to perform comparison",index:d};switch(e){case-1:return a==="<"||a==="=<";case 0:return a==="="||a===">="||a==="=<";case 1:return a===">"||a===">="}}}(this.op);return this.negate?!e:e}}(c("../tree")),function(a){a.Dimension=function(a,b){this.value=parseFloat(a),this.unit=b||null},a.Dimension.prototype={eval:function(){return this},toColor:function(){return new a.Color([this.value,this.value,this.value])},toCSS:function(){var a=this.value+this.unit;return a},operate:function(b,c){return new a.Dimension(a.operate(b,this.value,c.value),this.unit||c.unit)},compare:function(b){return b instanceof a.Dimension?b.value>this.value?-1:b.value<this.value?1:0:-1}}}(c("../tree")),function(a){a.Directive=function(b,c,d){this.name=b,Array.isArray(c)?(this.ruleset=new a.Ruleset([],c),this.ruleset.allowImports=!0):this.value=c},a.Directive.prototype={toCSS:function(a,b){return this.ruleset?(this.ruleset.root=!0,this.name+(b.compress?"{":" {\n ")+this.ruleset.toCSS(a,b).trim().replace(/\n/g,"\n ")+(b.compress?"}":"\n}\n")):this.name+" "+this.value.toCSS()+";\n"},eval:function(a){return a.frames.unshift(this),this.ruleset=this.ruleset&&this.ruleset.eval(a),a.frames.shift(),this},variable:function(b){return a.Ruleset.prototype.variable.call(this.ruleset,b)},find:function(){return a.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.ruleset)}}}(c("../tree")),function(a){a.Element=function(b,c,d){this.combinator=b instanceof a.Combinator?b:new a.Combinator(b),typeof c=="string"?this.value=c.trim():c?this.value=c:this.value="",this.index=d},a.Element.prototype.eval=function(b){return new a.Element(this.combinator,this.value.eval?this.value.eval(b):this.value,this.index)},a.Element.prototype.toCSS=function(a){return this.combinator.toCSS(a||{})+(this.value.toCSS?this.value.toCSS(a):this.value)},a.Combinator=function(a){a===" "?this.value=" ":a==="& "?this.value="& ":this.value=a?a.trim():""},a.Combinator.prototype.toCSS=function(a){return{"":""," ":" ","&":"","& ":" ",":":" :","+":a.compress?"+":" + ","~":a.compress?"~":" ~ ",">":a.compress?">":" > "}[this.value]}}(c("../tree")),function(a){a.Expression=function(a){this.value=a},a.Expression.prototype={eval:function(b){return this.value.length>1?new a.Expression(this.value.map(function(a){return a.eval(b)})):this.value.length===1?this.value[0].eval(b):this},toCSS:function(a){return this.value.map(function(b){return b.toCSS?b.toCSS(a):""}).join(" ")}}}(c("../tree")),function(a){a.Import=function(b,c,d,e){var f=this;this.index=e,this._path=b,this.features=d&&new a.Value(d),b instanceof a.Quoted?this.path=/\.(le?|c)ss(\?.*)?$/.test(b.value)?b.value:b.value+".less":this.path=b.value.value||b.value,this.css=/css(\?.*)?$/.test(this.path),this.css||c.push(this.path,function(b,c){b&&(b.index=e),f.root=c||new a.Ruleset([],[])})},a.Import.prototype={toCSS:function(a){var b=this.features?" "+this.features.toCSS(a):"";return this.css?"@import "+this._path.toCSS()+b+";\n":""},eval:function(b){var c,d=this.features&&this.features.eval(b);if(this.css)return this;c=new a.Ruleset([],this.root.rules.slice(0));for(var e=0;e<c.rules.length;e++)c.rules[e]instanceof a.Import&&Array.prototype
+.splice.apply(c.rules,[e,1].concat(c.rules[e].eval(b)));return this.features?new a.Media(c.rules,this.features.value):c.rules}}}(c("../tree")),function(a){a.JavaScript=function(a,b,c){this.escaped=c,this.expression=a,this.index=b},a.JavaScript.prototype={eval:function(b){var c,d=this,e={},f=this.expression.replace(/@\{([\w-]+)\}/g,function(c,e){return a.jsify((new a.Variable("@"+e,d.index)).eval(b))});try{f=new Function("return ("+f+")")}catch(g){throw{message:"JavaScript evaluation error: `"+f+"`",index:this.index}}for(var h in b.frames[0].variables())e[h.slice(1)]={value:b.frames[0].variables()[h].value,toJS:function(){return this.value.eval(b).toCSS()}};try{c=f.call(e)}catch(g){throw{message:"JavaScript evaluation error: '"+g.name+": "+g.message+"'",index:this.index}}return typeof c=="string"?new a.Quoted('"'+c+'"',c,this.escaped,this.index):Array.isArray(c)?new a.Anonymous(c.join(", ")):new a.Anonymous(c)}}}(c("../tree")),function(a){a.Keyword=function(a){this.value=a},a.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(b){return b instanceof a.Keyword?b.value===this.value?0:1:-1}},a.True=new a.Keyword("true"),a.False=new a.Keyword("false")}(c("../tree")),function(a){a.Media=function(b,c){var d=new a.Element("&",null,0),e=[new a.Selector([d])];this.features=new a.Value(c),this.ruleset=new a.Ruleset(e,b),this.ruleset.allowImports=!0},a.Media.prototype={toCSS:function(a,b){var c=this.features.toCSS(b);return this.ruleset.root=a.length===0||a[0].multiMedia,"@media "+c+(b.compress?"{":" {\n ")+this.ruleset.toCSS(a,b).trim().replace(/\n/g,"\n ")+(b.compress?"}":"\n}\n")},eval:function(b){b.mediaBlocks||(b.mediaBlocks=[],b.mediaPath=[]);var c=b.mediaBlocks.length;b.mediaPath.push(this),b.mediaBlocks.push(this);var d=new a.Media([],[]);return d.features=this.features.eval(b),b.frames.unshift(this.ruleset),d.ruleset=this.ruleset.eval(b),b.frames.shift(),b.mediaBlocks[c]=d,b.mediaPath.pop(),b.mediaPath.length===0?d.evalTop(b):d.evalNested(b)},variable:function(b){return a.Ruleset.prototype.variable.call(this.ruleset,b)},find:function(){return a.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.ruleset)},evalTop:function(b){var c=this;if(b.mediaBlocks.length>1){var d=new a.Element("&",null,0),e=[new a.Selector([d])];c=new a.Ruleset(e,b.mediaBlocks),c.multiMedia=!0}return delete b.mediaBlocks,delete b.mediaPath,c},evalNested:function(b){var c,d,e=b.mediaPath.concat([this]);for(c=0;c<e.length;c++)d=e[c].features instanceof a.Value?e[c].features.value:e[c].features,e[c]=Array.isArray(d)?d:[d];return this.features=new a.Value(this.permute(e).map(function(b){b=b.map(function(b){return b.toCSS?b:new a.Anonymous(b)});for(c=b.length-1;c>0;c--)b.splice(c,0,new a.Anonymous("and"));return new a.Expression(b)})),new a.Ruleset([],[])},permute:function(a){if(a.length===0)return[];if(a.length===1)return a[0];var b=[],c=this.permute(a.slice(1));for(var d=0;d<c.length;d++)for(var e=0;e<a[0].length;e++)b.push([a[0][e]].concat(c[d]));return b}}}(c("../tree")),function(a){a.mixin={},a.mixin.Call=function(b,c,d,e,f){this.selector=new a.Selector(b),this.arguments=c,this.index=d,this.filename=e,this.important=f},a.mixin.Call.prototype={eval:function(a){var b,c,d=[],e=!1;for(var f=0;f<a.frames.length;f++)if((b=a.frames[f].find(this.selector)).length>0){c=this.arguments&&this.arguments.map(function(b){return b.eval(a)});for(var g=0;g<b.length;g++)if(b[g].match(c,a))try{Array.prototype.push.apply(d,b[g].eval(a,this.arguments,this.important).rules),e=!0}catch(h){throw{message:h.message,index:this.index,filename:this.filename,stack:h.stack}}if(e)return d;throw{type:"Runtime",message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+this.arguments.map(function(a){return a.toCSS()}).join(", ")+")`",index:this.index,filename:this.filename}}throw{type:"Name",message:this.selector.toCSS().trim()+" is undefined",index:this.index,filename:this.filename}}},a.mixin.Definition=function(b,c,d,e,f){this.name=b,this.selectors=[new a.Selector([new a.Element(null,b)])],this.params=c,this.condition=e,this.variadic=f,this.arity=c.length,this.rules=d,this._lookups={},this.required=c.reduce(function(a,b){return!b.name||b.name&&!b.value?a+1:a},0),this.parent=a.Ruleset.prototype,this.frames=[]},a.mixin.Definition.prototype={toCSS:function(){return""},variable:function(a){return this.parent.variable.call(this,a)},variables:function(){return this.parent.variables.call(this)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},evalParams:function(b,c){var d=new a.Ruleset(null,[]),e;for(var f=0,g,h;f<this.params.length;f++)if(h=this.params[f].name)if(this.params[f].variadic&&c){e=[];for(var i=f;i<c.length;i++)e.push(c[i].eval(b));d.rules.unshift(new a.Rule(h,(new a.Expression(e)).eval(b)))}else if(g=c&&c[f]||this.params[f].value)d.rules.unshift(new a.Rule(h,g.eval(b)));else throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+c.length+" for "+this.arity+")"};return d},eval:function(b,c,d){var e=this.evalParams(b,c),f,g=[],h,i;for(var j=0;j<Math.max(this.params.length,c&&c.length);j++)g.push(c[j]||this.params[j].value);return e.rules.unshift(new a.Rule("@arguments",(new a.Expression(g)).eval(b))),h=d?this.rules.map(function(b){return new a.Rule(b.name,b.value,"!important",b.index)}):this.rules.slice(0),(new a.Ruleset(null,h)).eval({frames:[this,e].concat(this.frames,b.frames)})},match:function(a,b){var c=a&&a.length||0,d,e;if(!this.variadic){if(c<this.required)return!1;if(c>this.params.length)return!1;if(this.required>0&&c>this.params.length)return!1}if(this.condition&&!this.condition.eval({frames:[this.evalParams(b,a)].concat(b.frames)}))return!1;d=Math.min(c,this.arity);for(var f=0;f<d;f++)if(!this.params[f].name&&a[f].eval(b).toCSS()!=this.params[f].value.eval(b).toCSS())return!1;return!0}}}(c("../tree")),function(a){a.Operation=function(a,b){this.op=a.trim(),this.operands=b},a.Operation.prototype.eval=function(b){var c=this.operands[0].eval(b),d=this.operands[1].eval(b),e;if(c instanceof a.Dimension&&d instanceof a.Color)if(this.op==="*"||this.op==="+")e=d,d=c,c=e;else throw{name:"OperationError",message:"Can't substract or divide a color from a number"};return c.operate(this.op,d)},a.operate=function(a,b,c){switch(a){case"+":return b+c;case"-":return b-c;case"*":return b*c;case"/":return b/c}}}(c("../tree")),function(a){a.Paren=function(a){this.value=a},a.Paren.prototype={toCSS:function(a){return"("+this.value.toCSS(a)+")"},eval:function(b){return new a.Paren(this.value.eval(b))}}}(c("../tree")),function(a){a.Quoted=function(a,b,c,d){this.escaped=c,this.value=b||"",this.quote=a.charAt(0),this.index=d},a.Quoted.prototype={toCSS:function(){return this.escaped?this.value:this.quote+this.value+this.quote},eval:function(b){var c=this,d=this.value.replace(/`([^`]+)`/g,function(d,e){return(new a.JavaScript(e,c.index,!0)).eval(b).value}).replace(/@\{([\w-]+)\}/g,function(d,e){var f=(new a.Variable("@"+e,c.index)).eval(b);return"value"in f?f.value:f.toCSS()});return new a.Quoted(this.quote+d+this.quote,d,this.escaped,this.index)}}}(c("../tree")),function(a){a.Rule=function(b,c,d,e,f){this.name=b,this.value=c instanceof a.Value?c:new a.Value([c]),this.important=d?" "+d.trim():"",this.index=e,this.inline=f||!1,b.charAt(0)==="@"?this.variable=!0:this.variable=!1},a.Rule.prototype.toCSS=function(a){return this.variable?"":this.name+(a.compress?":":": ")+this.value.toCSS(a)+this.important+(this.inline?"":";")},a.Rule.prototype.eval=function(b){return new a.Rule(this.name,this.value.eval(b),this.important,this.index,this.inline)},a.Shorthand=function(a,b){this.a=a,this.b=b},a.Shorthand.prototype={toCSS:function(a){return this.a.toCSS(a)+"/"+this.b.toCSS(a)},eval:function(){return this}}}(c("../tree")),function(a){a.Ruleset=function(a,b,c){this.selectors=a,this.rules=b,this._lookups={},this.strictImports=c},a.Ruleset.prototype={eval:function(b){var c=this.selectors&&this.selectors.map(function(a){return a.eval(b)}),d=new a.Ruleset(c,this.rules.slice(0),this.strictImports);d.root=this.root,d.allowImports=this.allowImports,b.frames.unshift(d);if(d.root||d.allowImports||!d.strictImports)for(var e=0;e<d.rules.length;e++)d.rules[e]instanceof a.Import&&Array.prototype.splice.apply(d.rules,[e,1].concat(d.rules[e].eval(b)));for(var e=0;e<d.rules.length;e++)d.rules[e]instanceof a.mixin.Definition&&(d.rules[e].frames=b.frames.slice(0));for(var e=0;e<d.rules.length;e++)d.rules[e]instanceof a.mixin.Call&&Array.prototype.splice.apply(d.rules,[e,1].concat(d.rules[e].eval(b)));for(var e=0,f;e<d.rules.length;e++)f=d.rules[e],f instanceof a.mixin.Definition||(d.rules[e]=f.eval?f.eval(b):f);return b.frames.shift(),d},match:function(a){return!a||a.length===0},variables:function(){return this._variables?this._variables:this._variables=this.rules.reduce(function(b,c){return c instanceof a.Rule&&c.variable===!0&&(b[c.name]=c),b},{})},variable:function(a){return this.variables()[a]},rulesets:function(){return this._rulesets?this._rulesets:this._rulesets=this.rules.filter(function(b){return b instanceof a.Ruleset||b instanceof a.mixin.Definition})},find:function(b,c){c=c||this;var d=[],e,f,g=b.toCSS();return g in this._lookups?this._lookups[g]:(this.rulesets().forEach(function(e){if(e!==c)for(var g=0;g<e.selectors.length;g++)if(f=b.match(e.selectors[g])){b.elements.length>e.selectors[g].elements.length?Array.prototype.push.apply(d,e.find(new a.Selector(b.elements.slice(1)),c)):d.push(e);break}}),this._lookups[g]=d)},toCSS:function(b,c){var d=[],e=[],f=[],g=[],h,i;this.root||(b.length===0?g=this.selectors.map(function(a){return[a]}):this.joinSelectors(g,b,this.selectors));for(var j=0;j<this.rules.length;j++)i=this.rules[j],i.rules||i instanceof a.Directive||i instanceof a.Media?f.push(i.toCSS(g,c)):i instanceof a.Comment?i.silent||(this.root?f.push(i.toCSS(c)):e.push(i.toCSS(c))):i.toCSS&&!i.variable?e.push(i.toCSS(c)):i.value&&!i.variable&&e.push(i.value.toString());return f=f.join(""),this.root?d.push(e.join(c.compress?"":"\n")):e.length>0&&(h=g.map(function(a){return a.map(function(a){return a.toCSS(c)}).join("").trim()}).join(c.compress?",":",\n"),d.push(h,(c.compress?"{":" {\n ")+e.join(c.compress?"":"\n ")+(c.compress?"}":"\n}\n"))),d.push(f),d.join("")+(c.compress?"\n":"")},joinSelectors:function(a,b,c){for(var d=0;d<c.length;d++)this.joinSelector(a,b,c[d])},joinSelector:function(b,c,d){var e=[],f=[],g=[],h=[],i=!1,j;for(var k=0;k<d.elements.length;k++)j=d.elements[k],j.combinator.value.charAt(0)==="&"&&(i=!0),i?h.push(j):g.push(j);i||(h=g,g=[]),g.length>0&&e.push(new a.Selector(g)),h.length>0&&f.push(new a.Selector(h));for(var l=0;l<c.length;l++)b.push(e.concat(c[l]).concat(f))}}}(c("../tree")),function(a){a.Selector=function(a){this.elements=a,this.elements[0].combinator.value===""&&(this.elements[0].combinator.value=" ")},a.Selector.prototype.match=function(a){var b=this.elements.length,c=a.elements.length,d=Math.min(b,c);if(b<c)return!1;for(var e=0;e<d;e++)if(this.elements[e].value!==a.elements[e].value)return!1;return!0},a.Selector.prototype.eval=function(b){return new a.Selector(this.elements.map(function(a){return a.eval(b)}))},a.Selector.prototype.toCSS=function(a){return this._css?this._css:this._css=this.elements.map(function(b){return typeof b=="string"?" "+b.trim():b.toCSS(a)}).join("")}}(c("../tree")),function(b){b.URL=function(b,c){b.data?this.attrs=b:(typeof a!="undefined"&&!/^(?:https?:\/\/|file:\/\/|data:|\/)/.test(b.value)&&c.length>0&&(b.value=c[0]+(b.value.charAt(0)==="/"?b.value.slice(1):b.value)),this.value=b,this.paths=c)},b.URL.prototype={toCSS:function(){return"url("+(this.attrs?"data:"+this.attrs.mime+this.attrs.charset+this.attrs.base64+this.attrs.data:this.value.toCSS())+")"},eval:function(a){return this.attrs?this:new b.URL(this.value.eval(a),this.paths)}}}(c("../tree")),function(a){a.Value=function(a){this.value=a,this.is="value"},a.Value.prototype={eval:function(b){return this.value.length===1?this.value[0].eval(b):new a.Value(this.value.map(function(a){return a.eval(b)}))},toCSS:function(a){return this.value.map(function(b){return b.toCSS(a)}).join(a.compress?",":", ")}}}(c("../tree")),function(a){a.Variable=function(a,b,c){this.name=a,this.index=b,this.file=c},a.Variable.prototype={eval:function(b){var c,d,e=this.name;e.indexOf("@@")==0&&(e="@"+(new a.Variable(e.slice(1))).eval(b).value);if(c=a.find(b.frames,function(a){if(d=a.variable(e))return d.value.eval(b)}))return c;throw{type:"Name",message:"variable "+e+" is undefined",filename:this.file,index:this.index}}}}(c("../tree")),function(a){a.find=function(a,b){for(var c=0,d;c<a.length;c++)if(d=b.call(a,a[c]))return d;return null},a.jsify=function(a){return Array.isArray(a.value)&&a.value.length>1?"["+a.value.map(function(a){return a.toCSS(!1)}).join(", ")+"]":a.toCSS(!1)}}(c("./tree"));var f=location.protocol==="file:"||location.protocol==="chrome:"||location.protocol==="chrome-extension:"||location.protocol==="resource:";d.env=d.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||f?"development":"production"),d.async=!1,d.poll=d.poll||(f?1e3:1500),d.watch=function(){return this.watchMode=!0},d.unwatch=function(){return this.watchMode=!1},d.env==="development"?(d.optimization=0,/!watch/.test(location.hash)&&d.watch(),d.watchTimer=setInterval(function(){d.watchMode&&m(function(a,b,c,d,e){b&&p(b.toCSS(),d,e.lastModified)})},d.poll)):d.optimization=3;var g;try{g=typeof a.localStorage=="undefined"?null:a.localStorage}catch(h){g=null}var i=document.getElementsByTagName("link"),j=/^text\/(x-)?less$/;d.sheets=[];for(var k=0;k<i.length;k++)(i[k].rel==="stylesheet/less"||i[k].rel.match(/stylesheet/)&&i[k].type.match(j))&&d.sheets.push(i[k]);d.refresh=function(a){var b,c;b=c=new Date,m(function(a,d,e,f,g){g.local?t("loading "+f.href+" from cache."):(t("parsed "+f.href+" successfully."),p(d.toCSS(),f,g.lastModified)),t("css for "+f.href+" generated in "+(new Date-c)+"ms"),g.remaining===0&&t("css generated in "+(new Date-b)+"ms"),c=new Date},a),l()},d.refreshStyles=l,d.refresh(d.env==="development")})(window); \ No newline at end of file
diff --git a/module/web/static/js/libs/lodash-0.5.2.js b/module/web/static/js/libs/lodash-0.5.2.js
new file mode 100644
index 000000000..3c5448223
--- /dev/null
+++ b/module/web/static/js/libs/lodash-0.5.2.js
@@ -0,0 +1,4263 @@
+/*!
+ * Lo-Dash v0.5.2 <http://lodash.com>
+ * Copyright 2012 John-David Dalton <http://allyoucanleet.com/>
+ * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * <http://documentcloud.github.com/underscore>
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function(window, undefined) {
+ 'use strict';
+
+ /**
+ * Used to cache the last `_.templateSettings.evaluate` delimiter to avoid
+ * unnecessarily assigning `reEvaluateDelimiter` a new generated regexp.
+ * Assigned in `_.template`.
+ */
+ var lastEvaluateDelimiter;
+
+ /**
+ * Used to cache the last template `options.variable` to avoid unnecessarily
+ * assigning `reDoubleVariable` a new generated regexp. Assigned in `_.template`.
+ */
+ var lastVariable;
+
+ /**
+ * Used to match potentially incorrect data object references, like `obj.obj`,
+ * in compiled templates. Assigned in `_.template`.
+ */
+ var reDoubleVariable;
+
+ /**
+ * Used to match "evaluate" delimiters, including internal delimiters,
+ * in template text. Assigned in `_.template`.
+ */
+ var reEvaluateDelimiter;
+
+ /** Detect free variable `exports` */
+ var freeExports = typeof exports == 'object' && exports &&
+ (typeof global == 'object' && global && global == global.global && (window = global), exports);
+
+ /** Native prototype shortcuts */
+ var ArrayProto = Array.prototype,
+ BoolProto = Boolean.prototype,
+ ObjectProto = Object.prototype,
+ NumberProto = Number.prototype,
+ StringProto = String.prototype;
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = window._;
+
+ /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */
+ var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/;
+
+ /** Used to match empty string literals in compiled template source */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /** Used to match regexp flags from their coerced string values */
+ var reFlags = /\w*$/;
+
+ /** Used to insert the data object variable into compiled template source */
+ var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' +
+ (ObjectProto.valueOf + '')
+ .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
+ .replace(/valueOf|for [^\]]+/g, '.+?') + '$'
+ );
+
+ /** Used to match tokens in template text */
+ var reToken = /__token__(\d+)/g;
+
+ /** Used to match unescaped characters in strings for inclusion in HTML */
+ var reUnescapedHtml = /[&<"']/g;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to fix the JScript [[DontEnum]] bug */
+ var shadowed = [
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'valueOf'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** Used to replace template delimiters */
+ var token = '__token__';
+
+ /** Used to store tokenized template text snippets */
+ var tokenized = [];
+
+ /** Native method shortcuts */
+ var concat = ArrayProto.concat,
+ hasOwnProperty = ObjectProto.hasOwnProperty,
+ push = ArrayProto.push,
+ propertyIsEnumerable = ObjectProto.propertyIsEnumerable,
+ slice = ArrayProto.slice,
+ toString = ObjectProto.toString;
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind,
+ nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = window.isFinite,
+ nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
+
+ /** `Object#toString` result shortcuts */
+ var argsClass = '[object Arguments]',
+ arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ objectClass = '[object Object]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Timer shortcuts */
+ var clearTimeout = window.clearTimeout,
+ setTimeout = window.setTimeout;
+
+ /**
+ * Detect the JScript [[DontEnum]] bug:
+ * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+ * made non-enumerable as well.
+ */
+ var hasDontEnumBug;
+
+ /** Detect if own properties are iterated after inherited properties (IE < 9) */
+ var iteratesOwnLast;
+
+ /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */
+ var noArgsEnum = true;
+
+ (function() {
+ var props = [];
+ function ctor() { this.x = 1; }
+ ctor.prototype = { 'valueOf': 1, 'y': 1 };
+ for (var prop in new ctor) { props.push(prop); }
+ for (prop in arguments) { noArgsEnum = !prop; }
+ hasDontEnumBug = (props + '').length < 4;
+ iteratesOwnLast = props[0] != 'x';
+ }(1));
+
+ /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */
+ var noArgsClass = !isArguments(arguments);
+
+ /** Detect if `Array#slice` cannot be used to convert strings to arrays (Opera < 10.52) */
+ var noArraySliceOnStrings = slice.call('x')[0] != 'x';
+
+ /**
+ * Detect lack of support for accessing string characters by index:
+ * IE < 8 can't access characters by index and IE 8 can only access
+ * characters by index on string literals.
+ */
+ var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx';
+
+ /**
+ * Detect if a node's [[Class]] is unresolvable (IE < 9)
+ * and that the JS engine won't error when attempting to coerce an object to
+ * a string without a `toString` property value of `typeof` "function".
+ */
+ try {
+ var noNodeClass = ({ 'toString': 0 } + '', toString.call(window.document || 0) == objectClass);
+ } catch(e) { }
+
+ /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */
+ var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera));
+
+ /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */
+ var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent);
+
+ /** Detect if sourceURL syntax is usable without erroring */
+ try {
+ // The JS engine in Adobe products, like InDesign, will throw a syntax error
+ // when it encounters a single line comment beginning with the `@` symbol.
+ // The JS engine in Narwhal will generate the function `function anonymous(){//}`
+ // and throw a syntax error. In IE, `@` symbols are part of its non-standard
+ // conditional compilation support. The `@cc_on` statement activates its support
+ // while the trailing ` !` induces a syntax error to exlude it. Compatibility
+ // modes in IE > 8 require a space before the `!` to induce a syntax error.
+ // See http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx
+ var useSourceURL = (Function('//@cc_on !')(), true);
+ } catch(e){ }
+
+ /** Used to identify object classifications that are array-like */
+ var arrayLikeClasses = {};
+ arrayLikeClasses[boolClass] = arrayLikeClasses[dateClass] = arrayLikeClasses[funcClass] =
+ arrayLikeClasses[numberClass] = arrayLikeClasses[objectClass] = arrayLikeClasses[regexpClass] = false;
+ arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true;
+
+ /** Used to identify object classifications that `_.clone` supports */
+ var cloneableClasses = {};
+ cloneableClasses[argsClass] = cloneableClasses[funcClass] = false;
+ cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+ cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] =
+ cloneableClasses[stringClass] = true;
+
+ /**
+ * Used to escape characters for inclusion in HTML.
+ * The `>` and `/` characters don't require escaping in HTML and have no
+ * special meaning unless they're part of a tag or an unquoted attribute value
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (semi-related fun fact)
+ */
+ var htmlEscapes = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '"': '&quot;',
+ "'": '&#x27;'
+ };
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false,
+ 'unknown': true
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The `lodash` function.
+ *
+ * @name _
+ * @constructor
+ * @param {Mixed} value The value to wrap in a `LoDash` instance.
+ * @returns {Object} Returns a `LoDash` instance.
+ */
+ function lodash(value) {
+ // allow invoking `lodash` without the `new` operator
+ return new LoDash(value);
+ }
+
+ /**
+ * Creates a `LoDash` instance that wraps a value to allow chaining.
+ *
+ * @private
+ * @constructor
+ * @param {Mixed} value The value to wrap.
+ */
+ function LoDash(value) {
+ // exit early if already wrapped
+ if (value && value._wrapped) {
+ return value;
+ }
+ this._wrapped = value;
+ }
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in
+ * embedded Ruby (ERB). Change the following template settings to use alternative
+ * delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': /<%=([\s\S]+?)%>/g,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type String
+ */
+ 'variable': ''
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The template used to create iterator functions.
+ *
+ * @private
+ * @param {Obect} data The data object used to populate the text.
+ * @returns {String} Returns the interpolated text.
+ */
+ var iteratorTemplate = template(
+ // conditional strict mode
+ '<% if (useStrict) { %>\'use strict\';\n<% } %>' +
+
+ // the `iteratee` may be reassigned by the `top` snippet
+ 'var index, value, iteratee = <%= firstArg %>, ' +
+ // assign the `result` variable an initial value
+ 'result<% if (init) { %> = <%= init %><% } %>;\n' +
+ // add code to exit early or do so if the first argument is falsey
+ '<%= exit %>;\n' +
+ // add code after the exit snippet but before the iteration branches
+ '<%= top %>;\n' +
+
+ // the following branch is for iterating arrays and array-like objects
+ '<% if (arrayBranch) { %>' +
+ 'var length = iteratee.length; index = -1;' +
+ ' <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %>' +
+
+ // add support for accessing string characters by index if needed
+ ' <% if (noCharByIndex) { %>\n' +
+ ' if (toString.call(iteratee) == stringClass) {\n' +
+ ' iteratee = iteratee.split(\'\')\n' +
+ ' }' +
+ ' <% } %>\n' +
+
+ ' <%= arrayBranch.beforeLoop %>;\n' +
+ ' while (++index < length) {\n' +
+ ' value = iteratee[index];\n' +
+ ' <%= arrayBranch.inLoop %>\n' +
+ ' }' +
+ ' <% if (objectBranch) { %>\n}<% } %>' +
+ '<% } %>' +
+
+ // the following branch is for iterating an object's own/inherited properties
+ '<% if (objectBranch) { %>' +
+ ' <% if (arrayBranch) { %>\nelse {' +
+
+ // add support for iterating over `arguments` objects if needed
+ ' <% } else if (noArgsEnum) { %>\n' +
+ ' var length = iteratee.length; index = -1;\n' +
+ ' if (length && isArguments(iteratee)) {\n' +
+ ' while (++index < length) {\n' +
+ ' value = iteratee[index += \'\'];\n' +
+ ' <%= objectBranch.inLoop %>\n' +
+ ' }\n' +
+ ' } else {' +
+ ' <% } %>' +
+
+ ' <% if (!hasDontEnumBug) { %>\n' +
+ ' var skipProto = typeof iteratee == \'function\' && \n' +
+ ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' +
+ ' <% } %>' +
+
+ // iterate own properties using `Object.keys` if it's fast
+ ' <% if (isKeysFast && useHas) { %>\n' +
+ ' var ownIndex = -1,\n' +
+ ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' +
+ ' length = ownProps.length;\n\n' +
+ ' <%= objectBranch.beforeLoop %>;\n' +
+ ' while (++ownIndex < length) {\n' +
+ ' index = ownProps[ownIndex];\n' +
+ ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' +
+ ' value = iteratee[index];\n' +
+ ' <%= objectBranch.inLoop %>\n' +
+ ' <% if (!hasDontEnumBug) { %>}\n<% } %>' +
+ ' }' +
+
+ // else using a for-in loop
+ ' <% } else { %>\n' +
+ ' <%= objectBranch.beforeLoop %>;\n' +
+ ' for (index in iteratee) {' +
+ ' <% if (hasDontEnumBug) { %>\n' +
+ ' <% if (useHas) { %>if (hasOwnProperty.call(iteratee, index)) {\n <% } %>' +
+ ' value = iteratee[index];\n' +
+ ' <%= objectBranch.inLoop %>;\n' +
+ ' <% if (useHas) { %>}<% } %>' +
+
+ // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ // (if the prototype or a property on the prototype has been set)
+ // incorrectly sets a function's `prototype` property [[Enumerable]]
+ // value to `true`. Because of this Lo-Dash standardizes on skipping
+ // the the `prototype` property of functions regardless of its
+ // [[Enumerable]] value.
+ ' <% } else { %>\n' +
+ ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> &&\n' +
+ ' hasOwnProperty.call(iteratee, index)<% } %>) {\n' +
+ ' value = iteratee[index];\n' +
+ ' <%= objectBranch.inLoop %>\n' +
+ ' }' +
+ ' <% } %>\n' +
+ ' }' +
+ ' <% } %>' +
+
+ // Because IE < 9 can't set the `[[Enumerable]]` attribute of an
+ // existing property and the `constructor` property of a prototype
+ // defaults to non-enumerable, Lo-Dash skips the `constructor`
+ // property when it infers it's iterating over a `prototype` object.
+ ' <% if (hasDontEnumBug) { %>\n\n' +
+ ' var ctor = iteratee.constructor;\n' +
+ ' <% for (var k = 0; k < 7; k++) { %>\n' +
+ ' index = \'<%= shadowed[k] %>\';\n' +
+ ' if (<%' +
+ ' if (shadowed[k] == \'constructor\') {' +
+ ' %>!(ctor && ctor.prototype === iteratee) && <%' +
+ ' } %>hasOwnProperty.call(iteratee, index)) {\n' +
+ ' value = iteratee[index];\n' +
+ ' <%= objectBranch.inLoop %>\n' +
+ ' }' +
+ ' <% } %>' +
+ ' <% } %>' +
+ ' <% if (arrayBranch || noArgsEnum) { %>\n}<% } %>' +
+ '<% } %>\n' +
+
+ // add code to the bottom of the iteration function
+ '<%= bottom %>;\n' +
+ // finally, return the `result`
+ 'return result'
+ );
+
+ /**
+ * Reusable iterator options shared by
+ * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`,
+ * `reject`, `some`, and `sortBy`.
+ */
+ var baseIteratorOptions = {
+ 'args': 'collection, callback, thisArg',
+ 'init': 'collection',
+ 'top':
+ 'if (!callback) {\n' +
+ ' callback = identity\n' +
+ '}\n' +
+ 'else if (thisArg) {\n' +
+ ' callback = iteratorBind(callback, thisArg)\n' +
+ '}',
+ 'inLoop': 'if (callback(value, index, collection) === false) return result'
+ };
+
+ /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */
+ var countByIteratorOptions = {
+ 'init': '{}',
+ 'top':
+ 'var prop;\n' +
+ 'if (typeof callback != \'function\') {\n' +
+ ' var valueProp = callback;\n' +
+ ' callback = function(value) { return value[valueProp] }\n' +
+ '}\n' +
+ 'else if (thisArg) {\n' +
+ ' callback = iteratorBind(callback, thisArg)\n' +
+ '}',
+ 'inLoop':
+ 'prop = callback(value, index, collection);\n' +
+ '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)'
+ };
+
+ /** Reusable iterator options for `every` and `some` */
+ var everyIteratorOptions = {
+ 'init': 'true',
+ 'inLoop': 'if (!callback(value, index, collection)) return !result'
+ };
+
+ /** Reusable iterator options for `defaults` and `extend` */
+ var extendIteratorOptions = {
+ 'useHas': false,
+ 'useStrict': false,
+ 'args': 'object',
+ 'init': 'object',
+ 'top':
+ 'for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n' +
+ ' if (iteratee = arguments[argsIndex]) {',
+ 'inLoop': 'result[index] = value',
+ 'bottom': ' }\n}'
+ };
+
+ /** Reusable iterator options for `filter`, `reject`, and `where` */
+ var filterIteratorOptions = {
+ 'init': '[]',
+ 'inLoop': 'callback(value, index, collection) && result.push(value)'
+ };
+
+ /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */
+ var forEachIteratorOptions = {
+ 'top': 'if (thisArg) callback = iteratorBind(callback, thisArg)'
+ };
+
+ /** Reusable iterator options for `forIn` and `forOwn` */
+ var forOwnIteratorOptions = {
+ 'inLoop': {
+ 'object': baseIteratorOptions.inLoop
+ }
+ };
+
+ /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */
+ var mapIteratorOptions = {
+ 'init': '',
+ 'exit': 'if (!collection) return []',
+ 'beforeLoop': {
+ 'array': 'result = Array(length)',
+ 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]')
+ },
+ 'inLoop': {
+ 'array': 'result[index] = callback(value, index, collection)',
+ 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(callback(value, index, collection))'
+ }
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a new function optimized for searching large arrays for a given `value`,
+ * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=0] The index to start searching from.
+ * @param {Number} [largeSize=30] The length at which an array is considered large.
+ * @returns {Boolean} Returns `true` if `value` is found, else `false`.
+ */
+ function cachedContains(array, fromIndex, largeSize) {
+ fromIndex || (fromIndex = 0);
+
+ var length = array.length,
+ isLarge = (length - fromIndex) >= (largeSize || 30),
+ cache = isLarge ? {} : array;
+
+ if (isLarge) {
+ // init value cache
+ var key,
+ index = fromIndex - 1;
+
+ while (++index < length) {
+ // manually coerce `value` to string because `hasOwnProperty`, in some
+ // older versions of Firefox, coerces objects incorrectly
+ key = array[index] + '';
+ (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]);
+ }
+ }
+ return function(value) {
+ if (isLarge) {
+ var key = value + '';
+ return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1;
+ }
+ return indexOf(cache, value, fromIndex) > -1;
+ }
+ }
+
+ /**
+ * Creates compiled iteration functions. The iteration function will be created
+ * to iterate over only objects if the first argument of `options.args` is
+ * "object" or `options.inLoop.array` is falsey.
+ *
+ * @private
+ * @param {Object} [options1, options2, ...] The compile options objects.
+ *
+ * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks
+ * in the object loop.
+ *
+ * useStrict - A boolean to specify whether or not to include the ES5
+ * "use strict" directive.
+ *
+ * args - A string of comma separated arguments the iteration function will
+ * accept.
+ *
+ * init - A string to specify the initial value of the `result` variable.
+ *
+ * exit - A string of code to use in place of the default exit-early check
+ * of `if (!arguments[0]) return result`.
+ *
+ * top - A string of code to execute after the exit-early check but before
+ * the iteration branches.
+ *
+ * beforeLoop - A string or object containing an "array" or "object" property
+ * of code to execute before the array or object loops.
+ *
+ * inLoop - A string or object containing an "array" or "object" property
+ * of code to execute in the array or object loops.
+ *
+ * bottom - A string of code to execute after the iteration branches but
+ * before the `result` is returned.
+ *
+ * @returns {Function} Returns the compiled function.
+ */
+ function createIterator() {
+ var object,
+ prop,
+ value,
+ index = -1,
+ length = arguments.length;
+
+ // merge options into a template data object
+ var data = {
+ 'bottom': '',
+ 'exit': '',
+ 'init': '',
+ 'top': '',
+ 'arrayBranch': { 'beforeLoop': '' },
+ 'objectBranch': { 'beforeLoop': '' }
+ };
+
+ while (++index < length) {
+ object = arguments[index];
+ for (prop in object) {
+ value = (value = object[prop]) == null ? '' : value;
+ // keep this regexp explicit for the build pre-process
+ if (/beforeLoop|inLoop/.test(prop)) {
+ if (typeof value == 'string') {
+ value = { 'array': value, 'object': value };
+ }
+ data.arrayBranch[prop] = value.array;
+ data.objectBranch[prop] = value.object;
+ } else {
+ data[prop] = value;
+ }
+ }
+ }
+ // set additional template `data` values
+ var args = data.args,
+ firstArg = /^[^,]+/.exec(args)[0];
+
+ data.firstArg = firstArg;
+ data.hasDontEnumBug = hasDontEnumBug;
+ data.isKeysFast = isKeysFast;
+ data.noArgsEnum = noArgsEnum;
+ data.shadowed = shadowed;
+ data.useHas = data.useHas !== false;
+ data.useStrict = data.useStrict !== false;
+
+ if (!('noCharByIndex' in data)) {
+ data.noCharByIndex = noCharByIndex;
+ }
+ if (!data.exit) {
+ data.exit = 'if (!' + firstArg + ') return result';
+ }
+ if (firstArg != 'collection' || !data.arrayBranch.inLoop) {
+ data.arrayBranch = null;
+ }
+ // create the function factory
+ var factory = Function(
+ 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' +
+ 'hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' +
+ 'isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, ' +
+ 'propertyIsEnumerable, slice, stringClass, toString',
+ 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' +
+ 'return callee'
+ );
+ // return the compiled function
+ return factory(
+ arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn,
+ hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction,
+ isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys,
+ propertyIsEnumerable, slice, stringClass, toString
+ );
+ }
+
+ /**
+ * Used by `sortBy` to compare transformed `collection` values, stable sorting
+ * them in ascending order.
+ *
+ * @private
+ * @param {Object} a The object to compare to `b`.
+ * @param {Object} b The object to compare to `a`.
+ * @returns {Number} Returns the sort order indicator of `1` or `-1`.
+ */
+ function compareAscending(a, b) {
+ var ai = a.index,
+ bi = b.index;
+
+ a = a.criteria;
+ b = b.criteria;
+
+ if (a === undefined) {
+ return 1;
+ }
+ if (b === undefined) {
+ return -1;
+ }
+ // ensure a stable sort in V8 and other engines
+ // http://code.google.com/p/v8/issues/detail?id=90
+ return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1;
+ }
+
+ /**
+ * Used by `template` to replace tokens with their corresponding code snippets.
+ *
+ * @private
+ * @param {String} match The matched token.
+ * @param {String} index The `tokenized` index of the code snippet.
+ * @returns {String} Returns the code snippet.
+ */
+ function detokenize(match, index) {
+ return tokenized[index];
+ }
+
+ /**
+ * Used by `template` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Used by `escape` to escape characters for inclusion in HTML.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Checks if a given `value` is an object created by the `Object` constructor
+ * assuming objects created by the `Object` constructor have no inherited
+ * enumerable properties and that there are no `Object.prototype` extensions.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for
+ * `arguments` objects.
+ * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object,
+ * else `false`.
+ */
+ function isPlainObject(value, skipArgsCheck) {
+ // avoid non-objects and false positives for `arguments` objects
+ var result = false;
+ if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) {
+ return result;
+ }
+ // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+ // methods that are `typeof` "string" and still can coerce nodes to strings.
+ // Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
+ var ctor = value.constructor;
+ if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
+ (!isFunction(ctor) || ctor instanceof ctor)) {
+ // IE < 9 iterates inherited properties before own properties. If the first
+ // iterated property is an object's own property then there are no inherited
+ // enumerable properties.
+ if (iteratesOwnLast) {
+ forIn(value, function(objValue, objKey) {
+ result = !hasOwnProperty.call(value, objKey);
+ return false;
+ });
+ return result === false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ forIn(value, function(objValue, objKey) {
+ result = objKey;
+ });
+ return result === false || hasOwnProperty.call(value, result);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and the arguments (value, index, object).
+ *
+ * @private
+ * @param {Function} func The function to bind.
+ * @param {Mixed} [thisArg] The `this` binding of `func`.
+ * @returns {Function} Returns the new bound function.
+ */
+ function iteratorBind(func, thisArg) {
+ return function(value, index, object) {
+ return func.call(thisArg, value, index, object);
+ };
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @private
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Used by `template` to replace "escape" template delimiters with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} value The delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeEscape(match, value) {
+ if (match && reComplexDelimiter.test(value)) {
+ return '<e%-' + value + '%>';
+ }
+ var index = tokenized.length;
+ tokenized[index] = "' +\n__e(" + value + ") +\n'";
+ return token + index;
+ }
+
+ /**
+ * Used by `template` to replace "evaluate" template delimiters, or complex
+ * "escape" and "interpolate" delimiters, with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} escapeValue The complex "escape" delimiter value.
+ * @param {String} interpolateValue The complex "interpolate" delimiter value.
+ * @param {String} [evaluateValue] The "evaluate" delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeEvaluate(match, escapeValue, interpolateValue, evaluateValue) {
+ if (evaluateValue) {
+ var index = tokenized.length;
+ tokenized[index] = "';\n" + evaluateValue + ";\n__p += '";
+ return token + index;
+ }
+ return escapeValue
+ ? tokenizeEscape(null, escapeValue)
+ : tokenizeInterpolate(null, interpolateValue);
+ }
+
+ /**
+ * Used by `template` to replace "interpolate" template delimiters with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} value The delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeInterpolate(match, value) {
+ if (match && reComplexDelimiter.test(value)) {
+ return '<e%=' + value + '%>';
+ }
+ var index = tokenized.length;
+ tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'";
+ return token + index;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ return toString.call(value) == argsClass;
+ }
+ // fallback for browsers that can't detect `arguments` objects by [[Class]]
+ if (noArgsClass) {
+ isArguments = function(value) {
+ return !!(value && hasOwnProperty.call(value, 'callee'));
+ };
+ }
+
+ /**
+ * Checks if `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ return toString.call(value) == arrayClass;
+ };
+
+ /**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(''.concat);
+ * // => true
+ */
+ function isFunction(value) {
+ return typeof value == 'function';
+ }
+ // fallback for older versions of Chrome and Safari
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return toString.call(value) == funcClass;
+ };
+ }
+
+ /**
+ * A shim implementation of `Object.keys` that produces an array of the given
+ * object's own enumerable property names.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ */
+ var shimKeys = createIterator({
+ 'args': 'object',
+ 'init': '[]',
+ 'inLoop': 'result.push(index)'
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a clone of `value`. If `deep` is `true`, all nested objects will
+ * also be cloned otherwise they will be assigned by reference. If a value has
+ * a `clone` method it will be used to perform the clone. Functions, DOM nodes,
+ * `arguments` objects, and objects created by constructors other than `Object`
+ * are **not** cloned unless they have a custom `clone` method.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to clone.
+ * @param {Boolean} deep A flag to indicate a deep clone.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `deep`.
+ * @param {Array} [stack=[]] Internally used to keep track of traversed objects
+ * to avoid circular references.
+ * @param {Object} thorough Internally used to indicate whether or not to perform
+ * a more thorough clone of non-object values.
+ * @returns {Mixed} Returns the cloned `value`.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.clone({ 'name': 'moe' });
+ * // => { 'name': 'moe' }
+ *
+ * var shallow = _.clone(stooges);
+ * shallow[0] === stooges[0];
+ * // => true
+ *
+ * var deep = _.clone(stooges, true);
+ * shallow[0] === stooges[0];
+ * // => false
+ */
+ function clone(value, deep, guard, stack, thorough) {
+ if (value == null) {
+ return value;
+ }
+ if (guard) {
+ deep = false;
+ }
+ // avoid slower checks on primitives
+ thorough || (thorough = { 'value': null });
+ if (thorough.value == null) {
+ // primitives passed from iframes use the primary document's native prototypes
+ thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone);
+ }
+ // use custom `clone` method if available
+ var isObj = objectTypes[typeof value];
+ if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) {
+ thorough.value = null;
+ return value.clone(deep);
+ }
+ // inspect [[Class]]
+ if (isObj) {
+ // don't clone `arguments` objects, functions, or non-object Objects
+ var className = toString.call(value);
+ if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {
+ return value;
+ }
+ var isArr = className == arrayClass;
+ isObj = isArr || (className == objectClass ? isPlainObject(value, true) : isObj);
+ }
+ // shallow clone
+ if (!isObj || !deep) {
+ // don't clone functions
+ return isObj
+ ? (isArr ? slice.call(value) : extend({}, value))
+ : value;
+ }
+
+ var ctor = value.constructor;
+ switch (className) {
+ case boolClass:
+ return new ctor(value == true);
+
+ case dateClass:
+ return new ctor(+value);
+
+ case numberClass:
+ case stringClass:
+ return new ctor(value);
+
+ case regexpClass:
+ return ctor(value.source, reFlags.exec(value));
+ }
+
+ // check for circular references and return corresponding clone
+ stack || (stack = []);
+ var length = stack.length;
+ while (length--) {
+ if (stack[length].source == value) {
+ return stack[length].value;
+ }
+ }
+
+ // init cloned object
+ length = value.length;
+ var result = isArr ? ctor(length) : {};
+
+ // add current clone and original source value to the stack of traversed objects
+ stack.push({ 'value': result, 'source': value });
+
+ // recursively populate clone (susceptible to call stack limits)
+ if (isArr) {
+ var index = -1;
+ while (++index < length) {
+ result[index] = clone(value[index], deep, null, stack, thorough);
+ }
+ } else {
+ forOwn(value, function(objValue, key) {
+ result[key] = clone(objValue, deep, null, stack, thorough);
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Assigns enumerable properties of the default object(s) to the `destination`
+ * object for all `destination` properties that resolve to `null`/`undefined`.
+ * Once a property is set, additional defaults of the same property will be
+ * ignored.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [default1, default2, ...] The default objects.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var iceCream = { 'flavor': 'chocolate' };
+ * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' });
+ * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' }
+ */
+ var defaults = createIterator(extendIteratorOptions, {
+ 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop
+ });
+
+ /**
+ * Creates a shallow clone of `object` excluding the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Object} [prop1, prop2, ...] The properties to drop.
+ * @returns {Object} Returns an object without the dropped properties.
+ * @example
+ *
+ * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid');
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ var drop = createIterator({
+ 'useHas': false,
+ 'args': 'object',
+ 'init': '{}',
+ 'top': 'var props = concat.apply(ArrayProto, arguments)',
+ 'inLoop': 'if (indexOf(props, index) < 0) result[index] = value'
+ });
+
+ /**
+ * Assigns enumerable properties of the source object(s) to the `destination`
+ * object. Subsequent sources will overwrite propery assignments of previous
+ * sources.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [source1, source2, ...] The source objects.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.extend({ 'name': 'moe' }, { 'age': 40 });
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ var extend = createIterator(extendIteratorOptions);
+
+ /**
+ * Iterates over `object`'s own and inherited enumerable properties, executing
+ * the `callback` for each property. The `callback` is bound to `thisArg` and
+ * invoked with 3 arguments; (value, key, object). Callbacks may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Dog(name) {
+ * this.name = name;
+ * }
+ *
+ * Dog.prototype.bark = function() {
+ * alert('Woof, woof!');
+ * };
+ *
+ * _.forIn(new Dog('Dagny'), function(value, key) {
+ * alert(key);
+ * });
+ * // => alerts 'name' and 'bark' (order is not guaranteed)
+ */
+ var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, {
+ 'useHas': false
+ });
+
+ /**
+ * Iterates over `object`'s own enumerable properties, executing the `callback`
+ * for each property. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; (value, key, object). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * alert(key);
+ * });
+ * // => alerts '0', '1', and 'length' (order is not guaranteed)
+ */
+ var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions);
+
+ /**
+ * Creates a sorted array of all enumerable properties, own and inherited,
+ * of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ var functions = createIterator({
+ 'useHas': false,
+ 'args': 'object',
+ 'init': '[]',
+ 'inLoop': 'if (isFunction(value)) result.push(index)',
+ 'bottom': 'result.sort()'
+ });
+
+ /**
+ * Checks if the specified object `property` exists and is a direct property,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to check.
+ * @param {String} property The property to check for.
+ * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, property) {
+ return object ? hasOwnProperty.call(object, property) : false;
+ }
+
+ /**
+ * Checks if `value` is a boolean (`true` or `false`) value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false || toString.call(value) == boolClass;
+ }
+
+ /**
+ * Checks if `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return toString.call(value) == dateClass;
+ }
+
+ /**
+ * Checks if `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return value ? value.nodeType === 1 : false;
+ }
+
+ /**
+ * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+ * length of `0` and objects with no own enumerable properties are considered
+ * "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|String} value The value to inspect.
+ * @returns {Boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ *
+ * _.isEmpty('');
+ * // => true
+ */
+ var isEmpty = createIterator({
+ 'args': 'value',
+ 'init': 'true',
+ 'top':
+ 'var className = toString.call(value),\n' +
+ ' length = value.length;\n' +
+ 'if (arrayLikeClasses[className]' +
+ (noArgsClass ? ' || isArguments(value)' : '') + ' ||\n' +
+ ' (className == objectClass && length > -1 && length === length >>> 0 &&\n' +
+ ' isFunction(value.splice))' +
+ ') return !length',
+ 'inLoop': {
+ 'object': 'return false'
+ }
+ });
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other. If a value has an `isEqual` method it will be
+ * used to perform the comparison.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} a The value to compare.
+ * @param {Mixed} b The other value to compare.
+ * @param {Array} [stack=[]] Internally used to keep track of traversed objects
+ * to avoid circular references.
+ * @param {Object} thorough Internally used to indicate whether or not to perform
+ * a more thorough comparison of non-object values.
+ * @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
+ * @example
+ *
+ * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ *
+ * moe == clone;
+ * // => false
+ *
+ * _.isEqual(moe, clone);
+ * // => true
+ */
+ function isEqual(a, b, stack, thorough) {
+ // a strict comparison is necessary because `null == undefined`
+ if (a == null || b == null) {
+ return a === b;
+ }
+ // avoid slower checks on non-objects
+ thorough || (thorough = { 'value': null });
+ if (thorough.value == null) {
+ // primitives passed from iframes use the primary document's native prototypes
+ thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual);
+ }
+ if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) {
+ // unwrap any LoDash wrapped values
+ if (a._chain) {
+ a = a._wrapped;
+ }
+ if (b._chain) {
+ b = b._wrapped;
+ }
+ // use custom `isEqual` method if available
+ if (a.isEqual && isFunction(a.isEqual)) {
+ thorough.value = null;
+ return a.isEqual(b);
+ }
+ if (b.isEqual && isFunction(b.isEqual)) {
+ thorough.value = null;
+ return b.isEqual(a);
+ }
+ }
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ // compare [[Class]] names
+ var className = toString.call(a);
+ if (className != toString.call(b)) {
+ return false;
+ }
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return a != +a
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case regexpClass:
+ case stringClass:
+ // coerce regexes to strings (http://es5.github.com/#x15.10.6.4)
+ // treat string primitives and their corresponding object instances as equal
+ return a == b + '';
+ }
+ // exit early, in older browsers, if `a` is array-like but not `b`
+ var isArr = arrayLikeClasses[className];
+ if (noArgsClass && !isArr && (isArr = isArguments(a)) && !isArguments(b)) {
+ return false;
+ }
+ // exit for functions and DOM nodes
+ if (!isArr && (className != objectClass || (noNodeClass && (
+ (typeof a.toString != 'function' && typeof (a + '') == 'string') ||
+ (typeof b.toString != 'function' && typeof (b + '') == 'string'))))) {
+ return false;
+ }
+
+ // assume cyclic structures are equal
+ // the algorithm for detecting cyclic structures is adapted from ES 5.1
+ // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3)
+ stack || (stack = []);
+ var length = stack.length;
+ while (length--) {
+ if (stack[length] == a) {
+ return true;
+ }
+ }
+
+ var index = -1,
+ result = true,
+ size = 0;
+
+ // add `a` to the stack of traversed objects
+ stack.push(a);
+
+ // recursively compare objects and arrays (susceptible to call stack limits)
+ if (isArr) {
+ // compare lengths to determine if a deep comparison is necessary
+ size = a.length;
+ result = size == b.length;
+
+ if (result) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ if (!(result = isEqual(a[size], b[size], stack, thorough))) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ var ctorA = a.constructor,
+ ctorB = b.constructor;
+
+ // non `Object` object instances with different constructors are not equal
+ if (ctorA != ctorB && !(
+ isFunction(ctorA) && ctorA instanceof ctorA &&
+ isFunction(ctorB) && ctorB instanceof ctorB
+ )) {
+ return false;
+ }
+ // deep compare objects
+ for (var prop in a) {
+ if (hasOwnProperty.call(a, prop)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) {
+ return false;
+ }
+ }
+ }
+ // ensure both objects have the same number of properties
+ for (prop in b) {
+ // The JS engine in Adobe products, like InDesign, has a bug that causes
+ // `!size--` to throw an error so it must be wrapped in parentheses.
+ // https://github.com/documentcloud/underscore/issues/355
+ if (hasOwnProperty.call(b, prop) && !(size--)) {
+ // `size` will be `-1` if `b` has more properties than `a`
+ return false;
+ }
+ }
+ // handle JScript [[DontEnum]] bug
+ if (hasDontEnumBug) {
+ while (++index < 7) {
+ prop = shadowed[index];
+ if (hasOwnProperty.call(a, prop) &&
+ !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if `value` is a finite number.
+ *
+ * Note: This is not the same as native `isFinite`, which will return true for
+ * booleans and other values. See http://es5.github.com/#x15.1.2.5.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && toString.call(value) == numberClass;
+ }
+
+ /**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.com/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return value ? objectTypes[typeof value] : false;
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * Note: This is not the same as native `isNaN`, which will return true for
+ * `undefined` and other values. See http://es5.github.com/#x15.1.2.4.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return toString.call(value) == numberClass && value != +value
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is a number.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5;
+ * // => true
+ */
+ function isNumber(value) {
+ return toString.call(value) == numberClass;
+ }
+
+ /**
+ * Checks if `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/moe/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return toString.call(value) == regexpClass;
+ }
+
+ /**
+ * Checks if `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('moe');
+ * // => true
+ */
+ function isString(value) {
+ return toString.call(value) == stringClass;
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return value === undefined;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property names of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (order is not guaranteed)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ var type = typeof object;
+
+ // avoid iterating over the `prototype` property
+ if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) {
+ return shimKeys(object);
+ }
+ return object && objectTypes[type]
+ ? nativeKeys(object)
+ : [];
+ };
+
+ /**
+ * Merges enumerable properties of the source object(s) into the `destination`
+ * object. Subsequent sources will overwrite propery assignments of previous
+ * sources.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [source1, source2, ...] The source objects.
+ * @param {Object} [indicator] Internally used to indicate that the `stack`
+ * argument is an array of traversed objects instead of another source object.
+ * @param {Array} [stack=[]] Internally used to keep track of traversed objects
+ * to avoid circular references.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe' },
+ * { 'name': 'larry' }
+ * ];
+ *
+ * var ages = [
+ * { 'age': 40 },
+ * { 'age': 50 }
+ * ];
+ *
+ * _.merge(stooges, ages);
+ * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }]
+ */
+ var merge = createIterator(extendIteratorOptions, {
+ 'args': 'object, source, indicator, stack',
+ 'top':
+ 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' +
+ 'if (!recursive) stack = [];\n' +
+ 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' +
+ ' if (iteratee = arguments[argsIndex]) {',
+ 'inLoop':
+ 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' +
+ ' found = false; stackLength = stack.length;\n' +
+ ' while (stackLength--) {\n' +
+ ' if (found = stack[stackLength].source == value) break\n' +
+ ' }\n' +
+ ' if (found) {\n' +
+ ' result[index] = stack[stackLength].value\n' +
+ ' } else {\n' +
+ ' destValue = (destValue = result[index]) && isArr\n' +
+ ' ? (isArray(destValue) ? destValue : [])\n' +
+ ' : (isPlainObject(destValue) ? destValue : {});\n' +
+ ' stack.push({ value: destValue, source: value });\n' +
+ ' result[index] = callee(destValue, value, isPlainObject, stack)\n' +
+ ' }\n' +
+ '} else if (value != null) {\n' +
+ ' result[index] = value\n' +
+ '}'
+ });
+
+ /**
+ * Creates a shallow clone of `object` composed of the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Object} [prop1, prop2, ...] The properties to pick.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age');
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ function pick(object) {
+ var result = {};
+ if (!object) {
+ return result;
+ }
+ var prop,
+ index = 0,
+ props = concat.apply(ArrayProto, arguments),
+ length = props.length;
+
+ // start `index` at `1` to skip `object`
+ while (++index < length) {
+ prop = props[index];
+ if (prop in object) {
+ result[prop] = object[prop];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the size of `value` by returning `value.length` if `value` is an
+ * array, string, or `arguments` object. If `value` is an object, size is
+ * determined by returning the number of own enumerable properties it has.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|String} value The value to inspect.
+ * @returns {Number} Returns `value.length` or number of own enumerable properties.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('curly');
+ * // => 5
+ */
+ function size(value) {
+ if (!value) {
+ return 0;
+ }
+ var className = toString.call(value),
+ length = value.length;
+
+ // return `value.length` for `arguments` objects, arrays, strings, and DOM
+ // query collections of libraries like jQuery and MooTools
+ // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653
+ // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609
+ if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) ||
+ (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) {
+ return length;
+ }
+ return keys(value).length;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property values of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3]
+ */
+ var values = createIterator({
+ 'args': 'object',
+ 'init': '[]',
+ 'inLoop': 'result.push(value)'
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if a given `target` element is present in a `collection` using strict
+ * equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Mixed} target The value to check for.
+ * @returns {Boolean} Returns `true` if the `target` element is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 3);
+ * // => true
+ *
+ * _.contains({ 'name': 'moe', 'age': 40 }, 'moe');
+ * // => true
+ *
+ * _.contains('curly', 'ur');
+ * // => true
+ */
+ var contains = createIterator({
+ 'args': 'collection, target',
+ 'init': 'false',
+ 'noCharByIndex': false,
+ 'beforeLoop': {
+ 'array': 'if (toString.call(iteratee) == stringClass) return collection.indexOf(target) > -1'
+ },
+ 'inLoop': 'if (value === target) return true'
+ });
+
+ /**
+ * Creates an object composed of keys returned from running each element of
+ * `collection` through a `callback`. The corresponding value of each key is
+ * the number of times the key was returned by `callback`. The `callback` is
+ * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to count by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback The function called per iteration or
+ * property name to count by.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createIterator(baseIteratorOptions, countByIteratorOptions);
+
+ /**
+ * Checks if the `callback` returns a truthy value for **all** elements of a
+ * `collection`. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Boolean} Returns `true` if all elements pass the callback check, else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ */
+ var every = createIterator(baseIteratorOptions, everyIteratorOptions);
+
+ /**
+ * Examines each element in a `collection`, returning an array of all elements
+ * the `callback` returns truthy for. The `callback` is bound to `thisArg` and
+ * invoked with 3 arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of elements that passed callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ */
+ var filter = createIterator(baseIteratorOptions, filterIteratorOptions);
+
+ /**
+ * Examines each element in a `collection`, returning the first one the `callback`
+ * returns truthy for. The function returns as soon as it finds an acceptable
+ * element, and does not iterate over the entire `collection`. The `callback` is
+ * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias detect
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the element that passed the callback check, else `undefined`.
+ * @example
+ *
+ * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => 2
+ */
+ var find = createIterator(baseIteratorOptions, forEachIteratorOptions, {
+ 'init': '',
+ 'inLoop': 'if (callback(value, index, collection)) return value'
+ });
+
+ /**
+ * Iterates over a `collection`, executing the `callback` for each element in
+ * the `collection`. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; (value, index|key, collection). Callbacks may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array|Object} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(alert).join(',');
+ * // => alerts each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert);
+ * // => alerts each number (order is not guaranteed)
+ */
+ var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions);
+
+ /**
+ * Creates an object composed of keys returned from running each element of
+ * `collection` through a `callback`. The corresponding value of each key is an
+ * array of elements passed to `callback` that returned the key. The `callback`
+ * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to count by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback The function called per iteration or
+ * property name to group by.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, {
+ 'inLoop':
+ 'prop = callback(value, index, collection);\n' +
+ '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)'
+ });
+
+ /**
+ * Invokes the method named by `methodName` on each element in the `collection`.
+ * Additional arguments will be passed to each invoked method. If `methodName`
+ * is a function it will be invoked for, and `this` bound to, each element
+ * in the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of values returned from each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ var invoke = createIterator(mapIteratorOptions, {
+ 'args': 'collection, methodName',
+ 'top':
+ 'var args = slice.call(arguments, 2),\n' +
+ ' isFunc = typeof methodName == \'function\'',
+ 'inLoop': {
+ 'array':
+ 'result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)',
+ 'object':
+ 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') +
+ '((isFunc ? methodName : value[methodName]).apply(value, args))'
+ }
+ });
+
+ /**
+ * Creates a new array of values by running each element in the `collection`
+ * through a `callback`. The `callback` is bound to `thisArg` and invoked with
+ * 3 arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of elements returned by the callback.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (order is not guaranteed)
+ */
+ var map = createIterator(baseIteratorOptions, mapIteratorOptions);
+
+ /**
+ * Retrieves the value of a specified property from all elements in
+ * the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {String} property The property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.pluck(stooges, 'name');
+ * // => ['moe', 'larry', 'curly']
+ */
+ var pluck = createIterator(mapIteratorOptions, {
+ 'args': 'collection, property',
+ 'inLoop': {
+ 'array': 'result[index] = value[property]',
+ 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(value[property])'
+ }
+ });
+
+ /**
+ * Boils down a `collection` to a single value. The initial state of the
+ * reduction is `accumulator` and each successive step of it should be returned
+ * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4
+ * arguments; for arrays they are (accumulator, value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; });
+ * // => 6
+ */
+ var reduce = createIterator({
+ 'args': 'collection, callback, accumulator, thisArg',
+ 'init': 'accumulator',
+ 'top':
+ 'var noaccum = arguments.length < 3;\n' +
+ 'if (thisArg) callback = iteratorBind(callback, thisArg)',
+ 'beforeLoop': {
+ 'array': 'if (noaccum) result = collection[++index]'
+ },
+ 'inLoop': {
+ 'array':
+ 'result = callback(result, value, index, collection)',
+ 'object':
+ 'result = noaccum\n' +
+ ' ? (noaccum = false, value)\n' +
+ ' : callback(result, value, index, collection)'
+ }
+ });
+
+ /**
+ * The right-associative version of `_.reduce`.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ if (!collection) {
+ return accumulator;
+ }
+
+ var length = collection.length,
+ noaccum = arguments.length < 3;
+
+ if(thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ // Opera 10.53-10.60 JITted `length >>> 0` returns the wrong value for negative numbers
+ if (length > -1 && length === length >>> 0) {
+ var iteratee = noCharByIndex && toString.call(collection) == stringClass
+ ? collection.split('')
+ : collection;
+
+ if (length && noaccum) {
+ accumulator = iteratee[--length];
+ }
+ while (length--) {
+ accumulator = callback(accumulator, iteratee[length], length, collection);
+ }
+ return accumulator;
+ }
+
+ var prop,
+ props = keys(collection);
+
+ length = props.length;
+ if (length && noaccum) {
+ accumulator = collection[props[--length]];
+ }
+ while (length--) {
+ prop = props[length];
+ accumulator = callback(accumulator, collection[prop], prop, collection);
+ }
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter`, this method returns the values of a
+ * `collection` that `callback` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of elements that did **not** pass the callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ */
+ var reject = createIterator(baseIteratorOptions, filterIteratorOptions, {
+ 'inLoop': '!' + filterIteratorOptions.inLoop
+ });
+
+ /**
+ * Checks if the `callback` returns a truthy value for **any** element of a
+ * `collection`. The function returns as soon as it finds passing value, and
+ * does not iterate over the entire `collection`. The `callback` is bound to
+ * `thisArg` and invoked with 3 arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Boolean} Returns `true` if any element passes the callback check, else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false]);
+ * // => true
+ */
+ var some = createIterator(baseIteratorOptions, everyIteratorOptions, {
+ 'init': 'false',
+ 'inLoop': everyIteratorOptions.inLoop.replace('!', '')
+ });
+
+ /**
+ * Creates a new array, stable sorted in ascending order by the results of
+ * running each element of `collection` through a `callback`. The `callback`
+ * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to sort by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback The function called per iteration or
+ * property name to sort by.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of sorted elements.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * _.sortBy(['larry', 'brendan', 'moe'], 'length');
+ * // => ['moe', 'larry', 'brendan']
+ */
+ var sortBy = createIterator(baseIteratorOptions, countByIteratorOptions, mapIteratorOptions, {
+ 'inLoop': {
+ 'array':
+ 'result[index] = {\n' +
+ ' criteria: callback(value, index, collection),\n' +
+ ' index: index,\n' +
+ ' value: value\n' +
+ '}',
+ 'object':
+ 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '({\n' +
+ ' criteria: callback(value, index, collection),\n' +
+ ' index: index,\n' +
+ ' value: value\n' +
+ '})'
+ },
+ 'bottom':
+ 'result.sort(compareAscending);\n' +
+ 'length = result.length;\n' +
+ 'while (length--) {\n' +
+ ' result[length] = result[length].value\n' +
+ '}'
+ });
+
+ /**
+ * Converts the `collection`, to an array. Useful for converting the
+ * `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ if (!collection) {
+ return [];
+ }
+ if (collection.toArray && isFunction(collection.toArray)) {
+ return collection.toArray();
+ }
+ var length = collection.length;
+ if (length > -1 && length === length >>> 0) {
+ return (noArraySliceOnStrings ? toString.call(collection) == stringClass : typeof collection == 'string')
+ ? collection.split('')
+ : slice.call(collection);
+ }
+ return values(collection);
+ }
+
+ /**
+ * Examines each element in a `collection`, returning an array of all elements
+ * that contain the given `properties`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Object} properties The object of properties/values to filter by.
+ * @returns {Array} Returns a new array of elements that contain the given `properties`.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.where(stooges, { 'age': 40 });
+ * // => [{ 'name': 'moe', 'age': 40 }]
+ */
+ var where = createIterator(filterIteratorOptions, {
+ 'args': 'collection, properties',
+ 'top':
+ 'var pass, prop, propIndex, props = [];\n' +
+ 'forIn(properties, function(value, prop) { props.push(prop) });\n' +
+ 'var propsLength = props.length',
+ 'inLoop':
+ 'for (pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' +
+ ' prop = props[propIndex];\n' +
+ ' if (!(pass = value[prop] === properties[prop])) break\n' +
+ '}\n' +
+ 'if (pass) result.push(value)'
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a new array with all falsey values of `array` removed. The values
+ * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (array[index]) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new array of `array` elements not present in the other arrays
+ * using strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Array} [array1, array2, ...] Arrays to check.
+ * @returns {Array} Returns a new array of `array` elements not present in the
+ * other arrays.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var index = -1,
+ length = array.length,
+ flattened = concat.apply(result, arguments),
+ contains = cachedContains(flattened, length);
+
+ while (++index < length) {
+ if (!contains(array[index])) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the first element of the `array`. Pass `n` to return the first `n`
+ * elements of the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the first element or an array of the first `n`
+ * elements of `array`.
+ * @example
+ *
+ * _.first([5, 4, 3, 2, 1]);
+ * // => 5
+ */
+ function first(array, n, guard) {
+ if (array) {
+ return (n == null || guard) ? array[0] : slice.call(array, 0, n);
+ }
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `shallow` is
+ * truthy, `array` will only be flattened a single level.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @param {Boolean} shallow A flag to indicate only flattening a single level.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ */
+ function flatten(array, shallow) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var value,
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ value = array[index];
+ if (isArray(value)) {
+ push.apply(result, shallow ? value : flatten(value));
+ } else {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the `array` is already
+ * sorted, passing `true` for `isSorted` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Boolean|Number} [fromIndex=0] The index to start searching from or
+ * `true` to perform a binary search on a sorted `array`.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ if (!array) {
+ return -1;
+ }
+ var index = -1,
+ length = array.length;
+
+ if (fromIndex) {
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1;
+ } else {
+ index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ }
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets all but the last element of `array`. Pass `n` to exclude the last `n`
+ * elements from the result.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the last element or `n` elements of `array`.
+ * @example
+ *
+ * _.initial([3, 2, 1]);
+ * // => [3, 2]
+ */
+ function initial(array, n, guard) {
+ if (!array) {
+ return [];
+ }
+ return slice.call(array, 0, -((n == null || guard) ? 1 : n));
+ }
+
+ /**
+ * Computes the intersection of all the passed-in arrays using strict equality
+ * for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique elements, in order, that are
+ * present in **all** of the arrays.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var value,
+ index = -1,
+ length = array.length,
+ others = slice.call(arguments, 1),
+ cache = [];
+
+ while (++index < length) {
+ value = array[index];
+ if (indexOf(result, value) < 0 &&
+ every(others, function(other, index) {
+ return (cache[index] || (cache[index] = cachedContains(other)))(value);
+ })) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the last element of the `array`. Pass `n` to return the lasy `n`
+ * elementsvof the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the last element or an array of the last `n`
+ * elements of `array`.
+ * @example
+ *
+ * _.last([3, 2, 1]);
+ * // => 1
+ */
+ function last(array, n, guard) {
+ if (array) {
+ var length = array.length;
+ return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length);
+ }
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=array.length-1] The index to start searching from.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ if (!array) {
+ return -1;
+ }
+ var index = array.length;
+ if (fromIndex && typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Retrieves the maximum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to
+ * `thisArg` and invoked with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the maximum value.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.max(stooges, function(stooge) { return stooge.age; });
+ * // => { 'name': 'curly', 'age': 60 };
+ */
+ function max(array, callback, thisArg) {
+ var computed = -Infinity,
+ result = computed;
+
+ if (!array) {
+ return result;
+ }
+ var current,
+ index = -1,
+ length = array.length;
+
+ if (!callback) {
+ while (++index < length) {
+ if (array[index] > result) {
+ result = array[index];
+ }
+ }
+ return result;
+ }
+ if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ current = callback(array[index], index, array);
+ if (current > computed) {
+ computed = current;
+ result = array[index];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to `thisArg`
+ * and invoked with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the minimum value.
+ * @example
+ *
+ * _.min([10, 5, 100, 2, 1000]);
+ * // => 2
+ */
+ function min(array, callback, thisArg) {
+ var computed = Infinity,
+ result = computed;
+
+ if (!array) {
+ return result;
+ }
+ var current,
+ index = -1,
+ length = array.length;
+
+ if (!callback) {
+ while (++index < length) {
+ if (array[index] < result) {
+ result = array[index];
+ }
+ }
+ return result;
+ }
+ if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ current = callback(array[index], index, array);
+ if (current < computed) {
+ computed = current;
+ result = array[index];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `stop`. This method is a port of Python's
+ * `range()` function. See http://docs.python.org/library/functions.html#range.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Number} [start=0] The start of the range.
+ * @param {Number} end The end of the range.
+ * @param {Number} [step=1] The value to increment or descrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(10);
+ * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ *
+ * _.range(1, 11);
+ * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ *
+ * _.range(0, 30, 5);
+ * // => [0, 5, 10, 15, 20, 25]
+ *
+ * _.range(0, -10, -1);
+ * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ start = +start || 0;
+ step = +step || 1;
+
+ if (end == null) {
+ end = start;
+ start = 0;
+ }
+ // use `Array(length)` so V8 will avoid the slower "dictionary" mode
+ // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s
+ var index = -1,
+ length = Math.max(0, Math.ceil((end - start) / step)),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial`, this method gets all but the first value of
+ * `array`. Pass `n` to exclude the first `n` values from the result.
+ *
+ * @static
+ * @memberOf _
+ * @alias tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the first value or `n` values of `array`.
+ * @example
+ *
+ * _.rest([3, 2, 1]);
+ * // => [2, 1]
+ */
+ function rest(array, n, guard) {
+ if (!array) {
+ return [];
+ }
+ return slice.call(array, (n == null || guard) ? 1 : n);
+ }
+
+ /**
+ * Creates a new array of shuffled `array` values, using a version of the
+ * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to shuffle.
+ * @returns {Array} Returns a new shuffled array.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(array) {
+ if (!array) {
+ return [];
+ }
+ var rand,
+ index = -1,
+ length = array.length,
+ result = Array(length);
+
+ while (++index < length) {
+ rand = Math.floor(Math.random() * (index + 1));
+ result[index] = result[rand];
+ result[rand] = array[index];
+ }
+ return result;
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which the `value`
+ * should be inserted into `array` in order to maintain the sort order of the
+ * sorted `array`. If `callback` is passed, it will be executed for `value` and
+ * each element in `array` to compute their sort ranking. The `callback` is
+ * bound to `thisArg` and invoked with 1 argument; (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Mixed} value The value to evaluate.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Number} Returns the index at which the value should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 40], 35);
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ if (!array) {
+ return 0;
+ }
+ var mid,
+ low = 0,
+ high = array.length;
+
+ if (callback) {
+ if (thisArg) {
+ callback = bind(callback, thisArg);
+ }
+ value = callback(value);
+ while (low < high) {
+ mid = (low + high) >>> 1;
+ callback(array[mid]) < value ? low = mid + 1 : high = mid;
+ }
+ } else {
+ while (low < high) {
+ mid = (low + high) >>> 1;
+ array[mid] < value ? low = mid + 1 : high = mid;
+ }
+ }
+ return low;
+ }
+
+ /**
+ * Computes the union of the passed-in arrays using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique values, in order, that are
+ * present in one or more of the arrays.
+ * @example
+ *
+ * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2, 3, 101, 10]
+ */
+ function union() {
+ var index = -1,
+ result = [],
+ flattened = concat.apply(result, arguments),
+ length = flattened.length;
+
+ while (++index < length) {
+ if (indexOf(result, flattened[index]) < 0) {
+ result.push(flattened[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a duplicate-value-free version of the `array` using strict equality
+ * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true`
+ * for `isSorted` will run a faster algorithm. If `callback` is passed, each
+ * element of `array` is passed through a callback` before uniqueness is computed.
+ * The `callback` is bound to `thisArg` and invoked with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); });
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2, 3]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var computed,
+ index = -1,
+ length = array.length,
+ seen = [];
+
+ // juggle arguments
+ if (typeof isSorted == 'function') {
+ thisArg = callback;
+ callback = isSorted;
+ isSorted = false;
+ }
+ if (!callback) {
+ callback = identity;
+ } else if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ computed = callback(array[index], index, array);
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : indexOf(seen, computed) < 0
+ ) {
+ seen.push(computed);
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new array with all occurrences of the passed values removed using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {Mixed} [value1, value2, ...] Values to remove.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var index = -1,
+ length = array.length,
+ contains = cachedContains(arguments, 1, 20);
+
+ while (++index < length) {
+ if (!contains(array[index])) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Groups the elements of each array at their corresponding indexes. Useful for
+ * separate data sources that are coordinated through matching array indexes.
+ * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix
+ * in a similar fashion.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of grouped elements.
+ * @example
+ *
+ * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
+ * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]]
+ */
+ function zip(array) {
+ if (!array) {
+ return [];
+ }
+ var index = -1,
+ length = max(pluck(arguments, 'length')),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = pluck(arguments, index);
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed from an array of `keys` and an array of `values`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} keys The array of keys.
+ * @param {Array} [values=[]] The array of values.
+ * @returns {Object} Returns an object composed of the given keys and
+ * corresponding values.
+ * @example
+ *
+ * _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]);
+ * // => { 'moe': 30, 'larry': 40, 'curly': 50 }
+ */
+ function zipObject(keys, values) {
+ if (!keys) {
+ return {};
+ }
+ var index = -1,
+ length = keys.length,
+ result = {};
+
+ values || (values = []);
+ while (++index < length) {
+ result[keys[index]] = values[index];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a new function that is restricted to executing only after it is
+ * called `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Number} n The number of times the function must be called before
+ * it is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var renderNotes = _.after(notes.length, render);
+ * _.forEach(notes, function(note) {
+ * note.asyncSave({ 'success': renderNotes });
+ * });
+ * // `renderNotes` is run once, after all notes have saved
+ */
+ function after(n, func) {
+ if (n < 1) {
+ return func();
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * passed to the bound function. Lazy defined methods may be bound by passing
+ * the object they are bound to as `func` and the method name as `thisArg`.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function|Object} func The function to bind or the object the method belongs to.
+ * @param {Mixed} [thisArg] The `this` binding of `func` or the method name.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * // basic bind
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'moe' }, 'hi');
+ * func();
+ * // => 'hi moe'
+ *
+ * // lazy bind
+ * var object = {
+ * 'name': 'moe',
+ * 'greet': function(greeting) {
+ * return greeting + ' ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bind(object, 'greet', 'hi');
+ * func();
+ * // => 'hi moe'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + ', ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hi, moe!'
+ */
+ function bind(func, thisArg) {
+ var methodName,
+ isFunc = isFunction(func);
+
+ // juggle arguments
+ if (!isFunc) {
+ methodName = thisArg;
+ thisArg = func;
+ }
+ // use `Function#bind` if it exists and is fast
+ // (in V8 `Function#bind` is slower except when partially applied)
+ else if (isBindFast || (nativeBind && arguments.length > 2)) {
+ return nativeBind.call.apply(nativeBind, arguments);
+ }
+
+ var partialArgs = slice.call(arguments, 2);
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.com/#x15.3.4.5
+ var args = arguments,
+ thisBinding = thisArg;
+
+ if (!isFunc) {
+ func = thisArg[methodName];
+ }
+ if (partialArgs.length) {
+ args = args.length
+ ? partialArgs.concat(slice.call(args))
+ : partialArgs;
+ }
+ if (this instanceof bound) {
+ // get `func` instance if `bound` is invoked in a `new` expression
+ noop.prototype = func.prototype;
+ thisBinding = new noop;
+
+ // mimic the constructor's `return` behavior
+ // http://es5.github.com/#x13.2.2
+ var result = func.apply(thisBinding, args);
+ return result && objectTypes[typeof result]
+ ? result
+ : thisBinding
+ }
+ return func.apply(thisBinding, args);
+ }
+ return bound;
+ }
+
+ /**
+ * Binds methods on `object` to `object`, overwriting the existing method.
+ * If no method names are provided, all the function properties of `object`
+ * will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {String} [methodName1, methodName2, ...] Method names on the object to bind.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var buttonView = {
+ * 'label': 'lodash',
+ * 'onClick': function() { alert('clicked: ' + this.label); }
+ * };
+ *
+ * _.bindAll(buttonView);
+ * jQuery('#lodash_button').on('click', buttonView.onClick);
+ * // => When the button is clicked, `this.label` will have the correct value
+ */
+ var bindAll = createIterator({
+ 'useHas': false,
+ 'useStrict': false,
+ 'args': 'object',
+ 'init': 'object',
+ 'top':
+ 'var funcs = arguments,\n' +
+ ' length = funcs.length;\n' +
+ 'if (length > 1) {\n' +
+ ' for (var index = 1; index < length; index++) {\n' +
+ ' result[funcs[index]] = bind(result[funcs[index]], result)\n' +
+ ' }\n' +
+ ' return result\n' +
+ '}',
+ 'inLoop':
+ 'if (isFunction(result[index])) {\n' +
+ ' result[index] = bind(result[index], result)\n' +
+ '}'
+ });
+
+ /**
+ * Creates a new function that is the composition of the passed functions,
+ * where each function consumes the return value of the function that follows.
+ * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} [func1, func2, ...] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var greet = function(name) { return 'hi: ' + name; };
+ * var exclaim = function(statement) { return statement + '!'; };
+ * var welcome = _.compose(exclaim, greet);
+ * welcome('moe');
+ * // => 'hi: moe!'
+ */
+ function compose() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a new function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked. Pass
+ * `true` for `immediate` to cause debounce to invoke `func` on the leading,
+ * instead of the trailing, edge of the `wait` timeout. Subsequent calls to
+ * the debounced function will return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {Number} wait The number of milliseconds to delay.
+ * @param {Boolean} immediate A flag to indicate execution is on the leading
+ * edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * var lazyLayout = _.debounce(calculateLayout, 300);
+ * jQuery(window).on('resize', lazyLayout);
+ */
+ function debounce(func, wait, immediate) {
+ var args,
+ result,
+ thisArg,
+ timeoutId;
+
+ function delayed() {
+ timeoutId = null;
+ if (!immediate) {
+ func.apply(thisArg, args);
+ }
+ }
+
+ return function() {
+ var isImmediate = immediate && !timeoutId;
+ args = arguments;
+ thisArg = this;
+
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(delayed, wait);
+
+ if (isImmediate) {
+ result = func.apply(thisArg, args);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * will be passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {Number} wait The number of milliseconds to delay execution.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * var log = _.bind(console.log, console);
+ * _.delay(log, 1000, 'logged later');
+ * // => 'logged later' (Appears after one second.)
+ */
+ function delay(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function() { return func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments will be passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * _.defer(function() { alert('deferred'); });
+ * // returns from the function before `alert` is called
+ */
+ function defer(func) {
+ var args = slice.call(arguments, 1);
+ return setTimeout(function() { return func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Creates a new function that memoizes the result of `func`. If `resolver` is
+ * passed, it will be used to determine the cache key for storing the result
+ * based on the arguments passed to the memoized function. By default, the first
+ * argument passed to the memoized function is used as the cache key.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ */
+ function memoize(func, resolver) {
+ var cache = {};
+ return function() {
+ var prop = resolver ? resolver.apply(this, arguments) : arguments[0];
+ return hasOwnProperty.call(cache, prop)
+ ? cache[prop]
+ : (cache[prop] = func.apply(this, arguments));
+ };
+ }
+
+ /**
+ * Creates a new function that is restricted to one execution. Repeat calls to
+ * the function will return the value of the first call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // Application is only created once.
+ */
+ function once(func) {
+ var result,
+ ran = false;
+
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+
+ // clear the `func` variable so the function may be garbage collected
+ func = null;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those passed to the new function. This method
+ * is similar `bind`, except it does **not** alter the `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ': ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('moe');
+ * // => 'hi: moe'
+ */
+ function partial(func) {
+ var args = slice.call(arguments, 1),
+ argsLength = args.length;
+
+ return function() {
+ var result,
+ others = arguments;
+
+ if (others.length) {
+ args.length = argsLength;
+ push.apply(args, others);
+ }
+ result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args);
+ args.length = argsLength;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a new function that, when executed, will only call the `func`
+ * function at most once per every `wait` milliseconds. If the throttled
+ * function is invoked more than once during the `wait` timeout, `func` will
+ * also be called on the trailing edge of the timeout. Subsequent calls to the
+ * throttled function will return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {Number} wait The number of milliseconds to throttle executions to.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ */
+ function throttle(func, wait) {
+ var args,
+ result,
+ thisArg,
+ timeoutId,
+ lastCalled = 0;
+
+ function trailingCall() {
+ lastCalled = new Date;
+ timeoutId = null;
+ func.apply(thisArg, args);
+ }
+
+ return function() {
+ var now = new Date,
+ remain = wait - (now - lastCalled);
+
+ args = arguments;
+ thisArg = this;
+
+ if (remain <= 0) {
+ lastCalled = now;
+ result = func.apply(thisArg, args);
+ }
+ else if (!timeoutId) {
+ timeoutId = setTimeout(trailingCall, remain);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a new function that passes `value` to the `wrapper` function as its
+ * first argument. Additional arguments passed to the new function are appended
+ * to those passed to the `wrapper` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Mixed} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var hello = function(name) { return 'hello: ' + name; };
+ * hello = _.wrap(hello, function(func) {
+ * return 'before, ' + func('moe') + ', after';
+ * });
+ * hello();
+ * // => 'before, hello: moe, after'
+ */
+ function wrap(value, wrapper) {
+ return function() {
+ var args = [value];
+ if (arguments.length) {
+ push.apply(args, arguments);
+ }
+ return wrapper.apply(this, args);
+ };
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'`
+ * characters.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} string The string to escape.
+ * @returns {String} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Moe, Larry & Curly');
+ * // => "Moe, Larry &amp; Curly"
+ */
+ function escape(string) {
+ return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This function returns the first argument passed to it.
+ *
+ * Note: It is used throughout Lo-Dash as a default callback.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Mixed} value Any value.
+ * @returns {Mixed} Returns `value`.
+ * @example
+ *
+ * var moe = { 'name': 'moe' };
+ * moe === _.identity(moe);
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds functions properties of `object` to the `lodash` function and chainable
+ * wrapper.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object of function properties to add to `lodash`.
+ * @example
+ *
+ * _.mixin({
+ * 'capitalize': function(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ * });
+ *
+ * _.capitalize('larry');
+ * // => 'Larry'
+ *
+ * _('curly').capitalize();
+ * // => 'Curly'
+ */
+ function mixin(object) {
+ forEach(functions(object), function(methodName) {
+ var func = lodash[methodName] = object[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var args = [this._wrapped];
+ if (arguments.length) {
+ push.apply(args, arguments);
+ }
+ var result = func.apply(lodash, args);
+ if (this._chain) {
+ result = new LoDash(result);
+ result._chain = true;
+ }
+ return result;
+ };
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ window._ = oldDash;
+ return this;
+ }
+
+ /**
+ * Resolves the value of `property` on `object`. If `property` is a function
+ * it will be invoked and its result returned, else the property value is
+ * returned. If `object` is falsey, then `null` is returned.
+ *
+ * @deprecated
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {String} property The property to get the result of.
+ * @returns {Mixed} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, property) {
+ // based on Backbone's private `getValue` function
+ // https://github.com/documentcloud/backbone/blob/0.9.2/backbone.js#L1419-1424
+ if (!object) {
+ return null;
+ }
+ var value = object[property];
+ return isFunction(value) ? object[property]() : value;
+ }
+
+ /**
+ * A micro-templating method that handles arbitrary delimiters, preserves
+ * whitespace, and correctly escapes quotes within interpolated code.
+ *
+ * Note: In the development build `_.template` utilizes sourceURLs for easier
+ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ *
+ * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp`
+ * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page.
+ * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} text The template text.
+ * @param {Obect} data The data object used to populate the text.
+ * @param {Object} options The options object.
+ * @returns {Function|String} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using a compiled template
+ * var compiled = _.template('hello: <%= name %>');
+ * compiled({ 'name': 'moe' });
+ * // => 'hello: moe'
+ *
+ * var list = '<% _.forEach(people, function(name) { %> <li><%= name %></li> <% }); %>';
+ * _.template(list, { 'people': ['moe', 'larry', 'curly'] });
+ * // => '<li>moe</li><li>larry</li><li>curly</li>'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<b><%- value %></b>', { 'value': '<script>' });
+ * // => '<b>&lt;script></b>'
+ *
+ * // using the internal `print` function in "evaluate" delimiters
+ * _.template('<% print("Hello " + epithet); %>', { 'epithet': 'stooge' });
+ * // => 'Hello stooge.'
+ *
+ * // using custom template delimiter settings
+ * _.templateSettings = {
+ * 'interpolate': /\{\{(.+?)\}\}/g
+ * };
+ *
+ * _.template('Hello {{ name }}!', { 'name': 'Mustache' });
+ * // => 'Hello Mustache!'
+ *
+ * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+ * var compiled = _.template('hello: <%= data.name %>', null, { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * var __t, __p = '', __e = _.escape;
+ * __p += 'hello: ' + ((__t = ( data.name )) == null ? '' : __t);
+ * return __p;
+ * }
+ *
+ * // using the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and a stack trace
+ * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
+ function template(text, data, options) {
+ // based on John Resig's `tmpl` implementation
+ // http://ejohn.org/blog/javascript-micro-templating/
+ // and Laura Doktorova's doT.js
+ // https://github.com/olado/doT
+ options || (options = {});
+ text += '';
+
+ var isEvaluating,
+ result,
+ escapeDelimiter = options.escape,
+ evaluateDelimiter = options.evaluate,
+ interpolateDelimiter = options.interpolate,
+ settings = lodash.templateSettings,
+ variable = options.variable || settings.variable,
+ hasVariable = variable;
+
+ // use default settings if no options object is provided
+ if (escapeDelimiter == null) {
+ escapeDelimiter = settings.escape;
+ }
+ if (evaluateDelimiter == null) {
+ // use `false` as the fallback value, instead of leaving it `undefined`,
+ // so the initial assignment of `reEvaluateDelimiter` will still occur
+ evaluateDelimiter = settings.evaluate || false;
+ }
+ if (interpolateDelimiter == null) {
+ interpolateDelimiter = settings.interpolate;
+ }
+
+ // tokenize delimiters to avoid escaping them
+ if (escapeDelimiter) {
+ text = text.replace(escapeDelimiter, tokenizeEscape);
+ }
+ if (interpolateDelimiter) {
+ text = text.replace(interpolateDelimiter, tokenizeInterpolate);
+ }
+ if (evaluateDelimiter != lastEvaluateDelimiter) {
+ // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate`
+ // and internal `<e%- %>`, `<e%= %>` delimiters
+ lastEvaluateDelimiter = evaluateDelimiter;
+ reEvaluateDelimiter = RegExp(
+ '<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>' +
+ (evaluateDelimiter ? '|' + evaluateDelimiter.source : '')
+ , 'g');
+ }
+ isEvaluating = tokenized.length;
+ text = text.replace(reEvaluateDelimiter, tokenizeEvaluate);
+ isEvaluating = isEvaluating != tokenized.length;
+
+ // escape characters that cannot be included in string literals and
+ // detokenize delimiter code snippets
+ text = "__p += '" + text
+ .replace(reUnescapedString, escapeStringChar)
+ .replace(reToken, detokenize) + "';\n";
+
+ // clear stored code snippets
+ tokenized.length = 0;
+
+ // if `variable` is not specified and the template contains "evaluate"
+ // delimiters, wrap a with-statement around the generated code to add the
+ // data object to the top of the scope chain
+ if (!hasVariable) {
+ variable = lastVariable || 'obj';
+
+ if (isEvaluating) {
+ text = 'with (' + variable + ') {\n' + text + '\n}\n';
+ }
+ else {
+ if (variable != lastVariable) {
+ // generate `reDoubleVariable` to match references like `obj.obj` inside
+ // transformed "escape" and "interpolate" delimiters
+ lastVariable = variable;
+ reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g');
+ }
+ // avoid a with-statement by prepending data object references to property names
+ text = text
+ .replace(reInsertVariable, '$&' + variable + '.')
+ .replace(reDoubleVariable, '$1__d');
+ }
+ }
+
+ // cleanup code by stripping empty strings
+ text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text)
+ .replace(reEmptyStringMiddle, '$1')
+ .replace(reEmptyStringTrailing, '$1;');
+
+ // frame code as the function body
+ text = 'function(' + variable + ') {\n' +
+ (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+ 'var __t, __p = \'\', __e = _.escape' +
+ (isEvaluating
+ ? ', __j = Array.prototype.join;\n' +
+ 'function print() { __p += __j.call(arguments, \'\') }\n'
+ : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n'
+ ) +
+ text +
+ 'return __p\n}';
+
+ // add a sourceURL for easier debugging
+ // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ if (useSourceURL) {
+ text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']';
+ }
+
+ try {
+ result = Function('_', 'return ' + text)(lodash);
+ } catch(e) {
+ // defer syntax errors until the compiled template is executed to allow
+ // examining the `source` property beforehand and for consistency,
+ // because other template related errors occur at execution
+ result = function() { throw e; };
+ }
+
+ if (data) {
+ return result(data);
+ }
+ // provide the compiled function's source via its `toString` method, in
+ // supported environments, or the `source` property as a convenience for
+ // inlining compiled templates during the build process
+ result.source = text;
+ return result;
+ }
+
+ /**
+ * Executes the `callback` function `n` times. The `callback` is bound to
+ * `thisArg` and invoked with 1 argument; (index).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Number} n The number of times to execute the callback.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @example
+ *
+ * _.times(3, function() { genie.grantWish(); });
+ * // => calls `genie.grantWish()` 3 times
+ *
+ * _.times(3, function() { this.grantWish(); }, genie);
+ * // => also calls `genie.grantWish()` 3 times
+ */
+ function times(n, callback, thisArg) {
+ var index = -1;
+ if (thisArg) {
+ while (++index < n) {
+ callback.call(thisArg, index);
+ }
+ } else {
+ while (++index < n) {
+ callback(index);
+ }
+ }
+ }
+
+ /**
+ * Generates a unique id. If `prefix` is passed, the id will be appended to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} [prefix] The value to prefix the id with.
+ * @returns {Number|String} Returns a numeric id if no prefix is passed, else
+ * a string id may be returned.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ */
+ function uniqueId(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Wraps the value in a `lodash` wrapper object.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {Mixed} value The value to wrap.
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * var youngest = _.chain(stooges)
+ * .sortBy(function(stooge) { return stooge.age; })
+ * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; })
+ * .first()
+ * .value();
+ * // => 'moe is 40'
+ */
+ function chain(value) {
+ value = new LoDash(value);
+ value._chain = true;
+ return value;
+ }
+
+ /**
+ * Invokes `interceptor` with the `value` as the first argument, and then
+ * returns `value`. The purpose of this method is to "tap into" a method chain,
+ * in order to perform operations on intermediate results within the chain.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {Mixed} value The value to pass to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {Mixed} Returns `value`.
+ * @example
+ *
+ * _.chain([1,2,3,200])
+ * .filter(function(num) { return num % 2 == 0; })
+ * .tap(alert)
+ * .map(function(num) { return num * num })
+ * .value();
+ * // => // [2, 200] (alerted)
+ * // => [4, 40000]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * Enables method chaining on the wrapper object.
+ *
+ * @name chain
+ * @deprecated
+ * @memberOf _
+ * @category Chaining
+ * @returns {Mixed} Returns the wrapper object.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperChain() {
+ this._chain = true;
+ return this;
+ }
+
+ /**
+ * Extracts the wrapped value.
+ *
+ * @name value
+ * @memberOf _
+ * @category Chaining
+ * @returns {Mixed} Returns the wrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperValue() {
+ return this._wrapped;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type String
+ */
+ lodash.VERSION = '0.5.2';
+
+ // assign static methods
+ lodash.after = after;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.chain = chain;
+ lodash.clone = clone;
+ lodash.compact = compact;
+ lodash.compose = compose;
+ lodash.contains = contains;
+ lodash.countBy = countBy;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.drop = drop;
+ lodash.escape = escape;
+ lodash.every = every;
+ lodash.extend = extend;
+ lodash.filter = filter;
+ lodash.find = find;
+ lodash.first = first;
+ lodash.flatten = flatten;
+ lodash.forEach = forEach;
+ lodash.forIn = forIn;
+ lodash.forOwn = forOwn;
+ lodash.functions = functions;
+ lodash.groupBy = groupBy;
+ lodash.has = has;
+ lodash.identity = identity;
+ lodash.indexOf = indexOf;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.invoke = invoke;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isBoolean = isBoolean;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isNaN = isNaN;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isString = isString;
+ lodash.isUndefined = isUndefined;
+ lodash.keys = keys;
+ lodash.last = last;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.map = map;
+ lodash.max = max;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.min = min;
+ lodash.mixin = mixin;
+ lodash.noConflict = noConflict;
+ lodash.once = once;
+ lodash.partial = partial;
+ lodash.pick = pick;
+ lodash.pluck = pluck;
+ lodash.range = range;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.reject = reject;
+ lodash.rest = rest;
+ lodash.result = result;
+ lodash.shuffle = shuffle;
+ lodash.size = size;
+ lodash.some = some;
+ lodash.sortBy = sortBy;
+ lodash.sortedIndex = sortedIndex;
+ lodash.tap = tap;
+ lodash.template = template;
+ lodash.throttle = throttle;
+ lodash.times = times;
+ lodash.toArray = toArray;
+ lodash.union = union;
+ lodash.uniq = uniq;
+ lodash.uniqueId = uniqueId;
+ lodash.values = values;
+ lodash.where = where;
+ lodash.without = without;
+ lodash.wrap = wrap;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+
+ // assign aliases
+ lodash.all = every;
+ lodash.any = some;
+ lodash.collect = map;
+ lodash.detect = find;
+ lodash.each = forEach;
+ lodash.foldl = reduce;
+ lodash.foldr = reduceRight;
+ lodash.head = first;
+ lodash.include = contains;
+ lodash.inject = reduce;
+ lodash.methods = functions;
+ lodash.select = filter;
+ lodash.tail = rest;
+ lodash.take = first;
+ lodash.unique = uniq;
+
+ // add pseudo private properties used and removed during the build process
+ lodash._iteratorTemplate = iteratorTemplate;
+ lodash._shimKeys = shimKeys;
+
+ /*--------------------------------------------------------------------------*/
+
+ // assign private `LoDash` constructor's prototype
+ LoDash.prototype = lodash.prototype;
+
+ // add all static functions to `LoDash.prototype`
+ mixin(lodash);
+
+ // add `LoDash.prototype.chain` after calling `mixin()` to avoid overwriting
+ // it with the wrapped `lodash.chain`
+ LoDash.prototype.chain = wrapperChain;
+ LoDash.prototype.value = wrapperValue;
+
+ // add all mutator Array functions to the wrapper.
+ forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+ var func = ArrayProto[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var value = this._wrapped;
+ func.apply(value, arguments);
+
+ // Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array
+ // `shift()` and `splice()` functions that fail to remove the last element,
+ // `value[0]`, of array-like objects even though the `length` property is
+ // set to `0`. The `shift()` method is buggy in IE 8 compatibility mode,
+ // while `splice()` is buggy regardless of mode in IE < 9 and buggy in
+ // compatibility mode in IE 9.
+ if (value.length === 0) {
+ delete value[0];
+ }
+ if (this._chain) {
+ value = new LoDash(value);
+ value._chain = true;
+ }
+ return value;
+ };
+ });
+
+ // add all accessor Array functions to the wrapper.
+ forEach(['concat', 'join', 'slice'], function(methodName) {
+ var func = ArrayProto[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var value = this._wrapped,
+ result = func.apply(value, arguments);
+
+ if (this._chain) {
+ result = new LoDash(result);
+ result._chain = true;
+ }
+ return result;
+ };
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Lo-Dash
+ // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // Expose Lo-Dash to the global object even when an AMD loader is present in
+ // case Lo-Dash was injected by a third-party script and not intended to be
+ // loaded as a module. The global assignment can be reverted in the Lo-Dash
+ // module via its `noConflict()` method.
+ window._ = lodash;
+
+ // define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module
+ define(function() {
+ return lodash;
+ });
+ }
+ // check for `exports` after `define` in case a build optimizer adds an `exports` object
+ else if (freeExports) {
+ // in Node.js or RingoJS v0.8.0+
+ if (typeof module == 'object' && module && module.exports == freeExports) {
+ (module.exports = lodash)._ = lodash;
+ }
+ // in Narwhal or RingoJS v0.7.0-
+ else {
+ freeExports._ = lodash;
+ }
+ }
+ else {
+ // in a browser or Rhino
+ window._ = lodash;
+ }
+}(this));
diff --git a/module/web/static/js/libs/require-2.0.6.js b/module/web/static/js/libs/require-2.0.6.js
new file mode 100644
index 000000000..b592d5f22
--- /dev/null
+++ b/module/web/static/js/libs/require-2.0.6.js
@@ -0,0 +1,2041 @@
+/** vim: et:ts=4:sw=4:sts=4
+ * @license RequireJS 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/requirejs for details
+ */
+//Not using strict: uneven strict support in browsers, #392, and causes
+//problems with requirejs.exec()/transpiler plugins that may not be strict.
+/*jslint regexp: true, nomen: true, sloppy: true */
+/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */
+
+var requirejs, require, define;
+(function (global) {
+ var req, s, head, baseElement, dataMain, src,
+ interactiveScript, currentlyAddingScript, mainScript, subPath,
+ version = '2.0.6',
+ commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
+ cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+ jsSuffixRegExp = /\.js$/,
+ currDirRegExp = /^\.\//,
+ op = Object.prototype,
+ ostring = op.toString,
+ hasOwn = op.hasOwnProperty,
+ ap = Array.prototype,
+ aps = ap.slice,
+ apsp = ap.splice,
+ isBrowser = !!(typeof window !== 'undefined' && navigator && document),
+ isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
+ //PS3 indicates loaded and complete, but need to wait for complete
+ //specifically. Sequence is 'loading', 'loaded', execution,
+ // then 'complete'. The UA check is unfortunate, but not sure how
+ //to feature test w/o causing perf issues.
+ readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
+ /^complete$/ : /^(complete|loaded)$/,
+ defContextName = '_',
+ //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
+ isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
+ contexts = {},
+ cfg = {},
+ globalDefQueue = [],
+ useInteractive = false;
+
+ function isFunction(it) {
+ return ostring.call(it) === '[object Function]';
+ }
+
+ function isArray(it) {
+ return ostring.call(it) === '[object Array]';
+ }
+
+ /**
+ * Helper function for iterating over an array. If the func returns
+ * a true value, it will break out of the loop.
+ */
+ function each(ary, func) {
+ if (ary) {
+ var i;
+ for (i = 0; i < ary.length; i += 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function for iterating over an array backwards. If the func
+ * returns a true value, it will break out of the loop.
+ */
+ function eachReverse(ary, func) {
+ if (ary) {
+ var i;
+ for (i = ary.length - 1; i > -1; i -= 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ function hasProp(obj, prop) {
+ return hasOwn.call(obj, prop);
+ }
+
+ /**
+ * Cycles over properties in an object and calls a function for each
+ * property value. If the function returns a truthy value, then the
+ * iteration is stopped.
+ */
+ function eachProp(obj, func) {
+ var prop;
+ for (prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ if (func(obj[prop], prop)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple function to mix in properties from source into target,
+ * but only if target does not already have a property of the same name.
+ * This is not robust in IE for transferring methods that match
+ * Object.prototype names, but the uses of mixin here seem unlikely to
+ * trigger a problem related to that.
+ */
+ function mixin(target, source, force, deepStringMixin) {
+ if (source) {
+ eachProp(source, function (value, prop) {
+ if (force || !hasProp(target, prop)) {
+ if (deepStringMixin && typeof value !== 'string') {
+ if (!target[prop]) {
+ target[prop] = {};
+ }
+ mixin(target[prop], value, force, deepStringMixin);
+ } else {
+ target[prop] = value;
+ }
+ }
+ });
+ }
+ return target;
+ }
+
+ //Similar to Function.prototype.bind, but the 'this' object is specified
+ //first, since it is easier to read/figure out what 'this' will be.
+ function bind(obj, fn) {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+ }
+
+ function scripts() {
+ return document.getElementsByTagName('script');
+ }
+
+ //Allow getting a global that expressed in
+ //dot notation, like 'a.b.c'.
+ function getGlobal(value) {
+ if (!value) {
+ return value;
+ }
+ var g = global;
+ each(value.split('.'), function (part) {
+ g = g[part];
+ });
+ return g;
+ }
+
+ function makeContextModuleFunc(func, relMap, enableBuildCallback) {
+ return function () {
+ //A version of a require function that passes a moduleName
+ //value for items that may need to
+ //look up paths relative to the moduleName
+ var args = aps.call(arguments, 0), lastArg;
+ if (enableBuildCallback &&
+ isFunction((lastArg = args[args.length - 1]))) {
+ lastArg.__requireJsBuild = true;
+ }
+ args.push(relMap);
+ return func.apply(null, args);
+ };
+ }
+
+ function addRequireMethods(req, context, relMap) {
+ each([
+ ['toUrl'],
+ ['undef'],
+ ['defined', 'requireDefined'],
+ ['specified', 'requireSpecified']
+ ], function (item) {
+ var prop = item[1] || item[0];
+ req[item[0]] = context ? makeContextModuleFunc(context[prop], relMap) :
+ //If no context, then use default context. Reference from
+ //contexts instead of early binding to default context, so
+ //that during builds, the latest instance of the default
+ //context with its config gets used.
+ function () {
+ var ctx = contexts[defContextName];
+ return ctx[prop].apply(ctx, arguments);
+ };
+ });
+ }
+
+ /**
+ * Constructs an error with a pointer to an URL with more information.
+ * @param {String} id the error ID that maps to an ID on a web page.
+ * @param {String} message human readable error.
+ * @param {Error} [err] the original error, if there is one.
+ *
+ * @returns {Error}
+ */
+ function makeError(id, msg, err, requireModules) {
+ var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
+ e.requireType = id;
+ e.requireModules = requireModules;
+ if (err) {
+ e.originalError = err;
+ }
+ return e;
+ }
+
+ if (typeof define !== 'undefined') {
+ //If a define is already in play via another AMD loader,
+ //do not overwrite.
+ return;
+ }
+
+ if (typeof requirejs !== 'undefined') {
+ if (isFunction(requirejs)) {
+ //Do not overwrite and existing requirejs instance.
+ return;
+ }
+ cfg = requirejs;
+ requirejs = undefined;
+ }
+
+ //Allow for a require config object
+ if (typeof require !== 'undefined' && !isFunction(require)) {
+ //assume it is a config object.
+ cfg = require;
+ require = undefined;
+ }
+
+ function newContext(contextName) {
+ var inCheckLoaded, Module, context, handlers,
+ checkLoadedTimeoutId,
+ config = {
+ waitSeconds: 7,
+ baseUrl: './',
+ paths: {},
+ pkgs: {},
+ shim: {}
+ },
+ registry = {},
+ undefEvents = {},
+ defQueue = [],
+ defined = {},
+ urlFetched = {},
+ requireCounter = 1,
+ unnormalizedCounter = 1,
+ //Used to track the order in which modules
+ //should be executed, by the order they
+ //load. Important for consistent cycle resolution
+ //behavior.
+ waitAry = [];
+
+ /**
+ * Trims the . and .. from an array of path segments.
+ * It will keep a leading path segment if a .. will become
+ * the first path segment, to help with module name lookups,
+ * which act like paths, but can be remapped. But the end result,
+ * all paths that use this function should look normalized.
+ * NOTE: this method MODIFIES the input array.
+ * @param {Array} ary the array of path segments.
+ */
+ function trimDots(ary) {
+ var i, part;
+ for (i = 0; ary[i]; i += 1) {
+ part = ary[i];
+ if (part === '.') {
+ ary.splice(i, 1);
+ i -= 1;
+ } else if (part === '..') {
+ if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
+ //End of the line. Keep at least one non-dot
+ //path segment at the front so it can be mapped
+ //correctly to disk. Otherwise, there is likely
+ //no path mapping for a path starting with '..'.
+ //This can still fail, but catches the most reasonable
+ //uses of ..
+ break;
+ } else if (i > 0) {
+ ary.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @param {Boolean} applyMap apply the map config to the value. Should
+ * only be done if this normalization is for a dependency ID.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName, applyMap) {
+ var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
+ foundMap, foundI, foundStarMap, starI,
+ baseParts = baseName && baseName.split('/'),
+ normalizedBaseParts = baseParts,
+ map = config.map,
+ starMap = map && map['*'];
+
+ //Adjust any relative paths.
+ if (name && name.charAt(0) === '.') {
+ //If have a base name, try to normalize against it,
+ //otherwise, assume it is a top-level require that will
+ //be relative to baseUrl in the end.
+ if (baseName) {
+ if (config.pkgs[baseName]) {
+ //If the baseName is a package name, then just treat it as one
+ //name to concat the name with.
+ normalizedBaseParts = baseParts = [baseName];
+ } else {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that 'directory' and not name of the baseName's
+ //module. For instance, baseName of 'one/two/three', maps to
+ //'one/two/three.js', but we want the directory, 'one/two' for
+ //this normalization.
+ normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+ }
+
+ name = normalizedBaseParts.concat(name.split('/'));
+ trimDots(name);
+
+ //Some use of packages may use a . path to reference the
+ //'main' module name, so normalize for that.
+ pkgConfig = config.pkgs[(pkgName = name[0])];
+ name = name.join('/');
+ if (pkgConfig && name === pkgName + '/' + pkgConfig.main) {
+ name = pkgName;
+ }
+ } else if (name.indexOf('./') === 0) {
+ // No baseName, so this is ID is resolved relative
+ // to baseUrl, pull off the leading dot.
+ name = name.substring(2);
+ }
+ }
+
+ //Apply map config if available.
+ if (applyMap && (baseParts || starMap) && map) {
+ nameParts = name.split('/');
+
+ for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join('/');
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = map[baseParts.slice(0, j).join('/')];
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = mapValue[nameSegment];
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ foundI = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if (foundMap) {
+ break;
+ }
+
+ //Check for a star map match, but just hold on to it,
+ //if there is a shorter segment match later in a matching
+ //config, then favor over this star map.
+ if (!foundStarMap && starMap && starMap[nameSegment]) {
+ foundStarMap = starMap[nameSegment];
+ starI = i;
+ }
+ }
+
+ if (!foundMap && foundStarMap) {
+ foundMap = foundStarMap;
+ foundI = starI;
+ }
+
+ if (foundMap) {
+ nameParts.splice(0, foundI, foundMap);
+ name = nameParts.join('/');
+ }
+ }
+
+ return name;
+ }
+
+ function removeScript(name) {
+ if (isBrowser) {
+ each(scripts(), function (scriptNode) {
+ if (scriptNode.getAttribute('data-requiremodule') === name &&
+ scriptNode.getAttribute('data-requirecontext') === context.contextName) {
+ scriptNode.parentNode.removeChild(scriptNode);
+ return true;
+ }
+ });
+ }
+ }
+
+ function hasPathFallback(id) {
+ var pathConfig = config.paths[id];
+ if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
+ removeScript(id);
+ //Pop off the first array value, since it failed, and
+ //retry
+ pathConfig.shift();
+ context.undef(id);
+ context.require([id]);
+ return true;
+ }
+ }
+
+ /**
+ * Creates a module mapping that includes plugin prefix, module
+ * name, and path. If parentModuleMap is provided it will
+ * also normalize the name via require.normalize()
+ *
+ * @param {String} name the module name
+ * @param {String} [parentModuleMap] parent module map
+ * for the module name, used to resolve relative names.
+ * @param {Boolean} isNormalized: is the ID already normalized.
+ * This is true if this call is done for a define() module ID.
+ * @param {Boolean} applyMap: apply the map config to the ID.
+ * Should only be true if this map is for a dependency.
+ *
+ * @returns {Object}
+ */
+ function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
+ var url, pluginModule, suffix,
+ index = name ? name.indexOf('!') : -1,
+ prefix = null,
+ parentName = parentModuleMap ? parentModuleMap.name : null,
+ originalName = name,
+ isDefine = true,
+ normalizedName = '';
+
+ //If no name, then it means it is a require call, generate an
+ //internal name.
+ if (!name) {
+ isDefine = false;
+ name = '_@r' + (requireCounter += 1);
+ }
+
+ if (index !== -1) {
+ prefix = name.substring(0, index);
+ name = name.substring(index + 1, name.length);
+ }
+
+ if (prefix) {
+ prefix = normalize(prefix, parentName, applyMap);
+ pluginModule = defined[prefix];
+ }
+
+ //Account for relative paths if there is a base name.
+ if (name) {
+ if (prefix) {
+ if (pluginModule && pluginModule.normalize) {
+ //Plugin is loaded, use its normalize method.
+ normalizedName = pluginModule.normalize(name, function (name) {
+ return normalize(name, parentName, applyMap);
+ });
+ } else {
+ normalizedName = normalize(name, parentName, applyMap);
+ }
+ } else {
+ //A regular module.
+ normalizedName = normalize(name, parentName, applyMap);
+ url = context.nameToUrl(normalizedName);
+ }
+ }
+
+ //If the id is a plugin id that cannot be determined if it needs
+ //normalization, stamp it with a unique ID so two matching relative
+ //ids that may conflict can be separate.
+ suffix = prefix && !pluginModule && !isNormalized ?
+ '_unnormalized' + (unnormalizedCounter += 1) :
+ '';
+
+ return {
+ prefix: prefix,
+ name: normalizedName,
+ parentMap: parentModuleMap,
+ unnormalized: !!suffix,
+ url: url,
+ originalName: originalName,
+ isDefine: isDefine,
+ id: (prefix ?
+ prefix + '!' + normalizedName :
+ normalizedName) + suffix
+ };
+ }
+
+ function getModule(depMap) {
+ var id = depMap.id,
+ mod = registry[id];
+
+ if (!mod) {
+ mod = registry[id] = new context.Module(depMap);
+ }
+
+ return mod;
+ }
+
+ function on(depMap, name, fn) {
+ var id = depMap.id,
+ mod = registry[id];
+
+ if (hasProp(defined, id) &&
+ (!mod || mod.defineEmitComplete)) {
+ if (name === 'defined') {
+ fn(defined[id]);
+ }
+ } else {
+ getModule(depMap).on(name, fn);
+ }
+ }
+
+ function onError(err, errback) {
+ var ids = err.requireModules,
+ notified = false;
+
+ if (errback) {
+ errback(err);
+ } else {
+ each(ids, function (id) {
+ var mod = registry[id];
+ if (mod) {
+ //Set error on module, so it skips timeout checks.
+ mod.error = err;
+ if (mod.events.error) {
+ notified = true;
+ mod.emit('error', err);
+ }
+ }
+ });
+
+ if (!notified) {
+ req.onError(err);
+ }
+ }
+ }
+
+ /**
+ * Internal method to transfer globalQueue items to this context's
+ * defQueue.
+ */
+ function takeGlobalQueue() {
+ //Push all the globalDefQueue items into the context's defQueue
+ if (globalDefQueue.length) {
+ //Array splice in the values since the context code has a
+ //local var ref to defQueue, so cannot just reassign the one
+ //on context.
+ apsp.apply(defQueue,
+ [defQueue.length - 1, 0].concat(globalDefQueue));
+ globalDefQueue = [];
+ }
+ }
+
+ /**
+ * Helper function that creates a require function object to give to
+ * modules that ask for it as a dependency. It needs to be specific
+ * per module because of the implication of path mappings that may
+ * need to be relative to the module name.
+ */
+ function makeRequire(mod, enableBuildCallback, altRequire) {
+ var relMap = mod && mod.map,
+ modRequire = makeContextModuleFunc(altRequire || context.require,
+ relMap,
+ enableBuildCallback);
+
+ addRequireMethods(modRequire, context, relMap);
+ modRequire.isBrowser = isBrowser;
+
+ return modRequire;
+ }
+
+ handlers = {
+ 'require': function (mod) {
+ return makeRequire(mod);
+ },
+ 'exports': function (mod) {
+ mod.usingExports = true;
+ if (mod.map.isDefine) {
+ return (mod.exports = defined[mod.map.id] = {});
+ }
+ },
+ 'module': function (mod) {
+ return (mod.module = {
+ id: mod.map.id,
+ uri: mod.map.url,
+ config: function () {
+ return (config.config && config.config[mod.map.id]) || {};
+ },
+ exports: defined[mod.map.id]
+ });
+ }
+ };
+
+ function removeWaiting(id) {
+ //Clean up machinery used for waiting modules.
+ delete registry[id];
+
+ each(waitAry, function (mod, i) {
+ if (mod.map.id === id) {
+ waitAry.splice(i, 1);
+ if (!mod.defined) {
+ context.waitCount -= 1;
+ }
+ return true;
+ }
+ });
+ }
+
+ function findCycle(mod, traced, processed) {
+ var id = mod.map.id,
+ depArray = mod.depMaps,
+ foundModule;
+
+ //Do not bother with unitialized modules or not yet enabled
+ //modules.
+ if (!mod.inited) {
+ return;
+ }
+
+ //Found the cycle.
+ if (traced[id]) {
+ return mod;
+ }
+
+ traced[id] = true;
+
+ //Trace through the dependencies.
+ each(depArray, function (depMap) {
+ var depId = depMap.id,
+ depMod = registry[depId];
+
+ if (!depMod || processed[depId] ||
+ !depMod.inited || !depMod.enabled) {
+ return;
+ }
+
+ return (foundModule = findCycle(depMod, traced, processed));
+ });
+
+ processed[id] = true;
+
+ return foundModule;
+ }
+
+ function forceExec(mod, traced, uninited) {
+ var id = mod.map.id,
+ depArray = mod.depMaps;
+
+ if (!mod.inited || !mod.map.isDefine) {
+ return;
+ }
+
+ if (traced[id]) {
+ return defined[id];
+ }
+
+ traced[id] = mod;
+
+ each(depArray, function (depMap) {
+ var depId = depMap.id,
+ depMod = registry[depId],
+ value;
+
+ if (handlers[depId]) {
+ return;
+ }
+
+ if (depMod) {
+ if (!depMod.inited || !depMod.enabled) {
+ //Dependency is not inited,
+ //so this module cannot be
+ //given a forced value yet.
+ uninited[id] = true;
+ return;
+ }
+
+ //Get the value for the current dependency
+ value = forceExec(depMod, traced, uninited);
+
+ //Even with forcing it may not be done,
+ //in particular if the module is waiting
+ //on a plugin resource.
+ if (!uninited[depId]) {
+ mod.defineDepById(depId, value);
+ }
+ }
+ });
+
+ mod.check(true);
+
+ return defined[id];
+ }
+
+ function modCheck(mod) {
+ mod.check();
+ }
+
+ function checkLoaded() {
+ var map, modId, err, usingPathFallback,
+ waitInterval = config.waitSeconds * 1000,
+ //It is possible to disable the wait interval by using waitSeconds of 0.
+ expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
+ noLoads = [],
+ stillLoading = false,
+ needCycleCheck = true;
+
+ //Do not bother if this call was a result of a cycle break.
+ if (inCheckLoaded) {
+ return;
+ }
+
+ inCheckLoaded = true;
+
+ //Figure out the state of all the modules.
+ eachProp(registry, function (mod) {
+ map = mod.map;
+ modId = map.id;
+
+ //Skip things that are not enabled or in error state.
+ if (!mod.enabled) {
+ return;
+ }
+
+ if (!mod.error) {
+ //If the module should be executed, and it has not
+ //been inited and time is up, remember it.
+ if (!mod.inited && expired) {
+ if (hasPathFallback(modId)) {
+ usingPathFallback = true;
+ stillLoading = true;
+ } else {
+ noLoads.push(modId);
+ removeScript(modId);
+ }
+ } else if (!mod.inited && mod.fetched && map.isDefine) {
+ stillLoading = true;
+ if (!map.prefix) {
+ //No reason to keep looking for unfinished
+ //loading. If the only stillLoading is a
+ //plugin resource though, keep going,
+ //because it may be that a plugin resource
+ //is waiting on a non-plugin cycle.
+ return (needCycleCheck = false);
+ }
+ }
+ }
+ });
+
+ if (expired && noLoads.length) {
+ //If wait time expired, throw error of unloaded modules.
+ err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
+ err.contextName = context.contextName;
+ return onError(err);
+ }
+
+ //Not expired, check for a cycle.
+ if (needCycleCheck) {
+
+ each(waitAry, function (mod) {
+ if (mod.defined) {
+ return;
+ }
+
+ var cycleMod = findCycle(mod, {}, {}),
+ traced = {};
+
+ if (cycleMod) {
+ forceExec(cycleMod, traced, {});
+
+ //traced modules may have been
+ //removed from the registry, but
+ //their listeners still need to
+ //be called.
+ eachProp(traced, modCheck);
+ }
+ });
+
+ //Now that dependencies have
+ //been satisfied, trigger the
+ //completion check that then
+ //notifies listeners.
+ eachProp(registry, modCheck);
+ }
+
+ //If still waiting on loads, and the waiting load is something
+ //other than a plugin resource, or there are still outstanding
+ //scripts, then just try back later.
+ if ((!expired || usingPathFallback) && stillLoading) {
+ //Something is still waiting to load. Wait for it, but only
+ //if a timeout is not already in effect.
+ if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
+ checkLoadedTimeoutId = setTimeout(function () {
+ checkLoadedTimeoutId = 0;
+ checkLoaded();
+ }, 50);
+ }
+ }
+
+ inCheckLoaded = false;
+ }
+
+ Module = function (map) {
+ this.events = undefEvents[map.id] || {};
+ this.map = map;
+ this.shim = config.shim[map.id];
+ this.depExports = [];
+ this.depMaps = [];
+ this.depMatched = [];
+ this.pluginMaps = {};
+ this.depCount = 0;
+
+ /* this.exports this.factory
+ this.depMaps = [],
+ this.enabled, this.fetched
+ */
+ };
+
+ Module.prototype = {
+ init: function (depMaps, factory, errback, options) {
+ options = options || {};
+
+ //Do not do more inits if already done. Can happen if there
+ //are multiple define calls for the same module. That is not
+ //a normal, common case, but it is also not unexpected.
+ if (this.inited) {
+ return;
+ }
+
+ this.factory = factory;
+
+ if (errback) {
+ //Register for errors on this module.
+ this.on('error', errback);
+ } else if (this.events.error) {
+ //If no errback already, but there are error listeners
+ //on this module, set up an errback to pass to the deps.
+ errback = bind(this, function (err) {
+ this.emit('error', err);
+ });
+ }
+
+ //Do a copy of the dependency array, so that
+ //source inputs are not modified. For example
+ //"shim" deps are passed in here directly, and
+ //doing a direct modification of the depMaps array
+ //would affect that config.
+ this.depMaps = depMaps && depMaps.slice(0);
+ this.depMaps.rjsSkipMap = depMaps.rjsSkipMap;
+
+ this.errback = errback;
+
+ //Indicate this module has be initialized
+ this.inited = true;
+
+ this.ignore = options.ignore;
+
+ //Could have option to init this module in enabled mode,
+ //or could have been previously marked as enabled. However,
+ //the dependencies are not known until init is called. So
+ //if enabled previously, now trigger dependencies as enabled.
+ if (options.enabled || this.enabled) {
+ //Enable this module and dependencies.
+ //Will call this.check()
+ this.enable();
+ } else {
+ this.check();
+ }
+ },
+
+ defineDepById: function (id, depExports) {
+ var i;
+
+ //Find the index for this dependency.
+ each(this.depMaps, function (map, index) {
+ if (map.id === id) {
+ i = index;
+ return true;
+ }
+ });
+
+ return this.defineDep(i, depExports);
+ },
+
+ defineDep: function (i, depExports) {
+ //Because of cycles, defined callback for a given
+ //export can be called more than once.
+ if (!this.depMatched[i]) {
+ this.depMatched[i] = true;
+ this.depCount -= 1;
+ this.depExports[i] = depExports;
+ }
+ },
+
+ fetch: function () {
+ if (this.fetched) {
+ return;
+ }
+ this.fetched = true;
+
+ context.startTime = (new Date()).getTime();
+
+ var map = this.map;
+
+ //If the manager is for a plugin managed resource,
+ //ask the plugin to load it now.
+ if (this.shim) {
+ makeRequire(this, true)(this.shim.deps || [], bind(this, function () {
+ return map.prefix ? this.callPlugin() : this.load();
+ }));
+ } else {
+ //Regular dependency.
+ return map.prefix ? this.callPlugin() : this.load();
+ }
+ },
+
+ load: function () {
+ var url = this.map.url;
+
+ //Regular dependency.
+ if (!urlFetched[url]) {
+ urlFetched[url] = true;
+ context.load(this.map.id, url);
+ }
+ },
+
+ /**
+ * Checks is the module is ready to define itself, and if so,
+ * define it. If the silent argument is true, then it will just
+ * define, but not notify listeners, and not ask for a context-wide
+ * check of all loaded modules. That is useful for cycle breaking.
+ */
+ check: function (silent) {
+ if (!this.enabled || this.enabling) {
+ return;
+ }
+
+ var err, cjsModule,
+ id = this.map.id,
+ depExports = this.depExports,
+ exports = this.exports,
+ factory = this.factory;
+
+ if (!this.inited) {
+ this.fetch();
+ } else if (this.error) {
+ this.emit('error', this.error);
+ } else if (!this.defining) {
+ //The factory could trigger another require call
+ //that would result in checking this module to
+ //define itself again. If already in the process
+ //of doing that, skip this work.
+ this.defining = true;
+
+ if (this.depCount < 1 && !this.defined) {
+ if (isFunction(factory)) {
+ //If there is an error listener, favor passing
+ //to that instead of throwing an error.
+ if (this.events.error) {
+ try {
+ exports = context.execCb(id, factory, depExports, exports);
+ } catch (e) {
+ err = e;
+ }
+ } else {
+ exports = context.execCb(id, factory, depExports, exports);
+ }
+
+ if (this.map.isDefine) {
+ //If setting exports via 'module' is in play,
+ //favor that over return value and exports. After that,
+ //favor a non-undefined return value over exports use.
+ cjsModule = this.module;
+ if (cjsModule &&
+ cjsModule.exports !== undefined &&
+ //Make sure it is not already the exports value
+ cjsModule.exports !== this.exports) {
+ exports = cjsModule.exports;
+ } else if (exports === undefined && this.usingExports) {
+ //exports already set the defined value.
+ exports = this.exports;
+ }
+ }
+
+ if (err) {
+ err.requireMap = this.map;
+ err.requireModules = [this.map.id];
+ err.requireType = 'define';
+ return onError((this.error = err));
+ }
+
+ } else {
+ //Just a literal value
+ exports = factory;
+ }
+
+ this.exports = exports;
+
+ if (this.map.isDefine && !this.ignore) {
+ defined[id] = exports;
+
+ if (req.onResourceLoad) {
+ req.onResourceLoad(context, this.map, this.depMaps);
+ }
+ }
+
+ //Clean up
+ delete registry[id];
+
+ this.defined = true;
+ context.waitCount -= 1;
+ if (context.waitCount === 0) {
+ //Clear the wait array used for cycles.
+ waitAry = [];
+ }
+ }
+
+ //Finished the define stage. Allow calling check again
+ //to allow define notifications below in the case of a
+ //cycle.
+ this.defining = false;
+
+ if (!silent) {
+ if (this.defined && !this.defineEmitted) {
+ this.defineEmitted = true;
+ this.emit('defined', this.exports);
+ this.defineEmitComplete = true;
+ }
+ }
+ }
+ },
+
+ callPlugin: function () {
+ var map = this.map,
+ id = map.id,
+ pluginMap = makeModuleMap(map.prefix, null, false, true);
+
+ on(pluginMap, 'defined', bind(this, function (plugin) {
+ var load, normalizedMap, normalizedMod,
+ name = this.map.name,
+ parentName = this.map.parentMap ? this.map.parentMap.name : null;
+
+ //If current map is not normalized, wait for that
+ //normalized name to load instead of continuing.
+ if (this.map.unnormalized) {
+ //Normalize the ID if the plugin allows it.
+ if (plugin.normalize) {
+ name = plugin.normalize(name, function (name) {
+ return normalize(name, parentName, true);
+ }) || '';
+ }
+
+ normalizedMap = makeModuleMap(map.prefix + '!' + name,
+ this.map.parentMap,
+ false,
+ true);
+ on(normalizedMap,
+ 'defined', bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true,
+ ignore: true
+ });
+ }));
+ normalizedMod = registry[normalizedMap.id];
+ if (normalizedMod) {
+ if (this.events.error) {
+ normalizedMod.on('error', bind(this, function (err) {
+ this.emit('error', err);
+ }));
+ }
+ normalizedMod.enable();
+ }
+
+ return;
+ }
+
+ load = bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true
+ });
+ });
+
+ load.error = bind(this, function (err) {
+ this.inited = true;
+ this.error = err;
+ err.requireModules = [id];
+
+ //Remove temp unnormalized modules for this module,
+ //since they will never be resolved otherwise now.
+ eachProp(registry, function (mod) {
+ if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
+ removeWaiting(mod.map.id);
+ }
+ });
+
+ onError(err);
+ });
+
+ //Allow plugins to load other code without having to know the
+ //context or how to 'complete' the load.
+ load.fromText = function (moduleName, text) {
+ /*jslint evil: true */
+ var hasInteractive = useInteractive;
+
+ //Turn off interactive script matching for IE for any define
+ //calls in the text, then turn it back on at the end.
+ if (hasInteractive) {
+ useInteractive = false;
+ }
+
+ //Prime the system by creating a module instance for
+ //it.
+ getModule(makeModuleMap(moduleName));
+
+ req.exec(text);
+
+ if (hasInteractive) {
+ useInteractive = true;
+ }
+
+ //Support anonymous modules.
+ context.completeLoad(moduleName);
+ };
+
+ //Use parentName here since the plugin's name is not reliable,
+ //could be some weird string with no path that actually wants to
+ //reference the parentName's path.
+ plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb, er) {
+ deps.rjsSkipMap = true;
+ return context.require(deps, cb, er);
+ }), load, config);
+ }));
+
+ context.enable(pluginMap, this);
+ this.pluginMaps[pluginMap.id] = pluginMap;
+ },
+
+ enable: function () {
+ this.enabled = true;
+
+ if (!this.waitPushed) {
+ waitAry.push(this);
+ context.waitCount += 1;
+ this.waitPushed = true;
+ }
+
+ //Set flag mentioning that the module is enabling,
+ //so that immediate calls to the defined callbacks
+ //for dependencies do not trigger inadvertent load
+ //with the depCount still being zero.
+ this.enabling = true;
+
+ //Enable each dependency
+ each(this.depMaps, bind(this, function (depMap, i) {
+ var id, mod, handler;
+
+ if (typeof depMap === 'string') {
+ //Dependency needs to be converted to a depMap
+ //and wired up to this module.
+ depMap = makeModuleMap(depMap,
+ (this.map.isDefine ? this.map : this.map.parentMap),
+ false,
+ !this.depMaps.rjsSkipMap);
+ this.depMaps[i] = depMap;
+
+ handler = handlers[depMap.id];
+
+ if (handler) {
+ this.depExports[i] = handler(this);
+ return;
+ }
+
+ this.depCount += 1;
+
+ on(depMap, 'defined', bind(this, function (depExports) {
+ this.defineDep(i, depExports);
+ this.check();
+ }));
+
+ if (this.errback) {
+ on(depMap, 'error', this.errback);
+ }
+ }
+
+ id = depMap.id;
+ mod = registry[id];
+
+ //Skip special modules like 'require', 'exports', 'module'
+ //Also, don't call enable if it is already enabled,
+ //important in circular dependency cases.
+ if (!handlers[id] && mod && !mod.enabled) {
+ context.enable(depMap, this);
+ }
+ }));
+
+ //Enable each plugin that is used in
+ //a dependency
+ eachProp(this.pluginMaps, bind(this, function (pluginMap) {
+ var mod = registry[pluginMap.id];
+ if (mod && !mod.enabled) {
+ context.enable(pluginMap, this);
+ }
+ }));
+
+ this.enabling = false;
+
+ this.check();
+ },
+
+ on: function (name, cb) {
+ var cbs = this.events[name];
+ if (!cbs) {
+ cbs = this.events[name] = [];
+ }
+ cbs.push(cb);
+ },
+
+ emit: function (name, evt) {
+ each(this.events[name], function (cb) {
+ cb(evt);
+ });
+ if (name === 'error') {
+ //Now that the error handler was triggered, remove
+ //the listeners, since this broken Module instance
+ //can stay around for a while in the registry/waitAry.
+ delete this.events[name];
+ }
+ }
+ };
+
+ function callGetModule(args) {
+ getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
+ }
+
+ function removeListener(node, func, name, ieName) {
+ //Favor detachEvent because of IE9
+ //issue, see attachEvent/addEventListener comment elsewhere
+ //in this file.
+ if (node.detachEvent && !isOpera) {
+ //Probably IE. If not it will throw an error, which will be
+ //useful to know.
+ if (ieName) {
+ node.detachEvent(ieName, func);
+ }
+ } else {
+ node.removeEventListener(name, func, false);
+ }
+ }
+
+ /**
+ * Given an event from a script node, get the requirejs info from it,
+ * and then removes the event listeners on the node.
+ * @param {Event} evt
+ * @returns {Object}
+ */
+ function getScriptData(evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ var node = evt.currentTarget || evt.srcElement;
+
+ //Remove the listeners once here.
+ removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
+ removeListener(node, context.onScriptError, 'error');
+
+ return {
+ node: node,
+ id: node && node.getAttribute('data-requiremodule')
+ };
+ }
+
+ return (context = {
+ config: config,
+ contextName: contextName,
+ registry: registry,
+ defined: defined,
+ urlFetched: urlFetched,
+ waitCount: 0,
+ defQueue: defQueue,
+ Module: Module,
+ makeModuleMap: makeModuleMap,
+
+ /**
+ * Set a configuration for the context.
+ * @param {Object} cfg config object to integrate.
+ */
+ configure: function (cfg) {
+ //Make sure the baseUrl ends in a slash.
+ if (cfg.baseUrl) {
+ if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+ cfg.baseUrl += '/';
+ }
+ }
+
+ //Save off the paths and packages since they require special processing,
+ //they are additive.
+ var pkgs = config.pkgs,
+ shim = config.shim,
+ paths = config.paths,
+ map = config.map;
+
+ //Mix in the config values, favoring the new values over
+ //existing ones in context.config.
+ mixin(config, cfg, true);
+
+ //Merge paths.
+ config.paths = mixin(paths, cfg.paths, true);
+
+ //Merge map
+ if (cfg.map) {
+ config.map = mixin(map || {}, cfg.map, true, true);
+ }
+
+ //Merge shim
+ if (cfg.shim) {
+ eachProp(cfg.shim, function (value, id) {
+ //Normalize the structure
+ if (isArray(value)) {
+ value = {
+ deps: value
+ };
+ }
+ if (value.exports && !value.exports.__buildReady) {
+ value.exports = context.makeShimExports(value.exports);
+ }
+ shim[id] = value;
+ });
+ config.shim = shim;
+ }
+
+ //Adjust packages if necessary.
+ if (cfg.packages) {
+ each(cfg.packages, function (pkgObj) {
+ var location;
+
+ pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
+ location = pkgObj.location;
+
+ //Create a brand new object on pkgs, since currentPackages can
+ //be passed in again, and config.pkgs is the internal transformed
+ //state for all package configs.
+ pkgs[pkgObj.name] = {
+ name: pkgObj.name,
+ location: location || pkgObj.name,
+ //Remove leading dot in main, so main paths are normalized,
+ //and remove any trailing .js, since different package
+ //envs have different conventions: some use a module name,
+ //some use a file name.
+ main: (pkgObj.main || 'main')
+ .replace(currDirRegExp, '')
+ .replace(jsSuffixRegExp, '')
+ };
+ });
+
+ //Done with modifications, assing packages back to context config
+ config.pkgs = pkgs;
+ }
+
+ //If there are any "waiting to execute" modules in the registry,
+ //update the maps for them, since their info, like URLs to load,
+ //may have changed.
+ eachProp(registry, function (mod, id) {
+ //If module already has init called, since it is too
+ //late to modify them, and ignore unnormalized ones
+ //since they are transient.
+ if (!mod.inited && !mod.map.unnormalized) {
+ mod.map = makeModuleMap(id);
+ }
+ });
+
+ //If a deps array or a config callback is specified, then call
+ //require with those args. This is useful when require is defined as a
+ //config object before require.js is loaded.
+ if (cfg.deps || cfg.callback) {
+ context.require(cfg.deps || [], cfg.callback);
+ }
+ },
+
+ makeShimExports: function (exports) {
+ var func;
+ if (typeof exports === 'string') {
+ func = function () {
+ return getGlobal(exports);
+ };
+ //Save the exports for use in nodefine checking.
+ func.exports = exports;
+ return func;
+ } else {
+ return function () {
+ return exports.apply(global, arguments);
+ };
+ }
+ },
+
+ requireDefined: function (id, relMap) {
+ return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
+ },
+
+ requireSpecified: function (id, relMap) {
+ id = makeModuleMap(id, relMap, false, true).id;
+ return hasProp(defined, id) || hasProp(registry, id);
+ },
+
+ require: function (deps, callback, errback, relMap) {
+ var moduleName, id, map, requireMod, args;
+ if (typeof deps === 'string') {
+ if (isFunction(callback)) {
+ //Invalid call
+ return onError(makeError('requireargs', 'Invalid require call'), errback);
+ }
+
+ //Synchronous access to one module. If require.get is
+ //available (as in the Node adapter), prefer that.
+ //In this case deps is the moduleName and callback is
+ //the relMap
+ if (req.get) {
+ return req.get(context, deps, callback);
+ }
+
+ //Just return the module wanted. In this scenario, the
+ //second arg (if passed) is just the relMap.
+ moduleName = deps;
+ relMap = callback;
+
+ //Normalize module name, if it contains . or ..
+ map = makeModuleMap(moduleName, relMap, false, true);
+ id = map.id;
+
+ if (!hasProp(defined, id)) {
+ return onError(makeError('notloaded', 'Module name "' +
+ id +
+ '" has not been loaded yet for context: ' +
+ contextName));
+ }
+ return defined[id];
+ }
+
+ //Callback require. Normalize args. if callback or errback is
+ //not a function, it means it is a relMap. Test errback first.
+ if (errback && !isFunction(errback)) {
+ relMap = errback;
+ errback = undefined;
+ }
+ if (callback && !isFunction(callback)) {
+ relMap = callback;
+ callback = undefined;
+ }
+
+ //Any defined modules in the global queue, intake them now.
+ takeGlobalQueue();
+
+ //Make sure any remaining defQueue items get properly processed.
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
+ } else {
+ //args are id, deps, factory. Should be normalized by the
+ //define() function.
+ callGetModule(args);
+ }
+ }
+
+ //Mark all the dependencies as needing to be loaded.
+ requireMod = getModule(makeModuleMap(null, relMap));
+
+ requireMod.init(deps, callback, errback, {
+ enabled: true
+ });
+
+ checkLoaded();
+
+ return context.require;
+ },
+
+ undef: function (id) {
+ //Bind any waiting define() calls to this context,
+ //fix for #408
+ takeGlobalQueue();
+
+ var map = makeModuleMap(id, null, true),
+ mod = registry[id];
+
+ delete defined[id];
+ delete urlFetched[map.url];
+ delete undefEvents[id];
+
+ if (mod) {
+ //Hold on to listeners in case the
+ //module will be attempted to be reloaded
+ //using a different config.
+ if (mod.events.defined) {
+ undefEvents[id] = mod.events;
+ }
+
+ removeWaiting(id);
+ }
+ },
+
+ /**
+ * Called to enable a module if it is still in the registry
+ * awaiting enablement. parent module is passed in for context,
+ * used by the optimizer.
+ */
+ enable: function (depMap, parent) {
+ var mod = registry[depMap.id];
+ if (mod) {
+ getModule(depMap).enable();
+ }
+ },
+
+ /**
+ * Internal method used by environment adapters to complete a load event.
+ * A load event could be a script load or just a load pass from a synchronous
+ * load call.
+ * @param {String} moduleName the name of the module to potentially complete.
+ */
+ completeLoad: function (moduleName) {
+ var found, args, mod,
+ shim = config.shim[moduleName] || {},
+ shExports = shim.exports && shim.exports.exports;
+
+ takeGlobalQueue();
+
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ args[0] = moduleName;
+ //If already found an anonymous module and bound it
+ //to this name, then this is some other anon module
+ //waiting for its completeLoad to fire.
+ if (found) {
+ break;
+ }
+ found = true;
+ } else if (args[0] === moduleName) {
+ //Found matching define call for this script!
+ found = true;
+ }
+
+ callGetModule(args);
+ }
+
+ //Do this after the cycle of callGetModule in case the result
+ //of those calls/init calls changes the registry.
+ mod = registry[moduleName];
+
+ if (!found && !defined[moduleName] && mod && !mod.inited) {
+ if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
+ if (hasPathFallback(moduleName)) {
+ return;
+ } else {
+ return onError(makeError('nodefine',
+ 'No define call for ' + moduleName,
+ null,
+ [moduleName]));
+ }
+ } else {
+ //A script that does not call define(), so just simulate
+ //the call for it.
+ callGetModule([moduleName, (shim.deps || []), shim.exports]);
+ }
+ }
+
+ checkLoaded();
+ },
+
+ /**
+ * Converts a module name + .extension into an URL path.
+ * *Requires* the use of a module name. It does not support using
+ * plain URLs like nameToUrl.
+ */
+ toUrl: function (moduleNamePlusExt, relModuleMap) {
+ var index = moduleNamePlusExt.lastIndexOf('.'),
+ ext = null;
+
+ if (index !== -1) {
+ ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+ moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
+ }
+
+ return context.nameToUrl(normalize(moduleNamePlusExt, relModuleMap && relModuleMap.id, true),
+ ext);
+ },
+
+ /**
+ * Converts a module name to a file path. Supports cases where
+ * moduleName may actually be just an URL.
+ * Note that it **does not** call normalize on the moduleName,
+ * it is assumed to have already been normalized. This is an
+ * internal API, not a public one. Use toUrl for the public API.
+ */
+ nameToUrl: function (moduleName, ext) {
+ var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url,
+ parentPath;
+
+ //If a colon is in the URL, it indicates a protocol is used and it is just
+ //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
+ //or ends with .js, then assume the user meant to use an url and not a module id.
+ //The slash is important for protocol-less URLs as well as full paths.
+ if (req.jsExtRegExp.test(moduleName)) {
+ //Just a plain path, not module name lookup, so just return it.
+ //Add extension if it is included. This is a bit wonky, only non-.js things pass
+ //an extension, this method probably needs to be reworked.
+ url = moduleName + (ext || '');
+ } else {
+ //A module that needs to be converted to a path.
+ paths = config.paths;
+ pkgs = config.pkgs;
+
+ syms = moduleName.split('/');
+ //For each module name segment, see if there is a path
+ //registered for it. Start with most specific name
+ //and work up from it.
+ for (i = syms.length; i > 0; i -= 1) {
+ parentModule = syms.slice(0, i).join('/');
+ pkg = pkgs[parentModule];
+ parentPath = paths[parentModule];
+ if (parentPath) {
+ //If an array, it means there are a few choices,
+ //Choose the one that is desired
+ if (isArray(parentPath)) {
+ parentPath = parentPath[0];
+ }
+ syms.splice(0, i, parentPath);
+ break;
+ } else if (pkg) {
+ //If module name is just the package name, then looking
+ //for the main module.
+ if (moduleName === pkg.name) {
+ pkgPath = pkg.location + '/' + pkg.main;
+ } else {
+ pkgPath = pkg.location;
+ }
+ syms.splice(0, i, pkgPath);
+ break;
+ }
+ }
+
+ //Join the path parts together, then figure out if baseUrl is needed.
+ url = syms.join('/');
+ url += (ext || (/\?/.test(url) ? '' : '.js'));
+ url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+ }
+
+ return config.urlArgs ? url +
+ ((url.indexOf('?') === -1 ? '?' : '&') +
+ config.urlArgs) : url;
+ },
+
+ //Delegates to req.load. Broken out as a separate function to
+ //allow overriding in the optimizer.
+ load: function (id, url) {
+ req.load(context, id, url);
+ },
+
+ /**
+ * Executes a module callack function. Broken out as a separate function
+ * solely to allow the build system to sequence the files in the built
+ * layer in the right sequence.
+ *
+ * @private
+ */
+ execCb: function (name, callback, args, exports) {
+ return callback.apply(exports, args);
+ },
+
+ /**
+ * callback for script loads, used to check status of loading.
+ *
+ * @param {Event} evt the event from the browser for the script
+ * that was loaded.
+ */
+ onScriptLoad: function (evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ if (evt.type === 'load' ||
+ (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
+ //Reset interactive script so a script node is not held onto for
+ //to long.
+ interactiveScript = null;
+
+ //Pull out the name of the module and the context.
+ var data = getScriptData(evt);
+ context.completeLoad(data.id);
+ }
+ },
+
+ /**
+ * Callback for script errors.
+ */
+ onScriptError: function (evt) {
+ var data = getScriptData(evt);
+ if (!hasPathFallback(data.id)) {
+ return onError(makeError('scripterror', 'Script error', evt, [data.id]));
+ }
+ }
+ });
+ }
+
+ /**
+ * Main entry point.
+ *
+ * If the only argument to require is a string, then the module that
+ * is represented by that string is fetched for the appropriate context.
+ *
+ * If the first argument is an array, then it will be treated as an array
+ * of dependency string names to fetch. An optional function callback can
+ * be specified to execute when all of those dependencies are available.
+ *
+ * Make a local req variable to help Caja compliance (it assumes things
+ * on a require that are not standardized), and to give a short
+ * name for minification/local scope use.
+ */
+ req = requirejs = function (deps, callback, errback, optional) {
+
+ //Find the right context, use default
+ var context, config,
+ contextName = defContextName;
+
+ // Determine if have config object in the call.
+ if (!isArray(deps) && typeof deps !== 'string') {
+ // deps is a config object
+ config = deps;
+ if (isArray(callback)) {
+ // Adjust args if there are dependencies
+ deps = callback;
+ callback = errback;
+ errback = optional;
+ } else {
+ deps = [];
+ }
+ }
+
+ if (config && config.context) {
+ contextName = config.context;
+ }
+
+ context = contexts[contextName];
+ if (!context) {
+ context = contexts[contextName] = req.s.newContext(contextName);
+ }
+
+ if (config) {
+ context.configure(config);
+ }
+
+ return context.require(deps, callback, errback);
+ };
+
+ /**
+ * Support require.config() to make it easier to cooperate with other
+ * AMD loaders on globally agreed names.
+ */
+ req.config = function (config) {
+ return req(config);
+ };
+
+ /**
+ * Export require as a global, but only if it does not already exist.
+ */
+ if (!require) {
+ require = req;
+ }
+
+ req.version = version;
+
+ //Used to filter out dependencies that are already paths.
+ req.jsExtRegExp = /^\/|:|\?|\.js$/;
+ req.isBrowser = isBrowser;
+ s = req.s = {
+ contexts: contexts,
+ newContext: newContext
+ };
+
+ //Create default context.
+ req({});
+
+ //Exports some context-sensitive methods on global require, using
+ //default context if no context specified.
+ addRequireMethods(req);
+
+ if (isBrowser) {
+ head = s.head = document.getElementsByTagName('head')[0];
+ //If BASE tag is in play, using appendChild is a problem for IE6.
+ //When that browser dies, this can be removed. Details in this jQuery bug:
+ //http://dev.jquery.com/ticket/2709
+ baseElement = document.getElementsByTagName('base')[0];
+ if (baseElement) {
+ head = s.head = baseElement.parentNode;
+ }
+ }
+
+ /**
+ * Any errors that require explicitly generates will be passed to this
+ * function. Intercept/override it if you want custom error handling.
+ * @param {Error} err the error object.
+ */
+ req.onError = function (err) {
+ throw err;
+ };
+
+ /**
+ * Does the request to load a module for the browser case.
+ * Make this a separate function to allow other environments
+ * to override it.
+ *
+ * @param {Object} context the require context to find state.
+ * @param {String} moduleName the name of the module.
+ * @param {Object} url the URL to the module.
+ */
+ req.load = function (context, moduleName, url) {
+ var config = (context && context.config) || {},
+ node;
+ if (isBrowser) {
+ //In the browser so use a script tag
+ node = config.xhtml ?
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
+ document.createElement('script');
+ node.type = config.scriptType || 'text/javascript';
+ node.charset = 'utf-8';
+ node.async = true;
+
+ node.setAttribute('data-requirecontext', context.contextName);
+ node.setAttribute('data-requiremodule', moduleName);
+
+ //Set up load listener. Test attachEvent first because IE9 has
+ //a subtle issue in its addEventListener and script onload firings
+ //that do not match the behavior of all other browsers with
+ //addEventListener support, which fire the onload event for a
+ //script right after the script execution. See:
+ //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
+ //UNFORTUNATELY Opera implements attachEvent but does not follow the script
+ //script execution mode.
+ if (node.attachEvent &&
+ //Check if node.attachEvent is artificially added by custom script or
+ //natively supported by browser
+ //read https://github.com/jrburke/requirejs/issues/187
+ //if we can NOT find [native code] then it must NOT natively supported.
+ //in IE8, node.attachEvent does not have toString()
+ //Note the test for "[native code" with no closing brace, see:
+ //https://github.com/jrburke/requirejs/issues/273
+ !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
+ !isOpera) {
+ //Probably IE. IE (at least 6-8) do not fire
+ //script onload right after executing the script, so
+ //we cannot tie the anonymous define call to a name.
+ //However, IE reports the script as being in 'interactive'
+ //readyState at the time of the define call.
+ useInteractive = true;
+
+ node.attachEvent('onreadystatechange', context.onScriptLoad);
+ //It would be great to add an error handler here to catch
+ //404s in IE9+. However, onreadystatechange will fire before
+ //the error handler, so that does not help. If addEvenListener
+ //is used, then IE will fire error before load, but we cannot
+ //use that pathway given the connect.microsoft.com issue
+ //mentioned above about not doing the 'script execute,
+ //then fire the script load event listener before execute
+ //next script' that other browsers do.
+ //Best hope: IE10 fixes the issues,
+ //and then destroys all installs of IE 6-9.
+ //node.attachEvent('onerror', context.onScriptError);
+ } else {
+ node.addEventListener('load', context.onScriptLoad, false);
+ node.addEventListener('error', context.onScriptError, false);
+ }
+ node.src = url;
+
+ //For some cache cases in IE 6-8, the script executes before the end
+ //of the appendChild execution, so to tie an anonymous define
+ //call to the module name (which is stored on the node), hold on
+ //to a reference to this node, but clear after the DOM insertion.
+ currentlyAddingScript = node;
+ if (baseElement) {
+ head.insertBefore(node, baseElement);
+ } else {
+ head.appendChild(node);
+ }
+ currentlyAddingScript = null;
+
+ return node;
+ } else if (isWebWorker) {
+ //In a web worker, use importScripts. This is not a very
+ //efficient use of importScripts, importScripts will block until
+ //its script is downloaded and evaluated. However, if web workers
+ //are in play, the expectation that a build has been done so that
+ //only one script needs to be loaded anyway. This may need to be
+ //reevaluated if other use cases become common.
+ importScripts(url);
+
+ //Account for anonymous modules
+ context.completeLoad(moduleName);
+ }
+ };
+
+ function getInteractiveScript() {
+ if (interactiveScript && interactiveScript.readyState === 'interactive') {
+ return interactiveScript;
+ }
+
+ eachReverse(scripts(), function (script) {
+ if (script.readyState === 'interactive') {
+ return (interactiveScript = script);
+ }
+ });
+ return interactiveScript;
+ }
+
+ //Look for a data-main script attribute, which could also adjust the baseUrl.
+ if (isBrowser) {
+ //Figure out baseUrl. Get it from the script tag with require.js in it.
+ eachReverse(scripts(), function (script) {
+ //Set the 'head' where we can append children by
+ //using the script's parent.
+ if (!head) {
+ head = script.parentNode;
+ }
+
+ //Look for a data-main attribute to set main script for the page
+ //to load. If it is there, the path to data main becomes the
+ //baseUrl, if it is not already set.
+ dataMain = script.getAttribute('data-main');
+ if (dataMain) {
+ //Set final baseUrl if there is not already an explicit one.
+ if (!cfg.baseUrl) {
+ //Pull off the directory of data-main for use as the
+ //baseUrl.
+ src = dataMain.split('/');
+ mainScript = src.pop();
+ subPath = src.length ? src.join('/') + '/' : './';
+
+ cfg.baseUrl = subPath;
+ dataMain = mainScript;
+ }
+
+ //Strip off any trailing .js since dataMain is now
+ //like a module name.
+ dataMain = dataMain.replace(jsSuffixRegExp, '');
+
+ //Put the data-main script in the files to load.
+ cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain];
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * The function that handles definitions of modules. Differs from
+ * require() in that a string for the module should be the first argument,
+ * and the function to execute after dependencies are loaded should
+ * return a value to define the module corresponding to the first argument's
+ * name.
+ */
+ define = function (name, deps, callback) {
+ var node, context;
+
+ //Allow for anonymous functions
+ if (typeof name !== 'string') {
+ //Adjust args appropriately
+ callback = deps;
+ deps = name;
+ name = null;
+ }
+
+ //This module may not have dependencies
+ if (!isArray(deps)) {
+ callback = deps;
+ deps = [];
+ }
+
+ //If no name, and callback is a function, then figure out if it a
+ //CommonJS thing with dependencies.
+ if (!deps.length && isFunction(callback)) {
+ //Remove comments from the callback string,
+ //look for require calls, and pull them into the dependencies,
+ //but only if there are function args.
+ if (callback.length) {
+ callback
+ .toString()
+ .replace(commentRegExp, '')
+ .replace(cjsRequireRegExp, function (match, dep) {
+ deps.push(dep);
+ });
+
+ //May be a CommonJS thing even without require calls, but still
+ //could use exports, and module. Avoid doing exports and module
+ //work though if it just needs require.
+ //REQUIRES the function to expect the CommonJS variables in the
+ //order listed below.
+ deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
+ }
+ }
+
+ //If in IE 6-8 and hit an anonymous define() call, do the interactive
+ //work.
+ if (useInteractive) {
+ node = currentlyAddingScript || getInteractiveScript();
+ if (node) {
+ if (!name) {
+ name = node.getAttribute('data-requiremodule');
+ }
+ context = contexts[node.getAttribute('data-requirecontext')];
+ }
+ }
+
+ //Always save off evaluating the def call until the script onload handler.
+ //This allows multiple modules to be in a file without prematurely
+ //tracing dependencies, and allows for anonymous module support,
+ //where the module name is not known until the script onload event
+ //occurs. If no context, use the global queue, and get it processed
+ //in the onscript load callback.
+ (context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+
+
+ /**
+ * Executes the text. Normally just uses eval, but can be modified
+ * to use a better, environment-specific call. Only used for transpiling
+ * loader plugins, not for plain JS modules.
+ * @param {String} text the text to execute/evaluate.
+ */
+ req.exec = function (text) {
+ /*jslint evil: true */
+ return eval(text);
+ };
+
+ //Set up with config info.
+ req(cfg);
+}(this));
diff --git a/module/web/static/js/mobile.js b/module/web/static/js/mobile.js
new file mode 100644
index 000000000..58ccf5800
--- /dev/null
+++ b/module/web/static/js/mobile.js
@@ -0,0 +1,42 @@
+// Sets the require.js configuration for your application.
+require.config({
+
+ paths:{
+
+ jquery:"libs/jquery-1.8.0",
+ jqueryui:"libs/jqueryui",
+ flot:"libs/jquery.flot.min",
+ transit:"libs/jquery.transit-0.1.3",
+ fastClick:"libs/jquery.fastClick-0.2",
+ omniwindow: "libs/jquery.omniwindow",
+
+ underscore:"libs/lodash-0.5.2",
+ backbone:"libs/backbone-0.9.2",
+
+ // Require.js Plugins
+ text:"plugins/text-2.0.3"
+
+ },
+
+ // Sets the configuration for your third party scripts that are not AMD compatible
+ shim: {
+
+ "backbone": {
+ deps: ["underscore", "jquery"],
+ exports: "Backbone" //attaches "Backbone" to the window object
+ },
+ transit: ["jquery"],
+ fastClick: ["jquery"]
+
+ } // end Shim Configuration
+
+});
+
+define('mobile', ['routers/mobileRouter', 'transit', 'fastClick'], function(Mobile) {
+
+ var init = function(){
+ var router = new Mobile();
+ };
+
+ return {"init":init};
+}); \ No newline at end of file
diff --git a/module/web/static/js/models/File.js b/module/web/static/js/models/File.js
new file mode 100644
index 000000000..71aa2b84f
--- /dev/null
+++ b/module/web/static/js/models/File.js
@@ -0,0 +1,33 @@
+define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) {
+
+ return Backbone.Model.extend({
+
+ idAttribute: 'fid',
+
+ defaults: {
+ fid: -1,
+ name: null,
+ package: -1,
+ owner: -1,
+ size: -1,
+ status: -1,
+ media: -1,
+ added: -1,
+ fileorder: -1,
+ download: null
+ },
+
+
+ // Model Constructor
+ initialize: function() {
+
+ },
+
+ // Any time a model attribute is set, this method is called
+ validate: function(attrs) {
+
+ }
+
+ });
+
+}); \ No newline at end of file
diff --git a/module/web/static/js/models/Package.js b/module/web/static/js/models/Package.js
new file mode 100644
index 000000000..5a2940c66
--- /dev/null
+++ b/module/web/static/js/models/Package.js
@@ -0,0 +1,76 @@
+define(['jquery', 'backbone', 'underscore', 'collections/FileList', 'require'],
+ function($, Backbone, _, FileList, require) {
+
+ return Backbone.Model.extend({
+
+ idAttribute: 'pid',
+
+ defaults: {
+ pid: -1,
+ name: null,
+ folder: "",
+ root: -1,
+ owner: -1,
+ site: "",
+ comment: "",
+ password: "",
+ added: -1,
+ status: -1,
+ packageorder: -1,
+ stats: null,
+ fids: null,
+ pids: null,
+ files: null, // Collection
+ packs: null // Collection
+ },
+
+ // Model Constructor
+ initialize: function() {
+ },
+
+ // Changes url + method and delegates call to super class
+ fetch: function(options) {
+ options || (options = {});
+ options.url = 'api/getFileTree/' + this.get('pid') + '/false';
+ options.type = "post";
+
+ return Backbone.Model.prototype.fetch.call(this, options);
+ },
+
+ save: function(options) {
+ // TODO
+ },
+
+ destroy: function(options) {
+ options || (options = {});
+ // TODO: as post data
+ options.url = 'api/deletePackages/[' + this.get('pid') + ']';
+ options.type = "post";
+
+ return Backbone.Model.prototype.destroy.call(this, options);
+ },
+
+ parse: function(resp, xhr) {
+ // Package is loaded from tree collection
+ if (_.has(resp, 'root')) {
+ resp.root.files = new FileList(_.values(resp.files));
+ // circular dependencies needs to be avoided
+ var PackageList = require('collections/PackageList');
+ resp.root.packs = new PackageList(_.values(resp.packages));
+ return resp.root;
+ }
+ return Backbone.model.prototype.fetch.call(this, resp, xhr);
+ },
+
+ // Package data is complete when it contains collection for containing files or packs
+ isLoaded: function() {
+ return this.has('files');
+ },
+
+ // Any time a model attribute is set, this method is called
+ validate: function(attrs) {
+
+ }
+
+ });
+ }); \ No newline at end of file
diff --git a/module/web/static/js/models/TreeCollection.js b/module/web/static/js/models/TreeCollection.js
new file mode 100644
index 000000000..6476ea7b5
--- /dev/null
+++ b/module/web/static/js/models/TreeCollection.js
@@ -0,0 +1,38 @@
+define(['jquery', 'backbone', 'underscore', 'models/Package', 'collections/FileList', 'collections/PackageList'],
+ function($, Backbone, _, Package, FileList, PackageList) {
+
+ // TreeCollection
+ // A Model and not a collection, aggregates other collections
+ return Backbone.Model.extend({
+
+ defaults : {
+ root: null,
+ packages: null,
+ files: null
+ },
+
+ initialize: function() {
+
+ },
+
+ fetch: function(options) {
+ options || (options = {});
+ var pid = options.pid || -1;
+
+ // TODO: more options possible
+ options.url = 'api/getFileTree/' + pid + '/false';
+ options.type = "post";
+
+ return Backbone.Model.prototype.fetch.call(this, options);
+ },
+
+ parse: function(resp, xhr) {
+ return {
+ root: new Package(resp.root),
+ packages: new PackageList(_.values(resp.packages)),
+ files: new FileList(_.values(resp.files))
+ };
+ }
+
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/plugins/text-2.0.3.js b/module/web/static/js/plugins/text-2.0.3.js
new file mode 100644
index 000000000..bf61a3fe4
--- /dev/null
+++ b/module/web/static/js/plugins/text-2.0.3.js
@@ -0,0 +1,308 @@
+/**
+ * @license RequireJS text 2.0.3 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/requirejs/text for details
+ */
+/*jslint regexp: true */
+/*global require: false, XMLHttpRequest: false, ActiveXObject: false,
+ define: false, window: false, process: false, Packages: false,
+ java: false, location: false */
+
+define(['module'], function (module) {
+ 'use strict';
+
+ var text, fs,
+ progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
+ xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
+ bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
+ hasLocation = typeof location !== 'undefined' && location.href,
+ defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
+ defaultHostName = hasLocation && location.hostname,
+ defaultPort = hasLocation && (location.port || undefined),
+ buildMap = [],
+ masterConfig = (module.config && module.config()) || {};
+
+ text = {
+ version: '2.0.3',
+
+ strip: function (content) {
+ //Strips <?xml ...?> declarations so that external SVG and XML
+ //documents can be added to a document without worry. Also, if the string
+ //is an HTML document, only the part inside the body tag is returned.
+ if (content) {
+ content = content.replace(xmlRegExp, "");
+ var matches = content.match(bodyRegExp);
+ if (matches) {
+ content = matches[1];
+ }
+ } else {
+ content = "";
+ }
+ return content;
+ },
+
+ jsEscape: function (content) {
+ return content.replace(/(['\\])/g, '\\$1')
+ .replace(/[\f]/g, "\\f")
+ .replace(/[\b]/g, "\\b")
+ .replace(/[\n]/g, "\\n")
+ .replace(/[\t]/g, "\\t")
+ .replace(/[\r]/g, "\\r")
+ .replace(/[\u2028]/g, "\\u2028")
+ .replace(/[\u2029]/g, "\\u2029");
+ },
+
+ createXhr: masterConfig.createXhr || function () {
+ //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
+ var xhr, i, progId;
+ if (typeof XMLHttpRequest !== "undefined") {
+ return new XMLHttpRequest();
+ } else if (typeof ActiveXObject !== "undefined") {
+ for (i = 0; i < 3; i += 1) {
+ progId = progIds[i];
+ try {
+ xhr = new ActiveXObject(progId);
+ } catch (e) {}
+
+ if (xhr) {
+ progIds = [progId]; // so faster next time
+ break;
+ }
+ }
+ }
+
+ return xhr;
+ },
+
+ /**
+ * Parses a resource name into its component parts. Resource names
+ * look like: module/name.ext!strip, where the !strip part is
+ * optional.
+ * @param {String} name the resource name
+ * @returns {Object} with properties "moduleName", "ext" and "strip"
+ * where strip is a boolean.
+ */
+ parseName: function (name) {
+ var strip = false, index = name.indexOf("."),
+ modName = name.substring(0, index),
+ ext = name.substring(index + 1, name.length);
+
+ index = ext.indexOf("!");
+ if (index !== -1) {
+ //Pull off the strip arg.
+ strip = ext.substring(index + 1, ext.length);
+ strip = strip === "strip";
+ ext = ext.substring(0, index);
+ }
+
+ return {
+ moduleName: modName,
+ ext: ext,
+ strip: strip
+ };
+ },
+
+ xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
+
+ /**
+ * Is an URL on another domain. Only works for browser use, returns
+ * false in non-browser environments. Only used to know if an
+ * optimized .js version of a text resource should be loaded
+ * instead.
+ * @param {String} url
+ * @returns Boolean
+ */
+ useXhr: function (url, protocol, hostname, port) {
+ var uProtocol, uHostName, uPort,
+ match = text.xdRegExp.exec(url);
+ if (!match) {
+ return true;
+ }
+ uProtocol = match[2];
+ uHostName = match[3];
+
+ uHostName = uHostName.split(':');
+ uPort = uHostName[1];
+ uHostName = uHostName[0];
+
+ return (!uProtocol || uProtocol === protocol) &&
+ (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
+ ((!uPort && !uHostName) || uPort === port);
+ },
+
+ finishLoad: function (name, strip, content, onLoad) {
+ content = strip ? text.strip(content) : content;
+ if (masterConfig.isBuild) {
+ buildMap[name] = content;
+ }
+ onLoad(content);
+ },
+
+ load: function (name, req, onLoad, config) {
+ //Name has format: some.module.filext!strip
+ //The strip part is optional.
+ //if strip is present, then that means only get the string contents
+ //inside a body tag in an HTML string. For XML/SVG content it means
+ //removing the <?xml ...?> declarations so the content can be inserted
+ //into the current doc without problems.
+
+ // Do not bother with the work if a build and text will
+ // not be inlined.
+ if (config.isBuild && !config.inlineText) {
+ onLoad();
+ return;
+ }
+
+ masterConfig.isBuild = config.isBuild;
+
+ var parsed = text.parseName(name),
+ nonStripName = parsed.moduleName + '.' + parsed.ext,
+ url = req.toUrl(nonStripName),
+ useXhr = (masterConfig.useXhr) ||
+ text.useXhr;
+
+ //Load the text. Use XHR if possible and in a browser.
+ if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
+ text.get(url, function (content) {
+ text.finishLoad(name, parsed.strip, content, onLoad);
+ }, function (err) {
+ if (onLoad.error) {
+ onLoad.error(err);
+ }
+ });
+ } else {
+ //Need to fetch the resource across domains. Assume
+ //the resource has been optimized into a JS module. Fetch
+ //by the module name + extension, but do not include the
+ //!strip part to avoid file system issues.
+ req([nonStripName], function (content) {
+ text.finishLoad(parsed.moduleName + '.' + parsed.ext,
+ parsed.strip, content, onLoad);
+ });
+ }
+ },
+
+ write: function (pluginName, moduleName, write, config) {
+ if (buildMap.hasOwnProperty(moduleName)) {
+ var content = text.jsEscape(buildMap[moduleName]);
+ write.asModule(pluginName + "!" + moduleName,
+ "define(function () { return '" +
+ content +
+ "';});\n");
+ }
+ },
+
+ writeFile: function (pluginName, moduleName, req, write, config) {
+ var parsed = text.parseName(moduleName),
+ nonStripName = parsed.moduleName + '.' + parsed.ext,
+ //Use a '.js' file name so that it indicates it is a
+ //script that can be loaded across domains.
+ fileName = req.toUrl(parsed.moduleName + '.' +
+ parsed.ext) + '.js';
+
+ //Leverage own load() method to load plugin value, but only
+ //write out values that do not have the strip argument,
+ //to avoid any potential issues with ! in file names.
+ text.load(nonStripName, req, function (value) {
+ //Use own write() method to construct full module value.
+ //But need to create shell that translates writeFile's
+ //write() to the right interface.
+ var textWrite = function (contents) {
+ return write(fileName, contents);
+ };
+ textWrite.asModule = function (moduleName, contents) {
+ return write.asModule(moduleName, fileName, contents);
+ };
+
+ text.write(pluginName, nonStripName, textWrite, config);
+ }, config);
+ }
+ };
+
+ if (masterConfig.env === 'node' || (!masterConfig.env &&
+ typeof process !== "undefined" &&
+ process.versions &&
+ !!process.versions.node)) {
+ //Using special require.nodeRequire, something added by r.js.
+ fs = require.nodeRequire('fs');
+
+ text.get = function (url, callback) {
+ var file = fs.readFileSync(url, 'utf8');
+ //Remove BOM (Byte Mark Order) from utf8 files if it is there.
+ if (file.indexOf('\uFEFF') === 0) {
+ file = file.substring(1);
+ }
+ callback(file);
+ };
+ } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
+ text.createXhr())) {
+ text.get = function (url, callback, errback) {
+ var xhr = text.createXhr();
+ xhr.open('GET', url, true);
+
+ //Allow overrides specified in config
+ if (masterConfig.onXhr) {
+ masterConfig.onXhr(xhr, url);
+ }
+
+ xhr.onreadystatechange = function (evt) {
+ var status, err;
+ //Do not explicitly handle errors, those should be
+ //visible via console output in the browser.
+ if (xhr.readyState === 4) {
+ status = xhr.status;
+ if (status > 399 && status < 600) {
+ //An http 4xx or 5xx error. Signal an error.
+ err = new Error(url + ' HTTP status: ' + status);
+ err.xhr = xhr;
+ errback(err);
+ } else {
+ callback(xhr.responseText);
+ }
+ }
+ };
+ xhr.send(null);
+ };
+ } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
+ typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
+ //Why Java, why is this so awkward?
+ text.get = function (url, callback) {
+ var stringBuffer, line,
+ encoding = "utf-8",
+ file = new java.io.File(url),
+ lineSeparator = java.lang.System.getProperty("line.separator"),
+ input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
+ content = '';
+ try {
+ stringBuffer = new java.lang.StringBuffer();
+ line = input.readLine();
+
+ // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
+ // http://www.unicode.org/faq/utf_bom.html
+
+ // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
+ if (line && line.length() && line.charAt(0) === 0xfeff) {
+ // Eat the BOM, since we've already found the encoding on this file,
+ // and we plan to concatenating this buffer with others; the BOM should
+ // only appear at the top of a file.
+ line = line.substring(1);
+ }
+
+ stringBuffer.append(line);
+
+ while ((line = input.readLine()) !== null) {
+ stringBuffer.append(lineSeparator);
+ stringBuffer.append(line);
+ }
+ //Make sure we return a JavaScript string and not a Java string.
+ content = String(stringBuffer.toString()); //String
+ } finally {
+ input.close();
+ }
+ callback(content);
+ };
+ }
+
+ return text;
+});
diff --git a/module/web/static/js/routers/defaultRouter.js b/module/web/static/js/routers/defaultRouter.js
new file mode 100644
index 000000000..95b8de967
--- /dev/null
+++ b/module/web/static/js/routers/defaultRouter.js
@@ -0,0 +1,29 @@
+define(['jquery','backbone','views/headerView'], function($, Backbone, HeaderView){
+
+ var Router = Backbone.Router.extend({
+
+ initialize: function(){
+ Backbone.history.start();
+ },
+
+ // All of your Backbone Routes (add more)
+ routes: {
+
+ // When there is no hash bang on the url, the home method is called
+ '': 'home'
+
+ },
+
+ 'home': function(){
+ // Instantiating mainView and anotherView instances
+ var headerView = new HeaderView();
+
+ // Renders the mainView template
+ headerView.render();
+
+ }
+ });
+
+ // Returns the Router class
+ return Router;
+}); \ No newline at end of file
diff --git a/module/web/static/js/routers/mobileRouter.js b/module/web/static/js/routers/mobileRouter.js
new file mode 100644
index 000000000..7f1f7805e
--- /dev/null
+++ b/module/web/static/js/routers/mobileRouter.js
@@ -0,0 +1,55 @@
+define(['jquery','backbone', 'underscore'], function($, Backbone, _){
+
+ return Backbone.Router.extend({
+
+ initialize: function(){
+ _.bindAll(this, "changePage");
+
+ this.$el = $("#content");
+
+ // Tells Backbone to start watching for hashchange events
+ Backbone.history.start();
+
+ },
+
+ // All of your Backbone Routes (add more)
+ routes: {
+
+ // When there is no hash bang on the url, the home method is called
+ '': 'home'
+
+ },
+
+ 'home': function(){
+
+ var self = this;
+
+ $("#p1").fastClick(function(){
+ self.changePage($("<div class='page' style='background-color: #9acd32;'><h1>Page 1</h1><br>some content<br>sdfdsf<br>sdffg<h3>oiuzz</h3></div>"));
+ });
+
+ $("#p2").bind("click", function(){
+ self.changePage($("<div class='page' style='background-color: blue;'><h1>Page 2</h1><br>some content<br>sdfdsf<br><h2>sdfsdf</h2>sdffg</div>"));
+ });
+
+ },
+
+ changePage: function(content){
+
+ var oldpage = this.$el.find(".page");
+ content.css({x: "100%"});
+ this.$el.append(content);
+ content.transition({x:0}, function(){
+ window.setTimeout(function(){
+ oldpage.remove();
+ }, 400);
+ });
+
+// $("#viewport").transition({x: "100%"}, function(){
+// $("#viewport").html(content);
+// $("#viewport").transition({x: 0});
+// });
+ }
+
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/utils/animations.js b/module/web/static/js/utils/animations.js
new file mode 100644
index 000000000..9b1448f61
--- /dev/null
+++ b/module/web/static/js/utils/animations.js
@@ -0,0 +1,36 @@
+define(['jquery', 'underscore', 'transit'], function(jQuery, _) {
+
+ // Overwrite common animations with transitions
+ jQuery.each({
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" }
+ }, function(name, props) {
+ jQuery.fn[ name ] = function(speed, easing, callback) {
+ return this.transition(props, speed, easing, callback);
+ };
+ });
+
+ jQuery.fn._transit = jQuery.fn.transit;
+
+ // Over riding transit plugin to support hide and show
+ // Props retains it properties across multiple calls, therefore props.show value is introduced
+ jQuery.fn.transit = jQuery.fn.transition = function(props, duration, easing, callback) {
+ var self = this;
+ var cb = callback;
+ if (props && (props.opacity === 'hide' || (props.opacity === 0 && props.show === true))) {
+ props.opacity = 0;
+ props.show = true;
+
+ callback = function() {
+ self.css({display: 'none'});
+ if (typeof cb === 'function') { cb.apply(self); }
+ };
+ } else if (props && (props.opacity === 'show' || (props.opacity === 1 && props.show === true))) {
+ props.opacity = 1;
+ props.show = true;
+ this.css({display: 'block'});
+ }
+
+ return this._transit(props, duration, easing, callback);
+ };
+}); \ No newline at end of file
diff --git a/module/web/static/js/utils/lazyRequire.js b/module/web/static/js/utils/lazyRequire.js
new file mode 100644
index 000000000..d20d78610
--- /dev/null
+++ b/module/web/static/js/utils/lazyRequire.js
@@ -0,0 +1,89 @@
+// Define the module.
+define(
+ [
+ "require"
+ ],
+ function( require ){
+
+
+ // Define the states of loading for a given set of modules
+ // within a require() statement.
+ var states = {
+ unloaded: "UNLOADED",
+ loading: "LOADING",
+ loaded: "LOADED"
+ };
+
+
+ // Define the top-level module container. Mostly, we're making
+ // the top-level container a non-Function so that users won't
+ // try to invoke this without calling the once() method below.
+ var lazyRequire = {};
+
+
+ // I will return a new, unique instance of the requrieOnce()
+ // method. Each instance will only call the require() method
+ // once internally.
+ lazyRequire.once = function(){
+
+ // The modules start in an unloaded state before
+ // requireOnce() is invoked by the calling code.
+ var state = states.unloaded;
+ var args;
+
+ var requireOnce = function( dependencies, loadCallback ){
+
+ // Use the module state to determine which method to
+ // invoke (or just to ignore the invocation).
+ if (state === states.loaded){
+ loadCallback.apply(null, args);
+
+ // The modules have not yet been requested - let's
+ // lazy load them.
+ } else if (state !== states.loading){
+
+ // We're about to load the modules asynchronously;
+ // flag the interim state.
+ state = states.loading;
+
+ // Load the modules.
+ require(
+ dependencies,
+ function(){
+
+ args = arguments;
+ loadCallback.apply( null, args );
+ state = states.loaded;
+
+
+ }
+ );
+
+ // RequireJS is currently loading the modules
+ // asynchronously, but they have not finished
+ // loading yet.
+ } else {
+
+ // Simply ignore this call.
+ return;
+
+ }
+
+ };
+
+ // Return the new lazy loader.
+ return( requireOnce );
+
+ };
+
+
+ // -------------------------------------------------- //
+ // -------------------------------------------------- //
+
+
+ // Return the module definition.
+ return( lazyRequire );
+
+
+ }
+); \ No newline at end of file
diff --git a/module/web/static/js/views/fileView.js b/module/web/static/js/views/fileView.js
new file mode 100644
index 000000000..7db8112c8
--- /dev/null
+++ b/module/web/static/js/views/fileView.js
@@ -0,0 +1,20 @@
+define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) {
+
+ // Renders single file item
+ return Backbone.View.extend({
+
+ tagName: 'li',
+ events: {
+
+ },
+
+ initialize: function() {
+ },
+
+ render: function() {
+ this.$el.html(this.model.get('name'));
+ return this;
+ }
+
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/views/headerView.js b/module/web/static/js/views/headerView.js
new file mode 100644
index 000000000..21b591a3d
--- /dev/null
+++ b/module/web/static/js/views/headerView.js
@@ -0,0 +1,75 @@
+define(['jquery', 'backbone', 'flot', 'jqueryui/progressbar'], function($, Backbone){
+ // Renders the header with all information
+ return Backbone.View.extend({
+
+ el: 'header',
+
+ events: {
+
+ },
+
+ initialize: function() {
+
+ var totalPoints = 100;
+ var data = [];
+
+ function getRandomData() {
+ if (data.length > 0)
+ data = data.slice(1);
+
+ // do a random walk
+ while (data.length < totalPoints) {
+ var prev = data.length > 0 ? data[data.length - 1] : 50;
+ var y = prev + Math.random() * 10 - 5;
+ if (y < 0)
+ y = 0;
+ if (y > 100)
+ y = 100;
+ data.push(y);
+ }
+
+ // zip the generated y values with the x values
+ var res = [];
+ for (var i = 0; i < data.length; ++i)
+ res.push([i, data[i]])
+ return res;
+ }
+
+ var updateInterval = 1500;
+
+ var speedgraph = $.plot(this.$el.find("#speedgraph"), [getRandomData()], {
+ series:{
+ lines:{ show:true, lineWidth:2 },
+ shadowSize:0,
+ color:"#fee247"
+ },
+ xaxis:{ ticks:[], mode:"time" },
+ yaxis:{ ticks:[], min:0, autoscaleMargin:0.1 },
+ grid:{
+ show:true,
+// borderColor: "#757575",
+ borderColor:"white",
+ borderWidth:1,
+ labelMargin:0,
+ axisMargin:0,
+ minBorderMargin:0
+ }
+ });
+
+ function update() {
+ speedgraph.setData([ getRandomData() ]);
+ // since the axes don't change, we don't need to call plot.setupGrid()
+ speedgraph.draw();
+
+ setTimeout(update, updateInterval);
+ }
+
+ update();
+
+ },
+
+
+ render: function() {
+ }
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/views/mobile/my.js b/module/web/static/js/views/mobile/my.js
new file mode 100644
index 000000000..41203e6c5
--- /dev/null
+++ b/module/web/static/js/views/mobile/my.js
@@ -0,0 +1,275 @@
+(function($) {
+ $.widget('mobile.tabbar', $.mobile.navbar, {
+ _create: function() {
+ // Set the theme before we call the prototype, which will
+ // ensure buttonMarkup() correctly grabs the inheritied theme.
+ // We default to the "a" swatch if none is found
+ var theme = this.element.jqmData('theme') || "a";
+ this.element.addClass('ui-footer ui-footer-fixed ui-bar-' + theme);
+
+ // Make sure the page has padding added to it to account for the fixed bar
+ this.element.closest('[data-role="page"]').addClass('ui-page-footer-fixed');
+
+
+ // Call the NavBar _create prototype
+ $.mobile.navbar.prototype._create.call(this);
+ },
+
+ // Set the active URL for the Tab Bar, and highlight that button on the bar
+ setActive: function(url) {
+ // Sometimes the active state isn't properly cleared, so we reset it ourselves
+ this.element.find('a').removeClass('ui-btn-active ui-state-persist');
+ this.element.find('a[href="' + url + '"]').addClass('ui-btn-active ui-state-persist');
+ }
+ });
+
+ $(document).bind('pagecreate create', function(e) {
+ return $(e.target).find(":jqmData(role='tabbar')").tabbar();
+ });
+
+ $(":jqmData(role='page')").live('pageshow', function(e) {
+ // Grab the id of the page that's showing, and select it on the Tab Bar on the page
+ var tabBar, id = $(e.target).attr('id');
+
+ tabBar = $.mobile.activePage.find(':jqmData(role="tabbar")');
+ if(tabBar.length) {
+ tabBar.tabbar('setActive', '#' + id);
+ }
+ });
+
+var attachEvents = function() {
+ var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc;
+
+ $( document ).bind( {
+ "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) {
+ var theme,
+ $btn = $( closestEnabledButton( event.target ) ),
+ evt = event.type;
+
+ if ( $btn.length ) {
+ theme = $btn.attr( "data-" + $.mobile.ns + "theme" );
+
+ if ( evt === "vmousedown" ) {
+ if ( $.support.touch ) {
+ hov = setTimeout(function() {
+ $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme );
+ }, hoverDelay );
+ } else {
+ $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme );
+ }
+ } else if ( evt === "vmousecancel" || evt === "vmouseup" ) {
+ $btn.removeClass( "ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme );
+ } else if ( evt === "vmouseover" || evt === "focus" ) {
+ if ( $.support.touch ) {
+ foc = setTimeout(function() {
+ $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme );
+ }, hoverDelay );
+ } else {
+ $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme );
+ }
+ } else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) {
+ $btn.removeClass( "ui-btn-hover-" + theme + " ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme );
+ if ( hov ) {
+ clearTimeout( hov );
+ }
+ if ( foc ) {
+ clearTimeout( foc );
+ }
+ }
+ }
+ },
+ "focusin focus": function( event ){
+ $( closestEnabledButton( event.target ) ).addClass( $.mobile.focusClass );
+ },
+ "focusout blur": function( event ){
+ $( closestEnabledButton( event.target ) ).removeClass( $.mobile.focusClass );
+ }
+ });
+
+ attachEvents = null;
+};
+
+$.fn.buttonMarkup = function( options ) {
+ var $workingSet = this;
+
+ // Enforce options to be of type string
+ options = ( options && ( $.type( options ) == "object" ) )? options : {};
+ for ( var i = 0; i < $workingSet.length; i++ ) {
+ var el = $workingSet.eq( i ),
+ e = el[ 0 ],
+ o = $.extend( {}, $.fn.buttonMarkup.defaults, {
+ icon: options.icon !== undefined ? options.icon : el.jqmData( "icon" ),
+ iconpos: options.iconpos !== undefined ? options.iconpos : el.jqmData( "iconpos" ),
+ theme: options.theme !== undefined ? options.theme : el.jqmData( "theme" ) || $.mobile.getInheritedTheme( el, "c" ),
+ inline: options.inline !== undefined ? options.inline : el.jqmData( "inline" ),
+ shadow: options.shadow !== undefined ? options.shadow : el.jqmData( "shadow" ),
+ corners: options.corners !== undefined ? options.corners : el.jqmData( "corners" ),
+ iconshadow: options.iconshadow !== undefined ? options.iconshadow : el.jqmData( "iconshadow" ),
+ iconsize: options.iconsize !== undefined ? options.iconsize : el.jqmData( "iconsize" ),
+ mini: options.mini !== undefined ? options.mini : el.jqmData( "mini" )
+ }, options ),
+
+ // Classes Defined
+ innerClass = "ui-btn-inner",
+ textClass = "ui-btn-text",
+ buttonClass, iconClass,
+ // Button inner markup
+ buttonInner,
+ buttonText,
+ buttonIcon,
+ buttonElements;
+
+ $.each(o, function(key, value) {
+ e.setAttribute( "data-" + $.mobile.ns + key, value );
+ el.jqmData(key, value);
+ });
+
+ // Check if this element is already enhanced
+ buttonElements = $.data(((e.tagName === "INPUT" || e.tagName === "BUTTON") ? e.parentNode : e), "buttonElements");
+
+ if (buttonElements) {
+ e = buttonElements.outer;
+ el = $(e);
+ buttonInner = buttonElements.inner;
+ buttonText = buttonElements.text;
+ // We will recreate this icon below
+ $(buttonElements.icon).remove();
+ buttonElements.icon = null;
+ }
+ else {
+ buttonInner = document.createElement( o.wrapperEls );
+ buttonText = document.createElement( o.wrapperEls );
+ }
+ buttonIcon = o.icon ? document.createElement( "span" ) : null;
+
+ if ( attachEvents && !buttonElements) {
+ attachEvents();
+ }
+
+ // if not, try to find closest theme container
+ if ( !o.theme ) {
+ o.theme = $.mobile.getInheritedTheme( el, "c" );
+ }
+
+ buttonClass = "ui-btn ui-btn-up-" + o.theme;
+ buttonClass += o.inline ? " ui-btn-inline" : "";
+ buttonClass += o.shadow ? " ui-shadow" : "";
+ buttonClass += o.corners ? " ui-btn-corner-all" : "";
+
+ if ( o.mini !== undefined ) {
+ // Used to control styling in headers/footers, where buttons default to `mini` style.
+ buttonClass += o.mini ? " ui-mini" : " ui-fullsize";
+ }
+
+ if ( o.inline !== undefined ) {
+ // Used to control styling in headers/footers, where buttons default to `mini` style.
+ buttonClass += o.inline === false ? " ui-btn-block" : " ui-btn-inline";
+ }
+
+
+ if ( o.icon ) {
+ o.icon = "ui-icon-" + o.icon;
+ o.iconpos = o.iconpos || "left";
+
+ iconClass = "ui-icon " + o.icon;
+
+ if ( o.iconshadow ) {
+ iconClass += " ui-icon-shadow";
+ }
+
+ if ( o.iconsize ) {
+ iconClass += " ui-iconsize-" + o.iconsize;
+ }
+ }
+
+ if ( o.iconpos ) {
+ buttonClass += " ui-btn-icon-" + o.iconpos;
+
+ if ( o.iconpos == "notext" && !el.attr( "title" ) ) {
+ el.attr( "title", el.getEncodedText() );
+ }
+ }
+
+ innerClass += o.corners ? " ui-btn-corner-all" : "";
+
+ if ( o.iconpos && o.iconpos === "notext" && !el.attr( "title" ) ) {
+ el.attr( "title", el.getEncodedText() );
+ }
+
+ if ( buttonElements ) {
+ el.removeClass( buttonElements.bcls || "" );
+ }
+ el.removeClass( "ui-link" ).addClass( buttonClass );
+
+ buttonInner.className = innerClass;
+
+ buttonText.className = textClass;
+ if ( !buttonElements ) {
+ buttonInner.appendChild( buttonText );
+ }
+ if ( buttonIcon ) {
+ buttonIcon.className = iconClass;
+ if ( !(buttonElements && buttonElements.icon) ) {
+ buttonIcon.appendChild( document.createTextNode("\u00a0") );
+ buttonInner.appendChild( buttonIcon );
+ }
+ }
+
+ while ( e.firstChild && !buttonElements) {
+ buttonText.appendChild( e.firstChild );
+ }
+
+ if ( !buttonElements ) {
+ e.appendChild( buttonInner );
+ }
+
+ // Assign a structure containing the elements of this button to the elements of this button. This
+ // will allow us to recognize this as an already-enhanced button in future calls to buttonMarkup().
+ buttonElements = {
+ bcls : buttonClass,
+ outer : e,
+ inner : buttonInner,
+ text : buttonText,
+ icon : buttonIcon
+ };
+
+ $.data(e, 'buttonElements', buttonElements);
+ $.data(buttonInner, 'buttonElements', buttonElements);
+ $.data(buttonText, 'buttonElements', buttonElements);
+ if (buttonIcon) {
+ $.data(buttonIcon, 'buttonElements', buttonElements);
+ }
+ }
+
+ return this;
+};
+
+$.fn.buttonMarkup.defaults = {
+ corners: true,
+ shadow: true,
+ iconshadow: true,
+ iconsize: 18,
+ wrapperEls: "span"
+};
+
+function closestEnabledButton( element ) {
+ var cname;
+
+ while ( element ) {
+ // Note that we check for typeof className below because the element we
+ // handed could be in an SVG DOM where className on SVG elements is defined to
+ // be of a different type (SVGAnimatedString). We only operate on HTML DOM
+ // elements, so we look for plain "string".
+ cname = ( typeof element.className === 'string' ) && (element.className + ' ');
+ if ( cname && cname.indexOf("ui-btn ") > -1 && cname.indexOf("ui-disabled ") < 0 ) {
+ break;
+ }
+
+ element = element.parentNode;
+ }
+
+ return element;
+}
+
+
+})(jQuery);
diff --git a/module/web/static/js/views/modal/modalView.js b/module/web/static/js/views/modal/modalView.js
new file mode 100644
index 000000000..b20aab57d
--- /dev/null
+++ b/module/web/static/js/views/modal/modalView.js
@@ -0,0 +1,84 @@
+define(['jquery', 'backbone', 'underscore', 'text!tpl/default/modal.html', 'omniwindow'], function($, Backbone, _, template) {
+
+ return Backbone.View.extend({
+
+ events: {
+ 'click .btn-close': 'hide',
+ 'click .close': 'hide'
+ },
+
+ template: _.template(template),
+
+ dialog: null,
+
+ initialize: function() {
+
+ },
+
+ render: function() {
+ this.$el.html(this.template({ content: this.renderContent().html(), header: this.getHeader()}));
+ this.$el.addClass('modal hide');
+ this.$el.css({opacity: 0, scale: 0.7});
+ $("body").append(this.el);
+
+ this.dialog = this.$el.omniWindow({
+ overlay: {
+ selector: '#modal-overlay',
+ hideClass: 'hide',
+ animations: {
+ hide: function(subjects, internalCallback) {
+ subjects.overlay.fadeOut(400, function() {
+ internalCallback(subjects);
+ });
+ },
+ show: function(subjects, internalCallback) {
+ subjects.overlay.fadeIn(250, function() {
+ internalCallback(subjects);
+ });
+ }}},
+ modal: {
+ hideClass: 'hide',
+ animations: {
+ hide: function(subjects, internalCallback) {
+ subjects.modal.transition({opacity: 'hide', scale: 0.7}, 250, function() {
+ internalCallback(subjects);
+ });
+ },
+
+ show: function(subjects, internalCallback) {
+ subjects.modal.transition({opacity: 'show', scale: 1}, 250, function() {
+ internalCallback(subjects);
+ });
+ }}
+ }});
+
+ return this;
+ },
+ renderContent: function() {
+ return $('<h1>Content!</h1>');
+ },
+
+ getHeader: function() {
+ return 'Dialog';
+ },
+
+ show: function() {
+ if (this.dialog === null)
+ this.render();
+
+ this.dialog.trigger('show');
+
+ // TODO: set focus on first element
+ },
+
+ hide: function() {
+ this.dialog.trigger('hide');
+ },
+
+ destroy: function() {
+ this.$el.remove();
+ this.dialog = null;
+ }
+
+ });
+}); \ No newline at end of file
diff --git a/module/web/static/js/views/packageTreeView.js b/module/web/static/js/views/packageTreeView.js
new file mode 100644
index 000000000..30f159cf7
--- /dev/null
+++ b/module/web/static/js/views/packageTreeView.js
@@ -0,0 +1,75 @@
+define(['jquery', 'backbone', 'underscore', 'models/TreeCollection', 'views/packageView', 'views/fileView'],
+ function($, Backbone, _, TreeCollection, packageView, fileView) {
+
+ // Renders whole PackageView
+ return Backbone.View.extend({
+
+ el: '#content',
+
+ events: {
+ 'click #add': 'addPackage',
+ 'keypress #name': 'addOnEnter'
+ },
+
+ initialize: function() {
+ this.tree = new TreeCollection();
+
+ },
+
+ init: function() {
+ var self = this;
+ this.tree.fetch({success: function() {
+ self.render();
+ }});
+ },
+
+ render: function() {
+ var packs = this.tree.get('packages'),
+ files = this.tree.get('files');
+
+ this.$el.append($('<span>Root: ' + this.tree.get('root').get('name') + ' </span>'));
+ this.$el.append($('<input id="name" type="text" size="20">'));
+ this.$el.append($('<a id="add" href="#"> Add</a><br>'));
+
+ var ul = $('<ul></ul>');
+ packs.each(function(pack) {
+ ul.append(new packageView({model: pack}).render().el);
+ });
+
+ this.$el.append(ul);
+ this.$el.append($('<br> Files: ' + files.size() + '<br>'));
+
+ ul = $('<ul></ul>');
+ files.each(function(file) {
+ ul.append(new fileView({model: file}).render().el);
+ });
+
+ this.$el.append(ul);
+
+ return this;
+ },
+
+ addOnEnter: function(e) {
+ if (e.keyCode != 13) return;
+ this.addPackage(e);
+ },
+
+ addPackage: function() {
+ var self = this;
+ var settings = {
+ data: {
+ name: '"' + $('#name').val() + '"',
+ links: '["some link"]'
+ },
+ success: function() {
+ self.tree.fetch({success: function() {
+ self.render();
+ }});
+ }
+ };
+
+ $.ajax('api/addPackage', settings);
+ $('#name').val('');
+ }
+ });
+ }); \ No newline at end of file
diff --git a/module/web/static/js/views/packageView.js b/module/web/static/js/views/packageView.js
new file mode 100644
index 000000000..171325d1f
--- /dev/null
+++ b/module/web/static/js/views/packageView.js
@@ -0,0 +1,60 @@
+define(['jquery', 'backbone', 'underscore', 'views/fileView', 'utils/lazyRequire'],
+ function($, Backbone, _, fileView, lazyLoader) {
+
+ // Renders a single package item
+ return Backbone.View.extend({
+
+ tagName: 'li',
+ events: {
+ 'click .load': 'load',
+ 'click .delete': 'delete',
+ 'click .show-dialog': 'show'
+ },
+
+ modal: null,
+ requireOnce: lazyLoader.once(),
+
+ initialize: function() {
+ this.model.on('change', this.render, this);
+ this.model.on('remove', this.unrender, this);
+ },
+
+ render: function() {
+ this.$el.html('Package ' + this.model.get('pid') + ': ' + this.model.get('name'));
+ this.$el.append($('<a class="load" href="#"> Load</a>'));
+ this.$el.append($('<a class="delete" href="#"> Delete</a>'));
+ this.$el.append($('<a class="show-dialog" href="#"> Show</a>'));
+
+ if (this.model.isLoaded()) {
+ var ul = $('<ul></ul>');
+ this.model.get('files').each(function(file) {
+ ul.append(new fileView({model: file}).render().el);
+ });
+ this.$el.append(ul);
+ }
+ return this;
+ },
+
+ unrender: function() {
+ this.$el.remove();
+ },
+
+ load: function() {
+ this.model.fetch();
+ },
+
+ delete: function() {
+ this.model.destroy();
+ },
+
+ show: function() {
+ var self = this;
+ this.requireOnce(['views/modal/modalView'], function(modalView){
+ if (self.modal === null)
+ self.modal = new modalView();
+
+ self.modal.show();
+ });
+ }
+ });
+}); \ No newline at end of file
diff --git a/module/web/templates/500.html b/module/web/templates/500.html
index e15090b66..8bffe6dbb 100644
--- a/module/web/templates/500.html
+++ b/module/web/templates/500.html
@@ -1,5 +1,4 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd">
+<!doctype html>
<html>
<head>
<title>Server Error</title>
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/backbone/modal.html b/module/web/templates/default/backbone/modal.html
new file mode 100644
index 000000000..afc5d7b58
--- /dev/null
+++ b/module/web/templates/default/backbone/modal.html
@@ -0,0 +1,11 @@
+<div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+ <h3><%- header %></h3>
+</div>
+<div class="modal-body">
+ <%- content %>
+</div>
+<div class="modal-footer">
+ <a href="#" class="btn btn-close">Close</a>
+ <a href="#" class="btn btn-primary">Save</a>
+</div> \ No newline at end of file
diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html
index 0b20ecdb0..f193db75c 100644
--- a/module/web/templates/default/base.html
+++ b/module/web/templates/default/base.html
@@ -1,180 +1,115 @@
-<?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">
+<!DOCTYPE html>
+<html>
<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 %}
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <title>{% block title %}pyLoad {{ _("WebUI") }}{% endblock %}</title>
+ <meta name="description" content="pyLoad {{ _("Webinterface") }}">
+ <meta name="viewport" content="width=device-width">
+
+ <!-- TODO Include this font -->
+ <link href="http://fonts.googleapis.com/css?family=Abel" rel="stylesheet" type="text/css"/>
+ <link href="static/css/bootstrap.css" rel="stylesheet" type="text/css"/>
+ <link href="static/css/font.css" rel="stylesheet" type="text/css"/>
+ <link href="static/css/default/style.less" rel="stylesheet/less" type="text/css" media="screen"/>
+
+
+ <script src="static/js/libs/less-1.3.0.min.js" type="text/javascript"></script>
+ <script type="text/javascript" data-main="static/js/default" src="static/js/libs/require-2.0.6.js"></script>
+ <script>
+ require(['default'], function (App) {
+ App.init();
+ {% block require %}
+ {% endblock %}
+ });
+ </script>
+
+ {% 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 id="wrap">
+ <header>
+ <div class="center">
+ <div class="logo"></div>
+ <span class="title">pyLoad</span>
+
+ {% if user %}
+ <div id="notification_div">
+ No runnings tasks
+ <div class="progress progress-warning progress-striped" id="globalprogress">
+ <div class="bar" style="width: 60%"></div>
+ </div>
+ </div>
+
+ <div class="header_block">
+ <div class="header_icon" id="header_user">
+ <span>{{ user.name }}</span>
+ </div>
+ <div class="header_text">
+ <a href="/logout" id="header_logout">{{ _("Logout") }}</a>
+ </div>
+ </div>
+ <div id="speedgraph"></div>
+ <div class="header_block">
+ <div class="header_icon" id="header_speed">
+ <span class="header_text">500 kb/s</span>
+ </div>
+ <div class="header_icon" id="header_qeuue">
+ <span class="header_text">5 / 125</span>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ </header>
+ <div id="content">
+ {% for msg in messages %}
+ <p>{{ msg }}</p>
+ {% endfor %}
+
+ {% block content %}
+ {% endblock content %}
+ </div>
</div>
+<footer>
+ <div class="center">
+ <div class="logo"></div>
+ <div class="block copyright">
+ © 2008-2012<br>
+ The pyLoad Team<br>
+ </div>
+
+ <div class="block">
+ <h2 class="block-title">Powered by</h2>
+ asd <br>
+ dsfdsf <br>
+ sdf dsg <br>
+ </div>
+
+ <div class="block">
+ <h2 class="block-title">pyLoad</h2>
+ <a href="/toggle_mobile">{{_("Mobile Version")}}</a> <br>
+ dsfdsf <br>
+ sdf dsg <br>
+ </div>
+
+ <div class="block">
+ <h2 class="block-title">Community</h2>
+ asd <br>
+ dsfdsf <br>
+ sdf dsg <br>
+ </div>
+
+ <div class="block">
+ <h2 class="block-title">Development</h2>
+ asd <br>
+ dsfdsf <br>
+ sdf dsg <br>
+ </div>
+ </div>
+</footer>
+<div id="modal-overlay" class="hide"></div>
+{% block deferred %}
+{% endblock deferred %}
</body>
</html>
diff --git a/module/web/templates/default/captcha.html b/module/web/templates/default/captcha.html
deleted file mode 100644
index 332a9c102..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/dashboard.html b/module/web/templates/default/dashboard.html
new file mode 100644
index 000000000..05f5b85a3
--- /dev/null
+++ b/module/web/templates/default/dashboard.html
@@ -0,0 +1,65 @@
+{% extends 'default/base.html' %}
+{% block title %}
+ {{_("Dashboard")}} - {{ super()}}
+{% endblock %}
+
+{% block require %}
+ App.initPackageTree();
+{% endblock %}
+
+{% block content %}
+ <ul id="dash-nav" class="nav nav-pills">
+ <li>
+ <ul class="breadcrumb">
+ <li><a href="#">Home</a> <span class="divider">/</span></li>
+ <li><a href="#">Library</a> <span class="divider">/</span></li>
+ <li class="active">Data</li>
+ </ul>
+ </li>
+
+ <li style="float: right;">
+ <form class="form-search">
+ <div class="input-append">
+ <input type="text" class="span2 search-query">
+ <button type="submit" class="btn">{{ _("Search") }}</button>
+ </div>
+ </form>
+ </li>
+ <li class="dropdown" style="float: right;">
+ <a class="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#">
+ Type
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a>Audio</a></li>
+ <li><a>Video</a></li>
+ <li><a>Archive</a></li>
+ </ul>
+ </li>
+ <li class="dropdown" style="float: right;">
+ <a class="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#">
+ More
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a>Active</a></li>
+ <li><a>Failed</a></li>
+ </ul>
+ </li>
+
+ <li style="float: right;">
+ <a>Failed</a>
+ </li>
+ <li style="float: right;">
+ <a>Unfinished</a>
+ </li>
+ <li class="active" style="float: right;">
+ <a>All</a>
+ </li>
+
+ </ul>
+{% endblock %} \ 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 0efb1bcf8..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
index 9e91ad309..c8cd78a33 100644
--- a/module/web/templates/default/login.html
+++ b/module/web/templates/default/login.html
@@ -1,36 +1,43 @@
{% 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>
+<br>
+{% if logout %}
+<div id="logged_out">
+ <b>{{_("You were successfully logged out.")}}</b>
+</div>
+{% endif %}
+<br>
+<div class="login">
+ <form action="/login" method="post" class="form-horizontal">
<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>
-
+ <div class="control-group">
+ <label class="control-label" for="inputEmail">Username</label>
+ <div class="controls">
+ <input type="text" id="inputEmail" placeholder="Username" name="username">
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="inputPassword">Password</label>
+ <div class="controls">
+ <input type="password" id="inputPassword" placeholder="Password" name="password">
+ </div>
+ </div>
+ <div class="control-group">
+ <div class="controls">
+ <label class="checkbox">
+ <input type="checkbox"> Remember me
+ </label>
+ <button type="submit" class="btn">Login</button>
+ </div>
+ </div>
+ </form>
+</div>
+<br>
+<div style="text-align: center">
{% 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 %}
+{% endblock %} \ No newline at end of file
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 c88fa3568..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
index a4443025a..b7cdb7cb5 100644
--- a/module/web/templates/default/settings.html
+++ b/module/web/templates/default/settings.html
@@ -4,201 +4,33 @@
{% 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>
-
+ <script>
+ $(function() {
+ $( "#toptabs" ).tabs({
+ ajaxOptions: {
+ error: function( xhr, status, index, anchor ) {
+ $( anchor.hash ).html(
+ "Couldn't load this tab." );
+ }
+ }
+ });
+ });
+ </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>
+<div id="toptabs">
+ <ul>
+ <li><a href="#tabs-1">{{ _("Admin")}}</a></li>
+ <li><a href="ajax/content1.html">{{ _("Addons")}}</a></li>
+ <li><a href="ajax/content1.html">{{ _("Accounts")}}</a></li>
+ <li><a href="ajax/content1.html">{{ _("User")}}</a></li>
+ </ul>
+ <div id="tabs-1">
+ <p>Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.</p>
+ </div>
+</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 b61fa7149..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/templates/mobile/base.html b/module/web/templates/mobile/base.html
new file mode 100644
index 000000000..95d824730
--- /dev/null
+++ b/module/web/templates/mobile/base.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>{% block title %}pyLoad {{ _("Webinterface") }}{% endblock %}</title>
+ <meta name="description" content="">
+ <meta name="HandheldFriendly" content="True">
+ <meta name="MobileOptimized" content="320">
+ <meta name="viewport" content="width=device-width">
+ <meta http-equiv="cleartype" content="on">
+
+ <link rel="shortcut icon" href="img/touch/apple-touch-icon.png">
+ <link href="static/css/font.css" rel="stylesheet" type="text/css"/>
+ {# TODO: Needs cleanup #}
+ <link rel="stylesheet" href="/static/css/mobile/style.css">
+
+ <script type="text/javascript" data-main="static/js/mobile" src="static/js/libs/require-2.0.6.js"></script>
+ {% block head %}
+ {% endblock %}
+</head>
+<body class="viewport">
+<div id="wrap">
+<header>
+ <div class="logo"></div>
+ <span class="title">pyLoad</span>
+</header>
+<div id="content">
+ <h2>Tech Demo</h2>
+ <h3>In Development</h3>
+</div>
+</div>
+<footer>
+ Tab Bar
+</footer>
+<script>
+ require(['mobile'], function (App) {
+ App.init();
+ });
+</script>
+</body>
+</html>
diff --git a/module/web/templates/mobile/login.html b/module/web/templates/mobile/login.html
new file mode 100644
index 000000000..5a1625f43
--- /dev/null
+++ b/module/web/templates/mobile/login.html
@@ -0,0 +1,5 @@
+{% extends 'mobile/base.html' %}
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+{% block content %}
+<h1>Test test sd</h1>
+{% endblock %} \ No newline at end of file
diff --git a/module/web/utils.py b/module/web/utils.py
index a89c87558..967fc3412 100644
--- a/module/web/utils.py
+++ b/module/web/utils.py
@@ -12,104 +12,80 @@
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/>.
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
@author: RaNaN
"""
+import re
from bottle import request, HTTPError, redirect, ServerAdapter
-from webinterface import env, TEMPLATE
-
-from module.Api import has_permission, PERMS, ROLE
+from webinterface import env, TEMPLATE, PYLOAD
+# TODO: useful but needs a rewrite, too
def render_to_response(name, args={}, proc=[]):
for p in proc:
args.update(p())
-
- t = env.get_template(TEMPLATE + "/" + name)
+ if is_mobile():
+ t = env.get_or_select_template(("mobile/" + name,))
+ else:
+ t = env.get_or_select_template((TEMPLATE + "/" + name, "default/" + 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):
+def set_session(request, user):
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["uid"] = user.uid
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 get_user_api(s):
+ uid = s.get("uid", None)
+ if uid is not None:
+ api = PYLOAD.withUserContext(uid)
+ return api
+ return None
+
+def is_mobile():
+ if request.get_cookie("mobile"):
+ if request.get_cookie("mobile") == "True":
+ return True
+ else:
+ return False
+ mobile_ua = request.headers.get('User-Agent', '').lower()
+ if mobile_ua.find('opera mini') > 0:
+ return True
+ if mobile_ua.find('windows') > 0:
+ return False
+ if request.headers.get('Accept', '').lower().find('application/vnd.wap.xhtml+xml') > 0:
+ return True
+ if re.search('(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|android)', mobile_ua) is not None:
+ return True
+ mobile_ua = mobile_ua[:4]
+ mobile_agents = ['w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac','blaz','brew','cell','cldc','cmd-',
+ 'dang','doco','eric','hipt','inno','ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-',
+ 'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-','newt','noki','palm','pana','pant',
+ 'phil','play','port','prox','qwap','sage','sams','sany','sch-','sec-','send','seri','sgh-','shar','sie-',
+ 'siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-','tosh','tsm-','upg1','upsi','vk-v','voda',
+ 'wap-','wapa','wapi','wapp','wapr','webc','winw','winw','xda ','xda-']
+ if mobile_ua in mobile_agents:
+ return True
+ return 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):
+ api = get_user_api(s)
+ if api is not None:
if perm:
- perms = parse_permissions(s)
- if perm not in perms or not perms[perm]:
+ if api.user.hasPermission(perm):
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return HTTPError(403, "Forbidden")
else:
return redirect("/nopermission")
+ kwargs["api"] = api
return func(*args, **kwargs)
else:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
@@ -122,13 +98,6 @@ def login_required(perm=None):
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
diff --git a/module/web/webinterface.py b/module/web/webinterface.py
index ec8b2e56c..75907a0a8 100644
--- a/module/web/webinterface.py
+++ b/module/web/webinterface.py
@@ -30,7 +30,7 @@ PYLOAD_DIR = abspath(join(PROJECT_DIR, "..", ".."))
sys.path.append(PYLOAD_DIR)
from module import InitHomeDir
-from module.utils import decode, formatSize
+from module.utils import decode, format_size
import bottle
from bottle import run, app
@@ -77,6 +77,7 @@ if not exists(cache):
bcc = FileSystemBytecodeCache(cache, '%s.cache')
loader = PrefixLoader({
"default": FileSystemLoader(join(PROJECT_DIR, "templates", "default")),
+ "mobile": FileSystemLoader(join(PROJECT_DIR, "templates", "mobile")),
'js': FileSystemLoader(join(PROJECT_DIR, 'media', 'js'))
})
@@ -92,7 +93,7 @@ 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["formatsize"] = format_size
env.filters["getitem"] = lambda x, y: x.__getitem__(y)
if PREFIX:
env.filters["url"] = lambda x: x
@@ -121,7 +122,6 @@ if PREFIX:
web = PrefixMiddleware(web, prefix=PREFIX)
import pyload_app
-import json_app
import cnl_app
import api_app
@@ -133,14 +133,14 @@ 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=""):
+def run_threaded(host="0.0.0.0", port="8000", threads=3, cert="", key=""):
from wsgiserver import CherryPyWSGIServer
if cert and key:
CherryPyWSGIServer.ssl_certificate = cert
CherryPyWSGIServer.ssl_private_key = key
- CherryPyWSGIServer.numthreads = theads
+ CherryPyWSGIServer.numthreads = threads
from utils import CherryPyWSGI
diff --git a/pavement.py b/pavement.py
index ac9a6fa1a..7597ca9e0 100644
--- a/pavement.py
+++ b/pavement.py
@@ -3,7 +3,25 @@
from paver.easy import *
from paver.setuputils import setup
-from paver.doctools import cog
+try:
+ from paver.doctools import cog
+except:
+ cog = None
+
+import fnmatch
+
+# patch to let it support list of patterns
+def new_fnmatch(self, pattern):
+ if type(pattern) == list:
+ for p in pattern:
+ if fnmatch.fnmatch(self.name, p):
+ return True
+ return False
+ else:
+ return fnmatch.fnmatch(self.name, pattern)
+
+path.fnmatch = new_fnmatch
+
import sys
import re
@@ -23,13 +41,13 @@ if sys.version_info <= (2, 5):
setup(
name="pyload",
- version="0.4.9",
+ version="0.5.0",
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',
+ license='AGPL v3',
author="pyLoad Team",
author_email="support@pyload.org",
platforms = ('Any',),
@@ -40,7 +58,7 @@ setup(
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,
+ install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker >= 1.6', 'BeautifulSoup>=3.2, <3.3'] + extradeps,
extras_require={
'SSL': ["pyOpenSSL"],
'DLC': ['pycrypto'],
@@ -60,7 +78,7 @@ setup(
"Environment :: Console",
"Environment :: Web Environment",
"Intended Audience :: End Users/Desktop",
- "License :: OSI Approved :: GNU General Public License (GPL)",
+ "License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2"
]
@@ -86,7 +104,7 @@ options(
virtual="virtualenv2",
),
cog=Bunch(
- pattern="*.py",
+ pattern=["*.py", "*.rst"],
)
)
@@ -147,7 +165,7 @@ def get_source(options):
@task
-@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist')
+@needs('clean', 'generate_setup', 'get_source', 'setuptools.command.sdist')
def sdist():
""" Build source code package with distutils """
@@ -160,7 +178,7 @@ def sdist():
def thrift(options):
""" Generate Thrift stubs """
- print "add import for TApplicationException manually as long it is not fixed"
+ print "add import for TApplicationException manually as long as it is not fixed"
outdir = path("module") / "remote" / "thriftbackend"
(outdir / "gen-py").rmtree()
@@ -208,7 +226,6 @@ def generate_locale():
"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")
@@ -242,7 +259,8 @@ def generate_locale():
@task
def tests():
- call(["nosetests2"])
+ """ Run nosetests """
+ call(["tests/nosetests.sh"])
@task
def virtualenv(options):
@@ -263,7 +281,7 @@ def clean_env():
@task
-@needs('generate_setup', 'minilib', 'get_source', 'virtualenv')
+@needs('generate_setup', 'get_source', 'virtualenv')
def env_install():
"""Install pyLoad into the virtualenv"""
venv = options.virtualenv
diff --git a/paver-minilib.zip b/paver-minilib.zip
new file mode 100644
index 000000000..15cb0ecb3
--- /dev/null
+++ b/paver-minilib.zip
Binary files differ
diff --git a/pyLoadCli.py b/pyLoadCli.py
index 079cee19c..cf8fabd1a 100755
--- a/pyLoadCli.py
+++ b/pyLoadCli.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
-#Copyright (C) 2011 RaNaN
+#Copyright (C) 2012 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
@@ -289,14 +289,14 @@ class Cli:
print _("Please use this syntax: add <Package name> <link> <link2> ...")
return
- self.client.addPackage(args[0], args[1:], Destination.Queue)
+ 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)
+ self.client.addPackage(args[0], args[1:], Destination.Collector, "")
elif command == "del_file":
self.client.deleteFiles([int(x) for x in args])
@@ -392,7 +392,7 @@ class RefreshThread(Thread):
def print_help(config):
print
- print "pyLoadCli Copyright (c) 2008-2011 the pyLoad Team"
+ print "pyLoadCli Copyright (c) 2008-2012 the pyLoad Team"
print
print "Usage: [python] pyLoadCli.py [options] [command]"
print
@@ -402,13 +402,13 @@ def print_help(config):
print "<Options>"
print " -i, --interactive", " Start in interactive mode"
print
- print " -u, --username=", " " * 2, "Specify Username"
+ print " -u, --username=", " " * 2, "Specify user name"
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 " -a, --address=", " " * 3, "Use address (current=%s)" % config["addr"]
+ print " -p, --port", " " * 7, "Use 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 " -h, --help", " " * 7, "Display this help text"
print " -c, --commands", " " * 3, "List all available commands"
print
diff --git a/pyLoadCore.py b/pyLoadCore.py
index 35cac4682..99c01dbf7 100755
--- a/pyLoadCore.py
+++ b/pyLoadCore.py
@@ -1,18 +1,15 @@
#!/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.
+ Copyright(c) 2008-2012 pyLoad Team
+ http://www.pyload.org
- 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.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
+ Subjected to the terms and conditions in LICENSE
@author: spoob
@author: sebnapi
@@ -20,59 +17,80 @@
@author: mkaay
@version: v0.4.9
"""
-CURRENT_VERSION = '0.4.9'
+CURRENT_VERSION = '0.4.9.9-dev'
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
+from os import _exit, execl, getcwd, remove, walk, chdir, close
import signal
-import subprocess
import sys
from sys import argv, executable, exit
from time import time, sleep
from traceback import print_exc
+import locale
+locale.locale_alias = locale.windows_locale = {} #save ~100kb ram, no known sideeffects for now
+
+import subprocess
+subprocess.__doc__ = None # the module with the largest doc we are using
+
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.AccountManager import AccountManager
+from module.config.ConfigParser import ConfigParser
+from module.PluginManager import PluginManager
+from module.interaction.EventManager import EventManager
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
+import module.common.pylgettext as gettext
+from module.utils import formatSize, get_console_encoding
+from module.utils.fs import free_space, exists, makedirs, join, chmod
from codecs import getwriter
-enc = get_console_encoding(sys.stdout.encoding)
+# test runner overwrites sys.stdout
+if hasattr(sys.stdout, "encoding"): enc = get_console_encoding(sys.stdout.encoding)
+else: enc = "utf8"
+
+sys._stdout = sys.stdout
sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
# TODO List
# - configurable auth system ldap/mysql
# - cron job like sheduler
+# - plugin stack / multi decrypter
+# - media plugin type
+# - general progress info
+# - content attribute for files / sync status
+# - sync with disk content / file manager / nested packages
+# - sync between pyload cores
+# - new attributes (date|sync status)
+# - embedded packages
+# - would require new/modified link collector concept
+# - pausable links/packages
+# - toggable accounts
+# - interaction manager
+# - improve external scripts
+# - make pyload undestructable to fail plugins -> see ConfigParser first
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.pdb = None
self.arg_links = []
self.pidfile = "pyload.pid"
self.deleteLinks = False # will delete links on startup
@@ -129,7 +147,7 @@ class Core(object):
print pid
exit(0)
else:
- print "false"
+ print "false"
exit(1)
elif option == "--clean":
self.cleanTree()
@@ -144,7 +162,7 @@ class Core(object):
def print_help(self):
print ""
- print "pyLoad v%s 2008-2011 the pyLoad Team" % CURRENT_VERSION
+ print "pyLoad v%s 2008-2012 the pyLoad Team" % CURRENT_VERSION
print ""
if sys.argv[0].endswith(".py"):
print "Usage: python pyLoadCore.py [options]"
@@ -157,15 +175,15 @@ class Core(object):
#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 " -s, --setup", " " * 12, "Run setup assistant"
+ print " --configdir=<dir>", " " * 6, "Run with <dir> as configuration directory"
print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
- print " --changedir", " " * 12, "Change config dir permanently"
- print " --daemon", " " * 15, "Daemonmize after start"
+ print " --changedir", " " * 12, "Change configuration directory permanently"
+ print " --daemon", " " * 15, "Daemonize after startup"
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 " -q, --quit", " " * 13, "Quit a running pyLoad instance"
print " -h, --help", " " * 13, "Display this help screen"
print ""
@@ -188,6 +206,7 @@ class Core(object):
f = open(self.pidfile, "wb")
f.write(str(pid))
f.close()
+ chmod(self.pidfile, 0660)
def deletePidFile(self):
if self.checkPidFile():
@@ -258,15 +277,15 @@ class Core(object):
print join(path, f)
remove(join(path, f))
- def start(self, rpc=True, web=True):
+ def start(self, rpc=True, web=True, tests=False):
""" starts the fun :D """
self.version = CURRENT_VERSION
- if not exists("pyload.conf"):
+ if not exists("pyload.conf") and not tests:
from module.setup import Setup
- print "This is your first start, running configuration assistent now."
+ print "This is your first start, running configuration assistant now."
self.config = ConfigParser()
s = Setup(pypath, self.config)
res = False
@@ -295,11 +314,15 @@ class Core(object):
languages=[self.config['general']['language'],"en"],fallback=True)
translation.install(True)
+ # load again so translations are propagated
+ self.config.loadDefault()
+
self.debug = self.doDebug or self.config['general']['debug_mode']
self.remote &= self.config['remote']['activated']
pid = self.isAlreadyRunning()
- if pid:
+ # don't exit when in test runner
+ if pid and not tests:
print _("pyLoad already running with pid %s") % pid
exit()
@@ -326,8 +349,6 @@ class Core(object):
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:
@@ -339,8 +360,9 @@ class Core(object):
self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION)
self.log.info(_("Using home directory: %s") % getcwd())
-
- self.writePidFile()
+
+ if not tests:
+ self.writePidFile()
#@TODO refractor
@@ -348,24 +370,15 @@ class Core(object):
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)
+ self.captcha = True # checks seems to fail, although tesseract is available
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"))
+ self.eventManager = EventManager(self)
+ self.setupDB()
if self.deleteLinks:
self.log.info(_("All links removed"))
@@ -374,12 +387,11 @@ class Core(object):
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
+ from module.AddonManager import AddonManager
+ from module.interaction.InteractionManager import InteractionManager
+ from module.threads.ThreadManager import ThreadManager
if Api.activated != self.remote:
self.log.warning("Import error: API remote status not correct.")
@@ -390,16 +402,18 @@ class Core(object):
#hell yeah, so many important managers :D
self.pluginManager = PluginManager(self)
- self.pullManager = PullManager(self)
+ self.interactionManager = InteractionManager(self)
self.accountManager = AccountManager(self)
self.threadManager = ThreadManager(self)
- self.captchaManager = CaptchaManager(self)
- self.hookManager = HookManager(self)
+ self.addonManager = AddonManager(self)
self.remoteManager = RemoteManager(self)
self.js = JsEngine()
- self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
+ # enough initialization for test cases
+ if tests: return
+
+ self.log.info(_("Download time: %s") % self.api.isTimeDownload())
if rpc:
self.remoteManager.startBackends()
@@ -407,7 +421,12 @@ class Core(object):
if web:
self.init_webserver()
- spaceLeft = freeSpace(self.config["general"]["download_folder"])
+ dl_folder = self.config["general"]["download_folder"]
+
+ if not exists(dl_folder):
+ makedirs(dl_folder)
+
+ spaceLeft = free_space(dl_folder)
self.log.info(_("Free space: %s") % formatSize(spaceLeft))
@@ -430,15 +449,20 @@ class Core(object):
#self.scheduler.addJob(0, self.accountManager.getAccountInfos)
self.log.info(_("Activating Accounts..."))
- self.accountManager.getAccountInfos()
+ self.accountManager.refreshAllAccounts()
+ #restart failed
+ if self.config["download"]["restart_failed"]:
+ self.log.info(_("Restarting failed downloads..."))
+ self.api.restartFailed()
+
self.threadManager.pause = False
self.running = True
- self.log.info(_("Activating Plugins..."))
- self.hookManager.coreReady()
+ self.addonManager.activateAddons()
self.log.info(_("pyLoad is up and running"))
+ self.eventManager.dispatchEvent("coreReady")
#test api
# from module.common.APIExerciser import startApiExerciser
@@ -447,15 +471,18 @@ class Core(object):
#some memory stats
# from guppy import hpy
# hp=hpy()
+# print hp.heap()
# import objgraph
-# objgraph.show_most_common_types(limit=20)
+# objgraph.show_most_common_types(limit=30)
# import memdebug
# memdebug.start(8002)
+# from meliae import scanner
+# scanner.dump_all_objects(self.path('objs.json'))
locals().clear()
while True:
- sleep(2)
+ sleep(1.5)
if self.do_restart:
self.log.info(_("restarting pyLoad"))
self.restart()
@@ -466,13 +493,17 @@ class Core(object):
_exit(0) #@TODO thrift blocks shutdown
self.threadManager.work()
+ self.interactionManager.work()
self.scheduler.work()
def setupDB(self):
+ from module.database import DatabaseBackend
+ from module.FileManager import FileManager
+
self.db = DatabaseBackend(self) # the backend
self.db.setup()
- self.files = FileHandler(self)
+ self.files = FileManager(self)
self.db.manager = self.files #ugly?
def init_webserver(self):
@@ -484,7 +515,10 @@ class Core(object):
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
+ self.log = logging.getLogger("log") # setable in config
+
+ if not exists(self.config['log']['log_folder']):
+ makedirs(self.config['log']['log_folder'], 0700)
if self.config['log']['file_log']:
if self.config['log']['log_rotate']:
@@ -507,7 +541,7 @@ class Core(object):
h.close()
def check_install(self, check_name, legend, python=True, essential=False):
- """check wether needed tools are installed"""
+ """check whether needed tools are installed"""
try:
if python:
find_module(check_name)
@@ -523,46 +557,6 @@ class Core(object):
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)
@@ -578,22 +572,19 @@ class Core(object):
def shutdown(self):
self.log.info(_("shutting down..."))
+ self.eventManager.dispatchEvent("coreShutdown")
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()
+ self.api.stopAllDownloads()
+ self.addonManager.deactivateAddons()
except:
- if self.debug:
- print_exc()
+ self.print_exc()
self.log.info(_("error while shutting down"))
finally:
@@ -602,11 +593,27 @@ class Core(object):
self.deletePidFile()
+ def shell(self):
+ """ stop and open an ipython shell inplace"""
+ if self.debug:
+ from IPython import embed
+ sys.stdout = sys._stdout
+ embed()
+
+ def breakpoint(self):
+ if self.debug:
+ from IPython.core.debugger import Pdb
+ sys.stdout = sys._stdout
+ if not self.pdb: self.pdb = Pdb()
+ self.pdb.set_trace()
+
+ def print_exc(self):
+ if self.debug:
+ print_exc()
def path(self, *args):
return join(pypath, *args)
-
def deamon():
try:
pid = os.fork()
@@ -658,7 +665,7 @@ def main():
pyload_core.start()
except KeyboardInterrupt:
pyload_core.shutdown()
- pyload_core.log.info(_("killed pyLoad from Terminal"))
+ pyload_core.log.info(_("killed pyLoad from terminal"))
pyload_core.removeLogger()
_exit(1)
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/setup.py b/setup.py
new file mode 100644
index 000000000..d04135803
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,7 @@
+import os
+if os.path.exists("paver-minilib.zip"):
+ import sys
+ sys.path.insert(0, "paver-minilib.zip")
+
+import paver.tasks
+paver.tasks.main()
diff --git a/systemCheck.py b/systemCheck.py
index 60fe0313b..4b3c90753 100644
--- a/systemCheck.py
+++ b/systemCheck.py
@@ -50,7 +50,7 @@ def main():
core_info = []
if sys.version_info > (2, 8):
- core_err.append("Your python version is to new, Please use Python 2.6/2.7")
+ core_err.append("Your python version is too 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")
@@ -64,18 +64,18 @@ def main():
try:
from pycurl import AUTOREFERER
except:
- core_err.append("Your py-curl version is to old, please upgrade!")
+ core_err.append("Your py-curl version is too old, please upgrade!")
try:
import Image
except:
- core_err.append("Please install py-imaging/pil to use Hoster, which uses captchas.")
+ core_err.append("Please install py-imaging/pil to use Hoster, which use 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.")
+ core_err.append("Please install tesseract to use Hoster, which use captchas.")
try:
import OpenSSL
@@ -94,24 +94,6 @@ def main():
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 = []
@@ -120,7 +102,7 @@ def main():
try:
import flup
except:
- web_info.append("Install Flup to use FastCGI or optional webservers.")
+ web_info.append("Install Flup to use FastCGI or optional web servers.")
if web_err:
@@ -128,10 +110,10 @@ def main():
for err in web_err:
print(err)
else:
- print("No Problems detected, Webinterface should work fine.")
+ print("No Problems detected, web interface should work fine.")
if web_info:
- print("\nPossible improvements for webinterface:\n")
+ print("\nPossible improvements for web interface:\n")
for line in web_info:
print(line)
@@ -139,4 +121,5 @@ def main():
if __name__ == "__main__":
main()
- raw_input("Press Enter to Exit.")
+ # comp. with py2 and 3
+ 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/CrypterPluginTester.py b/tests/CrypterPluginTester.py
new file mode 100644
index 000000000..67e5ddebc
--- /dev/null
+++ b/tests/CrypterPluginTester.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+from os.path import dirname, join
+from nose.tools import nottest
+
+from logging import log, DEBUG
+
+from helper.Stubs import Core
+from helper.PluginTester import PluginTester
+
+from module.plugins.Base import Fail
+from module.utils import accumulate, to_int
+
+class CrypterPluginTester(PluginTester):
+ @nottest
+ def test_plugin(self, name, url, flag):
+
+ print "%s: %s" % (name, url.encode("utf8"))
+ log(DEBUG, "%s: %s", name, url.encode("utf8"))
+
+ plugin = self.core.pluginManager.getPluginClass(name)
+ p = plugin(self.core, None, "")
+ self.thread.plugin = p
+
+ try:
+ result = p._decrypt([url])
+
+ if to_int(flag):
+ assert to_int(flag) == len(result)
+
+ except Exception, e:
+ if isinstance(e, Fail) and flag == "fail":
+ pass
+ else:
+ raise
+
+
+# setup methods
+
+c = Core()
+
+f = open(join(dirname(__file__), "crypterlinks.txt"))
+links = [x.strip() for x in f.readlines()]
+urls = []
+flags = {}
+
+for l in links:
+ if not l or l.startswith("#"): continue
+ if l.startswith("http"):
+ if "||" in l:
+ l, flag = l.split("||")
+ flags[l] = flag
+
+ urls.append(l)
+
+h, crypter = c.pluginManager.parseUrls(urls)
+plugins = accumulate(crypter)
+for plugin, urls in plugins.iteritems():
+
+ def meta_class(plugin):
+ class _testerClass(CrypterPluginTester):
+ pass
+ _testerClass.__name__ = plugin
+ return _testerClass
+
+ _testerClass = meta_class(plugin)
+
+ for i, url in enumerate(urls):
+ def meta(plugin, url, flag, sig):
+ def _test(self):
+ self.test_plugin(plugin, url, flag)
+
+ _test.func_name = sig
+ return _test
+
+ sig = "test_LINK%d" % i
+ setattr(_testerClass, sig, meta(plugin, url, flags.get(url, None), sig))
+ print url
+
+ locals()[plugin] = _testerClass
+ del _testerClass
diff --git a/tests/HosterPluginTester.py b/tests/HosterPluginTester.py
new file mode 100644
index 000000000..627494a3f
--- /dev/null
+++ b/tests/HosterPluginTester.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+
+from os.path import dirname
+from logging import log, DEBUG
+from hashlib import md5
+from time import time
+from shutil import move
+import codecs
+
+from nose.tools import nottest
+
+from helper.Stubs import Core
+from helper.PluginTester import PluginTester
+
+from module.datatypes.PyFile import PyFile
+from module.plugins.Base import Fail
+from module.utils import accumulate
+from module.utils.fs import save_join, join, exists, listdir, remove, stat
+
+DL_DIR = join("Downloads", "tmp")
+
+class HosterPluginTester(PluginTester):
+ files = {}
+
+ def setUp(self):
+ PluginTester.setUp(self)
+ for f in self.files:
+ if exists(save_join(DL_DIR, f)): remove(save_join(DL_DIR, f))
+
+ # folder for reports
+ report = join("tmp", self.__class__.__name__)
+ if exists(report):
+ for f in listdir(report):
+ remove(join(report, f))
+
+
+ @nottest
+ def test_plugin(self, name, url, flag):
+ # Print to stdout to see whats going on
+ print "%s: %s, %s" % (name, url.encode("utf8"), flag)
+ log(DEBUG, "%s: %s, %s", name, url.encode("utf8"), flag)
+
+ # url and plugin should be only important thing
+ pyfile = PyFile(self.core, -1, url, url, 0, 0, "", name, 0, 0)
+ pyfile.initPlugin()
+
+ self.thread.pyfile = pyfile
+ self.thread.plugin = pyfile.plugin
+
+ try:
+ a = time()
+ pyfile.plugin.preprocessing(self.thread)
+
+ log(DEBUG, "downloading took %ds" % (time() - a))
+ log(DEBUG, "size %d kb" % (pyfile.size / 1024))
+
+ if flag == "offline":
+ raise Exception("No offline Exception raised.")
+
+ if pyfile.name not in self.files:
+ raise Exception("Filename %s not recognized." % pyfile.name)
+
+ if not exists(save_join(DL_DIR, pyfile.name)):
+ raise Exception("File %s does not exists." % pyfile.name)
+
+ hash = md5()
+ f = open(save_join(DL_DIR, pyfile.name), "rb")
+ while True:
+ buf = f.read(4096)
+ if not buf: break
+ hash.update(buf)
+ f.close()
+
+ if hash.hexdigest() != self.files[pyfile.name]:
+ log(DEBUG, "Hash is %s" % hash.hexdigest())
+
+ size = stat(f.name).st_size
+ if size < 1024 * 1024 * 10: # 10MB
+ # Copy for debug report
+ log(DEBUG, "Downloaded file copied to report")
+ move(f.name, join("tmp", plugin, f.name))
+
+ raise Exception("Hash does not match.")
+
+
+
+ except Exception, e:
+ if isinstance(e, Fail) and flag == "fail":
+ pass
+ elif isinstance(e, Fail) and flag == "offline" and e.message == "offline":
+ pass
+ else:
+ raise
+
+
+# setup methods
+
+c = Core()
+
+# decode everything as unicode
+f = codecs.open(join(dirname(__file__), "hosterlinks.txt"), "r", "utf_8")
+links = [x.strip() for x in f.readlines()]
+urls = []
+flags = {}
+
+for l in links:
+ if not l or l.startswith("#"): continue
+ if l.startswith("http"):
+ if "||" in l:
+ l, flag = l.split("||")
+ flags[l] = str(flag.strip())
+ urls.append(l)
+
+ elif len(l.rsplit(" ", 1)) == 2:
+ name, hash = l.rsplit(" ", 1)
+ HosterPluginTester.files[name] = str(hash)
+
+hoster, c = c.pluginManager.parseUrls(urls)
+
+plugins = accumulate(hoster)
+for plugin, urls in plugins.iteritems():
+ # closure functions to retain local scope
+ def meta_class(plugin):
+ class _testerClass(HosterPluginTester):
+ pass
+ _testerClass.__name__ = plugin
+ return _testerClass
+
+ _testerClass = meta_class(plugin)
+
+ for i, url in enumerate(urls):
+ def meta(__plugin, url, flag, sig):
+ def _test(self):
+ self.test_plugin(__plugin, url, flag)
+
+ _test.func_name = sig
+ return _test
+
+ tmp_flag = flags.get(url, None)
+ if flags.get(url, None):
+ sig = "test_LINK%d_%s" % (i, tmp_flag)
+ else:
+ sig = "test_LINK%d" % i
+
+ # set test method
+ setattr(_testerClass, sig, meta(plugin, url, tmp_flag, sig))
+
+
+ #register class
+ locals()[plugin] = _testerClass
+ # remove from locals, or tested twice
+ del _testerClass
diff --git a/tests/clonedigger.sh b/tests/clonedigger.sh
new file mode 100755
index 000000000..93c1cb323
--- /dev/null
+++ b/tests/clonedigger.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+clonedigger -o cpd.xml --cpd-output --fast --ignore-dir=lib --ignore-dir=remote module
diff --git a/tests/config/db.version b/tests/config/db.version
new file mode 100644
index 000000000..bf0d87ab1
--- /dev/null
+++ b/tests/config/db.version
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/tests/config/plugin.conf b/tests/config/plugin.conf
new file mode 100644
index 000000000..5e7ee3858
--- /dev/null
+++ b/tests/config/plugin.conf
@@ -0,0 +1,138 @@
+version: 2
+
+[MultiuploadCom]
+preferedHoster = multiupload
+ignoredHoster =
+
+[SerienjunkiesOrg]
+preferredHoster = RapidshareCom,UploadedTo,NetloadIn,FilefactoryCom,FreakshareNet,FilebaseTo,MegauploadCom,HotfileCom,DepositfilesCom,EasyshareCom,KickloadCom
+changeName = True
+
+[EmbeduploadCom]
+preferedHoster = embedupload
+ignoredHoster =
+
+[MultiloadCz]
+usedHoster =
+ignoredHoster =
+
+[WiiReloadedOrg]
+changeName = True
+
+[Xdcc]
+nick = pyload
+ident = pyloadident
+realname = pyloadreal
+
+[UlozTo]
+reuseCaptcha = True
+captchaUser =
+captchaNb =
+
+[YoutubeCom]
+quality = hd
+fmt = 0
+.mp4 = True
+.flv = True
+.webm = False
+.3gp = False
+
+[RapidshareCom]
+server = None
+
+[VeehdCom]
+filename_spaces = False
+replacement_char = _
+
+[RealdebridCom]
+https = False
+
+[ClickAndLoad]
+activated = True
+extern = False
+
+[ExtractArchive]
+activated = True
+fullpath = True
+overwrite = True
+passwordfile = unrar_passwords.txt
+deletearchive = False
+subfolder = False
+destination =
+queue = True
+renice = 0
+
+[CaptchaTrader]
+activated = True
+username =
+force = False
+passkey =
+
+[MergeFiles]
+activated = False
+
+[IRCInterface]
+activated = False
+host = Enter your server here!
+port = 6667
+ident = pyload-irc
+realname = pyload-irc
+nick = pyLoad-IRC
+owner = Enter your nick here!
+info_file = False
+info_pack = True
+captcha = True
+
+[Ev0InFetcher]
+activated = False
+interval = 10
+queue = False
+shows =
+quality = xvid
+hoster = NetloadIn,RapidshareCom,MegauploadCom,HotfileCom
+
+[EasybytezCom]
+activated = False
+includeHoster =
+excludeHoster =
+
+[XMPPInterface]
+activated = False
+jid = user@exmaple-jabber-server.org
+pw =
+tls = False
+owners = me@icq-gateway.org;some@msn-gateway.org
+info_file = False
+info_pack = True
+captcha = True
+
+[RehostTo]
+activated = False
+
+[MultiHoster]
+activated = True
+
+[MultiHome]
+activated = False
+interfaces = None
+
+[MultishareCz]
+activated = False
+includeHoster =
+excludeHoster = rapidshare.com|uloz.to
+
+[HotFolder]
+activated = False
+folder = container
+watch_file = False
+keep = True
+file = links.txt
+
+[ExternalScripts]
+activated = True
+
+[UpdateManager]
+activated = True
+interval = 360
+debug = False
+
diff --git a/tests/config/pyload.conf.org b/tests/config/pyload.conf.org
new file mode 100644
index 000000000..7fb1c8c87
--- /dev/null
+++ b/tests/config/pyload.conf.org
@@ -0,0 +1,75 @@
+version: 2
+
+[remote]
+nolocalauth = False
+activated = True
+port = 7227
+listenaddr = 127.0.0.1
+
+[log]
+log_size = 100
+log_folder = Logs
+file_log = False
+log_count = 5
+log_rotate = True
+
+[permission]
+group = users
+change_dl = False
+change_file = False
+user = user
+file = 0644
+change_group = False
+folder = 0755
+change_user = False
+
+[general]
+language = en
+download_folder = Downloads
+checksum = False
+folder_per_package = True
+debug_mode = True
+min_free_space = 200
+renice = 0
+
+[ssl]
+cert = ssl.crt
+activated = False
+key = ssl.key
+
+[webinterface]
+template = default
+activated = True
+prefix =
+server = builtin
+host = 127.0.0.1
+https = False
+port = 8001
+
+[proxy]
+username =
+proxy = False
+address = localhost
+password =
+type = http
+port = 7070
+
+[reconnect]
+endTime = 0:00
+activated = False
+method = ./reconnect.sh
+startTime = 0:00
+
+[download]
+max_downloads = 3
+limit_speed = False
+interface =
+skip_existing = False
+max_speed = -1
+ipv6 = False
+chunks = 3
+
+[downloadTime]
+start = 0:00
+end = 0:00
+
diff --git a/tests/config/pyload.db.org b/tests/config/pyload.db.org
new file mode 100644
index 000000000..d340531c5
--- /dev/null
+++ b/tests/config/pyload.db.org
Binary files differ
diff --git a/tests/crypterlinks.txt b/tests/crypterlinks.txt
new file mode 100644
index 000000000..4ff651888
--- /dev/null
+++ b/tests/crypterlinks.txt
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+# Crypter links, append ||fail or ||#N to mark error or number of expected results (single links+packages)
+
+http://www.filesonic.com/folder/19906605||2
diff --git a/tests/helper/BenchmarkTest.py b/tests/helper/BenchmarkTest.py
new file mode 100644
index 000000000..d28c52959
--- /dev/null
+++ b/tests/helper/BenchmarkTest.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+
+class BenchmarkTest:
+
+ bench = []
+ results = {}
+
+ @classmethod
+ def timestamp(cls, name, a):
+ t = time()
+ r = cls.results.get(name, [])
+ r.append((t-a) * 1000)
+ cls.results[name] = r
+
+ @classmethod
+ def benchmark(cls, n=1):
+
+ print "Benchmarking %s" % cls.__name__
+ print
+
+ for i in range(n):
+ cls.collect_results()
+
+ if "setUpClass" in cls.results:
+ cls.bench.insert(0, "setUpClass")
+
+ if "tearDownClass" in cls.results:
+ cls.bench.append("tearDownClass")
+
+ length = str(max([len(k) for k in cls.bench]) + 1)
+ total = 0
+
+ for k in cls.bench:
+ v = cls.results[k]
+
+ if len(v) > 1:
+ print ("%" + length +"s: %s | average: %.2f ms") % (k, ", ".join(["%.2f" % x for x in v]), sum(v)/len(v))
+ total += sum(v)/len(v)
+ else:
+ print ("%" + length +"s: %.2f ms") % (k, v[0])
+ total += v[0]
+
+ print "\ntotal: %.2f ms" % total
+
+
+ @classmethod
+ def collect_results(cls):
+ if hasattr(cls, "setUpClass"):
+ a = time()
+ cls.setUpClass()
+ cls.timestamp("setUpClass", a)
+
+ obj = cls()
+
+ for f in cls.bench:
+ a = time()
+ getattr(obj, "test_" + f)()
+ cls.timestamp(f, a)
+
+ if hasattr(cls, "tearDownClass"):
+ a = time()
+ cls.tearDownClass()
+ cls.timestamp("tearDownClass", a) \ No newline at end of file
diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py
new file mode 100644
index 000000000..ef61385be
--- /dev/null
+++ b/tests/helper/PluginTester.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+from unittest import TestCase
+from os import makedirs, remove
+from os.path import exists, join, expanduser
+from shutil import move
+from sys import exc_clear, exc_info
+from logging import log, DEBUG
+from time import sleep, time
+from random import randint
+from glob import glob
+
+from pycurl import LOW_SPEED_TIME, FORM_FILE
+from json import loads
+
+from Stubs import Thread, Core, noop
+
+from module.network.RequestFactory import getRequest, getURL
+from module.plugins.Hoster import Hoster, Abort, Fail
+
+def _wait(self):
+ """ waits the time previously set """
+ self.waiting = True
+
+ waittime = self.pyfile.waitUntil - time()
+ log(DEBUG, "waiting %ss" % waittime)
+
+ if self.wantReconnect and waittime > 300:
+ raise Fail("Would wait for reconnect %ss" % waittime)
+ elif waittime > 300:
+ raise Fail("Would wait %ss" % waittime)
+
+ while self.pyfile.waitUntil > time():
+ sleep(1)
+ if self.pyfile.abort: raise Abort
+
+ self.waiting = False
+ self.pyfile.setStatus("starting")
+
+Hoster.wait = _wait
+
+
+def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
+ result_type='textual'):
+ 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()
+
+ Ocr = self.core.pluginManager.loadClass("captcha", self.__name__)
+
+ if Ocr:
+ log(DEBUG, "Using tesseract for captcha")
+ sleep(randint(3000, 5000) / 1000.0)
+ if self.pyfile.abort: raise Abort
+
+ ocr = Ocr()
+ result = ocr.get_captcha(temp_file.name)
+ else:
+ log(DEBUG, "Using ct for captcha")
+ # put username and passkey into two lines in ct.conf
+ conf = join(expanduser("~"), "ct.conf")
+ if not exists(conf): raise Exception("CaptchaTrader config %s not found." % conf)
+ f = open(conf, "rb")
+ req = getRequest()
+
+ #raise timeout threshold
+ req.c.setopt(LOW_SPEED_TIME, 80)
+
+ try:
+ json = req.load("http://captchatrader.com/api/submit",
+ post={"api_key": "9f65e7f381c3af2b076ea680ae96b0b7",
+ "username": f.readline().strip(),
+ "password": f.readline().strip(),
+ "value": (FORM_FILE, temp_file.name),
+ "type": "file"}, multipart=True)
+ finally:
+ f.close()
+ req.close()
+
+ response = loads(json)
+ log(DEBUG, str(response))
+ result = response[1]
+
+ self.cTask = response[0]
+
+ return result
+
+Hoster.decryptCaptcha = decryptCaptcha
+
+
+def respond(ticket, value):
+ conf = join(expanduser("~"), "ct.conf")
+ f = open(conf, "rb")
+ try:
+ getURL("http://captchatrader.com/api/respond",
+ post={"is_correct": value,
+ "username": f.readline().strip(),
+ "password": f.readline().strip(),
+ "ticket": ticket})
+ except Exception, e :
+ print "CT Exception:", e
+ log(DEBUG, str(e))
+ finally:
+ f.close()
+
+
+
+def invalidCaptcha(self):
+ log(DEBUG, "Captcha invalid")
+ if self.cTask:
+ respond(self.cTask, 0)
+
+Hoster.invalidCaptcha = invalidCaptcha
+
+def correctCaptcha(self):
+ log(DEBUG, "Captcha correct")
+ if self.cTask:
+ respond(self.cTask, 1)
+
+Hoster.correctCaptcha = correctCaptcha
+
+Hoster.checkForSameFiles = noop
+
+class PluginTester(TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.core = Core()
+ name = "%s.%s" % (cls.__module__, cls.__name__)
+ for f in glob(join(name, "debug_*")):
+ remove(f)
+
+ # Copy debug report to attachment dir for jenkins
+ @classmethod
+ def tearDownClass(cls):
+ name = "%s.%s" % (cls.__module__, cls.__name__)
+ if not exists(name): makedirs(name)
+ for f in glob("debug_*"):
+ move(f, join(name, f))
+
+ def setUp(self):
+ self.thread = Thread(self.core)
+ exc_clear()
+
+ def tearDown(self):
+ exc = exc_info()
+ if exc != (None, None, None):
+ debug = self.thread.writeDebugReport()
+ log(DEBUG, debug)
diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py
new file mode 100644
index 000000000..5c44cfb58
--- /dev/null
+++ b/tests/helper/Stubs.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+
+import sys
+from os.path import abspath, dirname, join
+from time import strftime
+from traceback import format_exc
+
+sys.path.append(abspath(join(dirname(__file__), "..", "..", "module", "lib")))
+sys.path.append(abspath(join(dirname(__file__), "..", "..")))
+
+import __builtin__
+
+from module.datatypes.PyPackage import PyPackage
+from module.threads.BaseThread import BaseThread
+from module.config.ConfigParser import ConfigParser
+from module.network.RequestFactory import RequestFactory
+from module.PluginManager import PluginManager
+from module.common.JsEngine import JsEngine
+
+from logging import log, DEBUG, INFO, WARN, ERROR
+
+
+# Do nothing
+def noop(*args, **kwargs):
+ pass
+
+ConfigParser.save = noop
+
+class LogStub:
+ def debug(self, *args):
+ log(DEBUG, *args)
+
+ def info(self, *args):
+ log(INFO, *args)
+
+ def error(self, *args):
+ log(ERROR, *args)
+
+ def warning(self, *args):
+ log(WARN, *args)
+
+
+class NoLog:
+ def debug(self, *args):
+ pass
+
+ def info(self, *args):
+ pass
+
+ def error(self, *args):
+ log(ERROR, *args)
+
+ def warning(self, *args):
+ log(WARN, *args)
+
+
+class Core:
+ def __init__(self):
+ self.log = NoLog()
+
+ self.api = self.core = self
+ self.threadManager = self
+ self.debug = True
+ self.captcha = True
+ self.config = ConfigParser()
+ self.pluginManager = PluginManager(self)
+ self.requestFactory = RequestFactory(self)
+ __builtin__.pyreq = self.requestFactory
+ self.accountManager = AccountManager()
+ self.addonManager = AddonManager()
+ self.eventManager = self.interActionManager = NoopClass()
+ self.js = JsEngine()
+ self.cache = {}
+ self.packageCache = {}
+
+ self.log = LogStub()
+
+ def getServerVersion(self):
+ return "TEST_RUNNER on %s" % strftime("%d %h %Y")
+
+ def path(self, path):
+ return path
+
+ def updateLink(self, *args):
+ pass
+
+ def updatePackage(self, *args):
+ pass
+
+ def processingIds(self, *args):
+ return []
+
+ def getPackage(self, id):
+ return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0)
+
+ def print_exc(self):
+ log(ERROR, format_exc())
+
+
+class NoopClass:
+ def __getattr__(self, item):
+ return noop
+
+class AddonManager(NoopClass):
+ def activePlugins(self):
+ return []
+
+class AccountManager:
+
+ def getAccountForPlugin(self, name):
+ return None
+
+class Thread(BaseThread):
+ def __init__(self, core):
+ BaseThread.__init__(self, core)
+ self.plugin = None
+
+
+ def writeDebugReport(self):
+ if hasattr(self, "pyfile"):
+ dump = BaseThread.writeDebugReport(self, self.plugin.__name__, pyfile=self.pyfile)
+ else:
+ dump = BaseThread.writeDebugReport(self, self.plugin.__name__, plugin=self.plugin)
+
+ return dump
+
+__builtin__._ = lambda x: x
+__builtin__.pypath = abspath(join(dirname(__file__), "..", ".."))
+__builtin__.addonManager = AddonManager()
+__builtin__.pyreq = None
diff --git a/tests/helper/__init__.py b/tests/helper/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/helper/__init__.py
diff --git a/tests/hosterlinks.txt b/tests/hosterlinks.txt
new file mode 100755
index 000000000..70dcc44f6
--- /dev/null
+++ b/tests/hosterlinks.txt
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+# Valid files, with md5 hash
+# Please only use files around 5-15 MB and with explicit permission for redistribution
+
+http://download.pyload.org/random.bin
+random.bin d76505d0869f9f928a17d42d66326307
+Mořská želva ( Зелёная черепаха .&+ 綠蠵龜 _@- Đồi mồi dứa ).tar 932212256dc0b0a1e71c0944eef633a4
+Mořská_želva_(_Зелёная_черепаха_.&+_綠蠵龜__@-_Đồi_mồi_dứa_).tar 932212256dc0b0a1e71c0944eef633a4
+
+# Hoster links, append ||offline or ||fail to mark your expectation
+
+http://netload.in/datei9XirAJZs79/random.bin.htm
+http://rapidshare.com/files/445996776/random.bin
+http://hotfile.com/dl/101569859/2e01f04/random.bin.html
+http://www.megaupload.com/?d=1JZLOP3B
+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/
+
+
+http://ul.to/file/o41isx||offline
+http://www.4shared.com/file/-O5CBhQV/random.html||offline
+http://www.4shared.com/file/-O5CBhQV/random.html||offline
+http://www.fileserve.com/file/MxjZXjX||offline
+http://www.share-online.biz/download.php?id=PTCOX1GL6XAH||offline
+http://dl.free.fr/d4aL5dyXY||offline
+http://files.mail.ru/32EW66||offline
+http://www.shragle.com/files/f899389b/random.bin||offline
+
+
+# Hoster links with fancy unicode filenames:
+http://vs3iaw.1fichier.com/fr/
+http://www.4shared.com/file/rQltf2Fr/Mosk_elva___Зелная_черепаха___.html
+http://bezvadata.cz/stahnout/99273_morska-zelva-.-d-i-m-i-d-a-.tar
+http://www.crocko.com/A524453DA89841B4BFC4FB9125D6F186/
+http://czshare.com/2483034/zelva
+http://www.easybytez.com/etvhltkg0d05
+http://www.filejungle.com/f/qX5fxT/
+http://fp.io/43798f2b/
+http://www.filesonic.com/file/yU2cU6s
+http://www.fshare.vn/file/A7H8LSTP7Z/
+http://ifile.it/muwgivz
+http://letitbit.net/download/67793.60a7d3745791db7271a6e6c92cfe/Mořská_želva_(_Зелёная_черепаха_.___綠蠵龜___-_Đồi_mồi_dứa_).tar.html
+http://www.mediafire.com/file/n09th58z1x5r585
+http://www.quickshare.cz/stahnout-soubor/676150:morska-zelva----_-oi-moi-dua-tar_6MB
+http://www.uloz.to/12553820/morska-zelva-oi-moi-dua-tar
+http://www.wupload.com/file/2642593407/ \ No newline at end of file
diff --git a/tests/nosetests.sh b/tests/nosetests.sh
new file mode 100755
index 000000000..c68861b90
--- /dev/null
+++ b/tests/nosetests.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+NS=nosetests
+which nosetests2 > /dev/null && NS=nosetests2
+$NS tests/ --with-coverage --with-xunit --cover-package=module --cover-erase
+coverage xml
diff --git a/tests/plugin_tests.sh b/tests/plugin_tests.sh
new file mode 100755
index 000000000..be06c0dc5
--- /dev/null
+++ b/tests/plugin_tests.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+NS=nosetests
+which nosetests2 > /dev/null && NS=nosetests2
+# must be executed within tests dir
+cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+$NS HosterPluginTester.py CrypterPluginTester.py -s --with-xunit --with-coverage --cover-erase --cover-package=module.plugins --with-id
+coverage xml
diff --git a/tests/pyflakes.sh b/tests/pyflakes.sh
new file mode 100755
index 000000000..0c7e03891
--- /dev/null
+++ b/tests/pyflakes.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+find -name '*.py' |egrep -v '^.(/tests/|/module/lib)'|xargs pyflakes > pyflakes.log || :
+# Filter warnings and strip ./ from path
+cat pyflakes.log | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' | grep -i -E -v "'_'|pypath|webinterface|pyreq|hookmanager" > pyflakes.txt
+sed -i 's/^.\///g' pyflakes.txt
diff --git a/tests/quit_pyload.sh b/tests/quit_pyload.sh
new file mode 100755
index 000000000..e466bcb31
--- /dev/null
+++ b/tests/quit_pyload.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+PYTHON=python
+which python2 > /dev/null && PYTHON=python2
+$PYTHON pyLoadCore.py --configdir=tests/config --quit
+if [ -d userplugins ]; then
+ rm -r userplugins
+fi \ No newline at end of file
diff --git a/tests/run_pyload.sh b/tests/run_pyload.sh
new file mode 100755
index 000000000..66498cd10
--- /dev/null
+++ b/tests/run_pyload.sh
@@ -0,0 +1,17 @@
+#/usr/bin/env bash
+cp tests/config/pyload.db.org tests/config/pyload.db
+cp tests/config/pyload.conf.org tests/config/pyload.conf
+
+PYTHON=python
+which python2 > /dev/null && PYTHON=python2
+
+touch pyload.out
+$PYTHON pyLoadCore.py -d --configdir=tests/config > pyload.out 2> pyload.err &
+
+for i in {1..30}; do
+ grep 8001 pyload.out > /dev/null && echo "pyLoad started" && break
+ sleep 1
+done
+
+echo "pyLoad start script finished"
+
diff --git a/tests/sloccount.sh b/tests/sloccount.sh
new file mode 100755
index 000000000..0dab4164e
--- /dev/null
+++ b/tests/sloccount.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+sloccount --duplicates --wide --details module > sloccount.sc
diff --git a/tests/test_api.py b/tests/test_api.py
index f8901f731..0171b46bb 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,20 +1,18 @@
-# -*- coding: utf-8 -*-
-from module.common import APIExerciser
-from nose.tools import nottest
+from unittest import TestCase
+from pyLoadCore import Core
+from module.common.APIExerciser import APIExerciser
-class TestApi:
+class TestApi(TestCase):
- def __init__(self):
- self.api = APIExerciser.APIExerciser(None, True, "TestUser", "pwhere")
+ @classmethod
+ def setUpClass(cls):
+ cls.core = Core()
+ cls.core.start(False, False, True)
- 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):
+ api = APIExerciser(self.core)
- for i in range(0, 100):
- self.api.testAPI()
+ for i in range(2000):
+ api.testAPI()
diff --git a/tests/test_json.py b/tests/test_backends.py
index ff56e8f5a..71ccedd2f 100644
--- a/tests/test_json.py
+++ b/tests/test_backends.py
@@ -1,14 +1,30 @@
# -*- coding: utf-8 -*-
+
from urllib import urlencode
from urllib2 import urlopen, HTTPError
from json import loads
from logging import log
+from module.common import APIExerciser
+
url = "http://localhost:8001/api/%s"
-class TestJson:
+class TestBackends():
+
+ def setUp(self):
+ u = urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "sometestpw"}))
+ self.key = loads(u.read())
+ assert self.key is not False
+
+ def test_random(self):
+ api = APIExerciser.APIExerciser(None, True, "TestUser", "sometestpw")
+
+ assert api.api.login("crapp", "wrong pw") is False
+
+ for i in range(0, 1000):
+ api.testAPI()
def call(self, name, post=None):
if not post: post = {}
@@ -16,11 +32,6 @@ class TestJson:
u = urlopen(url % name, data=urlencode(post))
return loads(u.read())
- def setUp(self):
- u = urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "pwhere"}))
- self.key = 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
@@ -45,4 +56,4 @@ class TestJson:
except HTTPError, e:
assert e.code == 404
else:
- assert False \ No newline at end of file
+ assert False
diff --git a/tests/test_configparser.py b/tests/test_configparser.py
new file mode 100644
index 000000000..d797c7912
--- /dev/null
+++ b/tests/test_configparser.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from collections import defaultdict
+from helper.Stubs import Core
+
+from module.database import DatabaseBackend
+from module.config.ConfigParser import ConfigParser
+
+# TODO
+class TestConfigParser():
+
+ @classmethod
+ def setUpClass(cls):
+ cls.db = DatabaseBackend(Core())
+ cls.db.manager = cls.db.core
+ cls.db.manager.statusMsg = defaultdict(lambda: "statusmsg")
+ cls.config = ConfigParser()
+ cls.db.setup()
+ cls.db.clearAllConfigs()
+
+
+ def test_db(self):
+
+ self.db.saveConfig("plugin", "some value", 0)
+ self.db.saveConfig("plugin", "some other value", 1)
+
+ assert self.db.loadConfig("plugin", 0) == "some value"
+ assert self.db.loadConfig("plugin", 1) == "some other value"
+
+ d = self.db.loadAllConfigs()
+ assert d[0]["plugin"] == "some value"
+
+ self.db.deleteConfig("plugin")
+
+ assert not self.db.loadAllConfigs()
+
+
+ def test_dict(self):
+
+ assert self.config["general"]["language"]
+ self.config["general"]["language"] = "de"
+ assert self.config["general"]["language"] == "de"
+
+ def test_config(self):
+ pass
+
+ def test_userconfig(self):
+ pass \ No newline at end of file
diff --git a/tests/test_database.py b/tests/test_database.py
new file mode 100644
index 000000000..b7408e213
--- /dev/null
+++ b/tests/test_database.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+
+from collections import defaultdict
+
+from helper.Stubs import Core
+from helper.BenchmarkTest import BenchmarkTest
+
+from module.database import DatabaseBackend
+
+# disable asyncronous queries
+DatabaseBackend.async = DatabaseBackend.queue
+
+from random import choice
+
+class TestDatabase(BenchmarkTest):
+ bench = ["insert", "insert_links", "insert_many", "get_packages",
+ "get_files", "get_files_queued", "get_package_childs", "get_package_files",
+ "get_package_data", "get_file_data", "find_files", "collector", "purge"]
+ pids = None
+ fids = None
+ owner = 123
+ pstatus = 0
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pids = [-1]
+ cls.fids = []
+
+ cls.db = DatabaseBackend(Core())
+ cls.db.manager = cls.db.core
+ cls.db.manager.statusMsg = defaultdict(lambda: "statusmsg")
+
+ cls.db.setup()
+ cls.db.purgeAll()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.db.purgeAll()
+ cls.db.shutdown()
+
+ # benchmarker ignore setup
+ def setUp(self):
+ self.db.purgeAll()
+ self.pids = [-1]
+ self.fids = []
+
+ self.test_insert(20)
+ self.test_insert_many()
+ self.fids = self.db.getAllFiles().keys()
+
+
+ def test_insert(self, n=200):
+ for i in range(n):
+ pid = self.db.addPackage("name", "folder", choice(self.pids), "password", "site", "comment", self.pstatus,
+ self.owner)
+ self.pids.append(pid)
+
+ def test_insert_links(self):
+ for i in range(10000):
+ fid = self.db.addLink("url %s" % i, "name", "plugin", choice(self.pids), self.owner)
+ self.fids.append(fid)
+
+ def test_insert_many(self):
+ for pid in self.pids:
+ self.db.addLinks([("url %s" % i, "plugin") for i in range(50)], pid, self.owner)
+
+ def test_get_packages(self):
+ packs = self.db.getAllPackages()
+ n = len(packs)
+ assert n == len(self.pids) - 1
+
+ print "Fetched %d packages" % n
+ self.assert_pack(choice(packs.values()))
+
+ def test_get_files(self):
+ files = self.db.getAllFiles()
+ n = len(files)
+ assert n >= len(self.pids)
+
+ print "Fetched %d files" % n
+ self.assert_file(choice(files.values()))
+
+ def test_get_files_queued(self):
+ files = self.db.getAllFiles(unfinished=True)
+ print "Fetched %d files queued" % len(files)
+
+ def test_delete(self):
+ pid = choice(self.pids)
+ self.db.deletePackage(pid)
+ self.pids.remove(pid)
+
+ def test_get_package_childs(self):
+ pid = choice(self.pids)
+ packs = self.db.getAllPackages(root=pid)
+
+ print "Package %d has %d packages" % (pid, len(packs))
+ self.assert_pack(choice(packs.values()))
+
+ def test_get_package_files(self):
+ pid = choice(self.pids)
+ files = self.db.getAllFiles(package=pid)
+
+ print "Package %d has %d files" % (pid, len(files))
+ self.assert_file(choice(files.values()))
+
+ def test_get_package_data(self, stats=False):
+ pid = choice(self.pids)
+ p = self.db.getPackageInfo(pid, stats)
+ self.assert_pack(p)
+ # test again with stat
+ if not stats:
+ self.test_get_package_data(True)
+
+ def test_get_file_data(self):
+ fid = choice(self.fids)
+ f = self.db.getFileInfo(fid)
+ self.assert_file(f)
+
+ def test_find_files(self):
+ files = self.db.getAllFiles(search="1")
+ print "Found %s files" % len(files)
+ f = choice(files.values())
+
+ assert "1" in f.name
+
+ def test_collector(self):
+ self.db.saveCollector(0, "data")
+ assert self.db.retrieveCollector(0) == "data"
+ self.db.deleteCollector(0)
+
+ def test_purge(self):
+ self.db.purgeLinks()
+
+
+ def test_user_context(self):
+ self.db.purgeAll()
+
+ p1 = self.db.addPackage("name", "folder", 0, "password", "site", "comment", self.pstatus, 0)
+ self.db.addLink("url", "name", "plugin", p1, 0)
+ p2 = self.db.addPackage("name", "folder", 0, "password", "site", "comment", self.pstatus, 1)
+ self.db.addLink("url", "name", "plugin", p2, 1)
+
+ assert len(self.db.getAllPackages(owner=0)) == 1 == len(self.db.getAllFiles(owner=0))
+ assert len(self.db.getAllPackages(root=0, owner=0)) == 1 == len(self.db.getAllFiles(package=p1, owner=0))
+ assert len(self.db.getAllPackages(owner=1)) == 1 == len(self.db.getAllFiles(owner=1))
+ assert len(self.db.getAllPackages(root=0, owner=1)) == 1 == len(self.db.getAllFiles(package=p2, owner=1))
+ assert len(self.db.getAllPackages()) == 2 == len(self.db.getAllFiles())
+
+ self.db.deletePackage(p1, 1)
+ assert len(self.db.getAllPackages(owner=0)) == 1 == len(self.db.getAllFiles(owner=0))
+ self.db.deletePackage(p1, 0)
+ assert len(self.db.getAllPackages(owner=1)) == 1 == len(self.db.getAllFiles(owner=1))
+ self.db.deletePackage(p2)
+
+ assert len(self.db.getAllPackages()) == 0
+
+ def test_count(self):
+ self.db.purgeAll()
+
+ assert self.db.filecount() == 0
+ assert self.db.queuecount() == 0
+ assert self.db.proccesscount() == 0
+
+ def assert_file(self, f):
+ try:
+ assert f is not None
+ self.assert_int(f, ("fid", "status", "size", "media", "fileorder", "added", "package", "owner"))
+ assert f.status in range(5)
+ assert f.owner == self.owner
+ assert f.media in range(1024)
+ assert f.package in self.pids
+ assert f.added > 10 ** 6 # date is usually big integer
+ except:
+ print f
+ raise
+
+ def assert_pack(self, p):
+ try:
+ assert p is not None
+ self.assert_int(p, ("pid", "root", "added", "status", "packageorder", "owner"))
+ assert p.pid in self.pids
+ assert p.owner == self.owner
+ assert p.status in range(5)
+ assert p.root in self.pids
+ assert p.added > 10 ** 6
+ except:
+ print p
+ raise
+
+ def assert_int(self, obj, list):
+ for attr in list: assert type(getattr(obj, attr)) == int
+
+if __name__ == "__main__":
+ TestDatabase.benchmark() \ No newline at end of file
diff --git a/tests/test_filemanager.py b/tests/test_filemanager.py
new file mode 100644
index 000000000..f5bdd9df3
--- /dev/null
+++ b/tests/test_filemanager.py
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+
+from random import choice
+
+from helper.Stubs import Core
+from helper.BenchmarkTest import BenchmarkTest
+
+from module.database import DatabaseBackend
+# disable asyncronous queries
+DatabaseBackend.async = DatabaseBackend.queue
+
+from module.FileManager import FileManager
+
+class TestFileManager(BenchmarkTest):
+ bench = ["add_packages", "add_files", "get_files_root", "get",
+ "get_package_content", "get_package_tree",
+ "order_package", "order_files", "move"]
+
+ pids = [-1]
+ count = 100
+
+ @classmethod
+ def setUpClass(cls):
+ c = Core()
+ # db needs seperate initialisation
+ cls.db = c.db = DatabaseBackend(c)
+ cls.db.setup()
+ cls.db.purgeAll()
+
+ cls.m = cls.db.manager = FileManager(c)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.db.purgeAll()
+ cls.db.shutdown()
+
+
+ # benchmarker ignore setup
+ def setUp(self):
+ self.db.purgeAll()
+ self.pids = [-1]
+
+ self.count = 20
+ self.test_add_packages()
+ self.test_add_files()
+
+ def test_add_packages(self):
+ for i in range(100):
+ pid = self.m.addPackage("name", "folder", choice(self.pids), "", "", "", False)
+ self.pids.append(pid)
+
+ if -1 in self.pids:
+ self.pids.remove(-1)
+
+ def test_add_files(self):
+ for pid in self.pids:
+ self.m.addLinks([("plugin %d" % i, "url %s" % i) for i in range(self.count)], pid)
+
+ count = self.m.getQueueCount()
+ files = self.count * len(self.pids)
+ # in test runner files get added twice
+ assert count == files or count == files * 2
+
+ def test_get(self):
+ info = self.m.getPackageInfo(choice(self.pids))
+ assert info.stats.linkstotal == self.count
+
+ fid = choice(info.fids)
+ f = self.m.getFile(fid)
+ assert f.fid in self.m.files
+
+ f.name = "new name"
+ f.sync()
+ finfo = self.m.getFileInfo(fid)
+ assert finfo is not None
+ assert finfo.name == "new name"
+
+ p = self.m.getPackage(choice(self.pids))
+ assert p is not None
+ assert p.pid in self.m.packages
+ p.sync()
+
+ p.delete()
+
+ self.m.getTree(-1, True, False)
+
+
+ def test_get_files_root(self):
+ view = self.m.getTree(-1, True, False)
+
+ for pid in self.pids:
+ assert pid in view.packages
+
+ assert len(view.packages) == len(self.pids)
+
+ p = choice(view.packages.values())
+ assert len(p.fids) == self.count
+ assert p.stats.linkstotal == self.count
+
+
+ def test_get_package_content(self):
+ view = self.m.getTree(choice(self.pids), False, False)
+ p = view.root
+
+ assert len(view.packages) == len(p.pids)
+ for pid in p.pids: assert pid in view.packages
+
+ def test_get_package_tree(self):
+ view = self.m.getTree(choice(self.pids), True, False)
+ for pid in view.root.pids: assert pid in view.packages
+ for fid in view.root.fids: assert fid in view.files
+
+ def test_delete(self):
+ self.m.deleteFile(self.count * 5)
+ self.m.deletePackage(choice(self.pids))
+
+ def test_order_package(self):
+ parent = self.m.addPackage("order", "", -1, "", "", "", False)
+ self.m.addLinks([("url", "plugin") for i in range(100)], parent)
+
+ pids = [self.m.addPackage("c", "", parent, "", "", "", False) for i in range(5)]
+ v = self.m.getTree(parent, False, False)
+ self.assert_ordered(pids, 0, 5, v.root.pids, v.packages, True)
+
+ pid = v.packages.keys()[0]
+ self.assert_pack_ordered(parent, pid, 3)
+ self.assert_pack_ordered(parent, pid, 0)
+ self.assert_pack_ordered(parent, pid, 0)
+ self.assert_pack_ordered(parent, pid, 4)
+ pid = v.packages.keys()[2]
+ self.assert_pack_ordered(parent, pid, 4)
+ self.assert_pack_ordered(parent, pid, 3)
+ self.assert_pack_ordered(parent, pid, 2)
+
+
+ def test_order_files(self):
+ parent = self.m.addPackage("order", "", -1, "", "", "", False)
+ self.m.addLinks([("url", "plugin") for i in range(100)], parent)
+ v = self.m.getTree(parent, False, False)
+
+ fids = v.root.fids[10:20]
+ v = self.assert_files_ordered(parent, fids, 0)
+
+ fids = v.root.fids[20:30]
+
+ self.m.orderFiles(fids, parent, 99)
+ v = self.m.getTree(parent, False, False)
+ assert fids[-1] == v.root.fids[-1]
+ assert fids[0] == v.root.fids[90]
+ self.assert_ordered(fids, 90, 100, v.root.fids, v.files)
+
+ fids = v.root.fids[80:]
+ v = self.assert_files_ordered(parent, fids, 20)
+
+ self.m.orderFiles(fids, parent, 80)
+ v = self.m.getTree(parent, False, False)
+ self.assert_ordered(fids, 61, 81, v.root.fids, v.files)
+
+ fids = v.root.fids[50:51]
+ self.m.orderFiles(fids, parent, 99)
+ v = self.m.getTree(parent, False, False)
+ self.assert_ordered(fids, 99, 100, v.root.fids, v.files)
+
+ fids = v.root.fids[50:51]
+ v = self.assert_files_ordered(parent, fids, 0)
+
+
+ def assert_files_ordered(self, parent, fids, pos):
+ fs = [self.m.getFile(choice(fids)) for i in range(5)]
+ self.m.orderFiles(fids, parent, pos)
+ v = self.m.getTree(parent, False, False)
+ self.assert_ordered(fids, pos, pos+len(fids), v.root.fids, v.files)
+
+ return v
+
+ def assert_pack_ordered(self, parent, pid, pos):
+ self.m.orderPackage(pid, pos)
+ v = self.m.getTree(parent, False, False)
+ self.assert_ordered([pid], pos, pos+1, v.root.pids, v.packages, True)
+
+ # assert that ordering is total, complete with no gaps
+ def assert_ordered(self, part, start, end, data, dict, pack=False):
+ assert data[start:end] == part
+ if pack:
+ assert sorted([p.packageorder for p in dict.values()]) == range(len(dict))
+ assert [dict[pid].packageorder for pid in part] == range(start, end)
+ else:
+ assert sorted([f.fileorder for f in dict.values()]) == range(len(dict))
+ assert [dict[fid].fileorder for fid in part] == range(start, end)
+
+
+ def test_move(self):
+
+ pid = self.pids[-1]
+ pid2 = self.pids[1]
+
+ self.m.movePackage(pid, -1)
+ v = self.m.getTree(-1, False, False)
+
+ assert v.root.pids[-1] == pid
+ assert sorted([p.packageorder for p in v.packages.values()]) == range(len(v.packages))
+
+ v = self.m.getTree(pid, False, False)
+ fids = v.root.fids[10:20]
+ self.m.moveFiles(fids, pid2)
+ v = self.m.getTree(pid2, False, False)
+
+ assert sorted([f.fileorder for f in v.files.values()]) == range(len(v.files))
+ assert len(v.files) == self.count + len(fids)
+
+
+
+if __name__ == "__main__":
+ TestFileManager.benchmark() \ No newline at end of file
diff --git a/tests/test_interactionManager.py b/tests/test_interactionManager.py
new file mode 100644
index 000000000..920d84b9d
--- /dev/null
+++ b/tests/test_interactionManager.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+from unittest import TestCase
+from helper.Stubs import Core
+
+from module.Api import Input, Output
+from module.interaction.InteractionManager import InteractionManager
+
+class TestInteractionManager(TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.core = Core()
+
+ def setUp(self):
+ self.im = InteractionManager(self.core)
+
+
+ def test_notifications(self):
+
+ n = self.im.createNotification("test", "notify")
+ assert self.im.getNotifications() == [n]
+
+ for i in range(10):
+ self.im.createNotification("title", "test")
+
+ assert len(self.im.getNotifications()) == 11
+
+
+ def test_captcha(self):
+ assert self.im.getTask() is None
+
+ t = self.im.newCaptchaTask("1", "", "")
+ assert t.output == Output.Captcha
+ self.im.handleTask(t)
+ assert t is self.im.getTask()
+
+ t2 = self.im.newCaptchaTask("2", "", "")
+ self.im.handleTask(t2)
+
+ assert self.im.getTask(Output.Query) is None
+ assert self.im.getTask() is t
+
+ self.im.removeTask(t)
+ assert self.im.getTask() is t2
+
+ self.im.getTaskByID(t2.iid)
+ assert self.im.getTask() is None
+
+
+ def test_query(self):
+ assert self.im.getTask() is None
+ t = self.im.newQueryTask(Input.Text, None, "text")
+ assert t.description == "text"
+ self.im.handleTask(t)
+
+ assert self.im.getTask(Output.Query) is t
+ assert self.im.getTask(Output.Captcha) is None \ No newline at end of file
diff --git a/tests/test_syntax.py b/tests/test_syntax.py
new file mode 100644
index 000000000..a4cc53ee5
--- /dev/null
+++ b/tests/test_syntax.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from os import walk
+from os.path import abspath, dirname, join
+
+from unittest import TestCase
+
+PATH = abspath(join(dirname(abspath(__file__)), "..", ""))
+
+# needed to register globals
+from helper import Stubs
+
+class TestSyntax(TestCase):
+ pass
+
+
+for path, dirs, files in walk(join(PATH, "module")):
+
+ for f in files:
+ if not f.endswith(".py") or f.startswith("__"): continue
+ fpath = join(path, f)
+ pack = fpath.replace(PATH, "")[1:-3] #replace / and .py
+ imp = pack.replace("/", ".")
+ packages = imp.split(".")
+ #__import__(imp)
+
+ # to much sideeffect when importing
+ if "web" in packages or "lib" in packages: continue
+ if "ThriftTest" in packages: continue
+
+ # currying
+ def meta(imp, sig):
+ def _test(self=None):
+ __import__(imp)
+
+ _test.func_name = sig
+ return _test
+
+ # generate test methods
+ sig = "test_%s_%s" % (packages[-2], packages[-1])
+
+
+ setattr(TestSyntax, sig, meta(imp, sig)) \ No newline at end of file