diff options
1271 files changed, 108653 insertions, 60075 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ea0e5508f --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib/* +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# ignoreing unneeded files, using glob syntax +*~ +*.pidaproject +.svn +*.DS_Store +*.egg-info +*.project +*.pydevproject +Downloads/* +container/* +Logs/* +docs/module/ +docs/pyload/ +docs/_build +DLC.py +links.txt +ssl.crt +ssl.key +cert.pem +*.prefs +*.orig +*.rej +dist/* +build/* +env/* +node_modules +temp +dist +.tmp +.sass-cache +app/components + diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 1215b241d..000000000 --- a/.hgignore +++ /dev/null @@ -1,36 +0,0 @@ -# ignoreing unneeded files, using glob syntax -syntax: glob -*.pyc -*~ -*.pidaproject -.svn -*.DS_Store -*.egg-info -*.project -*.pydevproject -Downloads/* -container/* -Logs/* -docs/module/ -docs/_build -module/plugins/container/DLC_*.py -failed_links.txt -module/config/gui.xml -module/config/core.xml -module/config/plugin.xml -links.txt -ssl.crt -ssl.key -cert.pem -module/web/pyload.db -*.svg -*.prefs -*.po -*.orig -*.rej -pyload/* -dist/* -build/* -setup.py -paver-minilib.zip -env/* @@ -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 deleted file mode 100644 index bc08fe2e4..000000000 --- a/LICENSE +++ /dev/null @@ -1,619 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..e3a8bf86a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,653 @@ +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 + of this license document, but changing it is not allowed. + + Preamble + + 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, +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. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + 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. + + TERMS AND CONDITIONS + + 0. Definitions. + + "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. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. 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 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 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 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 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 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 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. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..21d889a5d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,21 @@ +include *.py *.md setup.* CLA MANIFEST.in + + +recursive-include pyload *.py +recursive-include pyload/plugins *_2*.pyc + +recursive-include pyload/remote * +recursive-exclude pyload/remote *.py[co] + +recursive-include pyload/web .bowerrc .jshintrc Gruntfile.js package.json +prune pyload/web/node_modules + +recursive-include pyload/web/dist * +recursive-include pyload/web/app * +prune pyload/web/app/components + +recursive-include docs Makefile *.py *.conf *.rst *.svg *.png + +recursive-include tests *.py *.txt *.sh *.org + +recursive-include locale *.pot *.mo diff --git a/README b/README deleted file mode 100644 index 7f3c4f4c8..000000000 --- a/README +++ /dev/null @@ -1,89 +0,0 @@ - -Description -=========== - -pyLoad is a free and open source downloader for 1-click-hosting sites -like rapidshare.com or uploaded.to. -It supports link decryption as well as all important container formats. - -pyLoad is written entirely in Python and is currently under heavy development. - -For news, downloads, wiki, forum and further information visit http://pyload.org/ - -To report bugs, suggest features, ask a question, get the developer version -or help us out, visit http://bitbucket.org/spoob/pyload/ - -Documentation about extending pyLoad can be found at http://docs.pyload.org or join us at #pyload on irc.freenode.net - -Dependencies -============ - -You need at least python 2.5 to run pyLoad and all of these required libaries. -They should be automatically installed when using pip install. -The prebuilt pyload packages also install these dependencies or have them included, so manuall install -is only needed when installing pyLoad from source. - -Required --------- - -- pycurl a.k.a python-curl -- jinja2 -- beaker -- thrift -- simplejson (for python 2.5) - -Some plugins require additional packages, only install these when needed. - -Optional --------- - -- pycrypto: RSDF/CCF/DLC support -- tesseract, python-pil a.k.a python-imaging: Automatic captcha recognition for a small amount of plugins -- jsengine (spidermonkey, ossp-js, pyv8, rhino): Used for several hoster, ClickNLoad -- feedparser -- BeautifulSoup -- pyOpenSSL: For SSL connection - -First start -=========== - -Note: If you installed pyload via package-manager `python pyLoadCore.py` is probably equivalent to `pyLoadCore` - -Run:: - - python pyLoadCore.py - -and follow the instructions of the setup assistent. - -For a list of options use:: - - python pyLoadCore.py -h - -Configuration -============= - -After finishing the setup assistent pyLoad is ready to use and more configuration can be done via webinterface. -Additionally you could simply edit the config files located in your pyLoad home dir (defaults to: ~/.pyload) -with your favorite editor and edit the appropriate options. For a short description of -the options take a look at http://pyload.org/configuration. - -To restart the configure assistent run:: - - python pyLoadCore.py -s - -Adding downloads ----------------- - -To start the CLI and connect to a local server, run:: - - python pyLoadCli.py -l - -for more options refer to:: - - python pyLoadCli.py -h - -The webinterface can be accessed when pointing your webbrowser to the ip and configured port, defaults to http://localhost:8000 - -Notes -===== -For more information, see http://pyload.org/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..2fa51f329 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# pyLoad [](http://nightly.pyload.org/job/Nightly/) + +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. + +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/ + +To report bugs, suggest features, ask a question, get the developer version +or help us out, visit https://github.com/pyload/pyload + +Documentation about extending pyLoad can be found at http://docs.pyload.org or join us at #pyload on irc.freenode.net + +Dependencies +------------ + +You need at least python 2.5 to run pyLoad and all of these required libaries. +They should be automatically installed when using pip install. +The prebuilt pyload packages also install these dependencies or have them included, so manual install +is only needed when installing pyLoad from source. + +### Required + +- pycurl a.k.a python-curl +- beaker +- simplejson (for python 2.5) + +Some plugins require additional packages, only install these when needed. + +### Optional + +- pycrypto: RSDF/CCF/DLC support +- tesseract, python-pil a.k.a python-imaging: Automatic captcha recognition for a small amount of plugins +- jsengine (spidermonkey, ossp-js, pyv8, rhino): Used for several hoster, ClickNLoad +- feedparser +- BeautifulSoup +- pyOpenSSL: For SSL connection + +First start +----------- + +Note: If you installed pyload via package-manager `python pyLoadCore.py` is probably equivalent to `pyLoadCore` + +Run:: + + python pyload.py + +and follow the instructions of the setup assistent. + +For a list of options use:: + + python pyload.py -h + +Configuration +------------- + +After finishing the setup assistent pyLoad is ready to use and more configuration can be done via webinterface. +Additionally you could simply edit the config files located in your pyLoad home dir (defaults to: ~/.pyload) +with your favorite editor and edit the appropriate options. For a short description of +the options take a look at http://pyload.org/configuration. + +To restart the configure assistent run:: + + python pyload.py -s + +### Adding downloads + +To start the CLI and connect to a local server, run:: + + python pyload-cli.py -l + +for more options refer to:: + + python pyload-cli.py -h + +The webinterface can be accessed when pointing your webbrowser to the ip and configured port, defaults to http://localhost:8000 + +Notes +----- +For more information, see http://pyload.org/ diff --git a/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/components.rst b/docs/api/components.rst new file mode 100644 index 000000000..08560b535 --- /dev/null +++ b/docs/api/components.rst @@ -0,0 +1,20 @@ +.. _components: + +Components +========== + +The API consists of different parts, all combined and accessible over one interface. A summary and documetation +of the available components listed by topic can be found below. + +.. autosummary:: + :toctree: module + + pyload.api.CoreApi.CoreApi + pyload.api.ConfigApi.ConfigApi + pyload.api.DownloadPreparingApi.DownloadPreparingApi + pyload.api.DownloadApi.DownloadApi + pyload.api.FileApi.FileApi + pyload.api.CollectorApi.CollectorApi + pyload.api.AccountApi.AccountApi + pyload.api.UserInteractionApi.UserInteractionApi + pyload.api.AddonApi.AddonApi
\ No newline at end of file diff --git a/docs/api/datatypes.rst b/docs/api/datatypes.rst new file mode 100644 index 000000000..df9fb6e41 --- /dev/null +++ b/docs/api/datatypes.rst @@ -0,0 +1,560 @@ +.. _datatypes: + +******************* +Datatype Definition +******************* + +Below you find a copy of :file:`pyload/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('pyload/remote/pyload.thrift', 'rb').read()) ]]] + namespace java org.pyload.thrift + + typedef i32 FileID + typedef i32 PackageID + typedef i32 ResultID + typedef i32 InteractionID + typedef i32 UserID + typedef i64 UTCDate + typedef i64 ByteCount + typedef list<string> LinkList + typedef string PluginName + typedef string JSONString + + // NA - Not Available + enum DownloadStatus { + NA, + Offline, + Online, + Queued, + Paused, + Finished, + Skipped, + Failed, + Starting, + Waiting, + Downloading, + TempOffline, + Aborted, + Decrypting, + Processing, + Custom, + Unknown + } + + // Download states, combination of several downloadstatuses + // defined in Api + enum DownloadState { + All, + Finished, + Unfinished, + Failed, + Unmanaged // internal state + } + + enum MediaType { + All = 0 + Other = 1, + Audio = 2, + Image = 4, + Video = 8, + Document = 16, + Archive = 32, + Executable = 64 + } + + enum FileStatus { + Ok, + Missing, + Remote, // file is available at remote location + } + + 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, ip, s.o. + enum InputType { + NA, + Text, + Int, + File, + Folder, + Textbox, + Password, + Time, + 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 + PluginList, // a list plugins from pyload + 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 Interaction { + All = 0, + Notification = 1, + Captcha = 2, + Query = 4, + } + + 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 Input { + 1: InputType type, + 2: optional JSONString default_value, + 3: optional JSONString data, + } + + struct DownloadProgress { + 1: FileID fid, + 2: PackageID pid, + 3: ByteCount speed, // per second + 4: DownloadStatus status, + } + + struct ProgressInfo { + 1: PluginName plugin, + 2: string name, + 3: string statusmsg, + 4: i32 eta, // in seconds + 5: ByteCount done, + 6: ByteCount total, // arbitary number, size in case of files + 7: optional DownloadProgress download + } + + // 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: UserID owner, + 5: ByteCount size, + 6: FileStatus status, + 7: MediaType media, + 8: UTCDate added, + 9: i16 fileorder, + 10: 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: UserID owner, + 6: string site, + 7: string comment, + 8: string password, + 9: UTCDate added, + 10: list<string> tags, + 11: PackageStatus status, + 12: bool shared, + 13: i16 packageorder, + 14: PackageStats stats, + 15: list<FileID> fids, + 16: 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 ServerStatus { + 1: ByteCount speed, + 2: i16 linkstotal, + 3: i16 linksqueue, + 4: ByteCount sizetotal, + 5: ByteCount sizequeue, + 6: bool notifications, + 7: bool paused, + 8: bool download, + 9: bool reconnect, + } + + struct InteractionTask { + 1: InteractionID iid, + 2: Interaction type, + 3: Input input, + 4: string title, + 5: string description, + 6: PluginName plugin, + } + + struct AddonService { + 1: string func_name, + 2: string description, + 3: list<string> arguments, + 4: optional i16 media, + } + + struct AddonInfo { + 1: string func_name, + 2: string description, + 3: JSONString value, + } + + struct ConfigItem { + 1: string name, + 2: string label, + 3: string description, + 4: Input input, + 5: JSONString value, + } + + struct ConfigHolder { + 1: string name, // for plugin this is the PluginName + 2: string label, + 3: string description, + 4: string explanation, + 5: list<ConfigItem> items, + 6: optional list<AddonInfo> info, + } + + struct ConfigInfo { + 1: string name + 2: string label, + 3: string description, + 4: string category, + 5: bool user_context, + 6: optional bool activated, + } + + struct EventInfo { + 1: string eventname, + 2: list<JSONString> event_args, //will contain json objects + } + + 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: list <ConfigItem> config, + } + + 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 + } + + exception InvalidConfigSection { + 1: string section + } + + exception Unauthorized { + } + + exception Forbidden { + } + + exception Conflict { + } + + + service Pyload { + + /////////////////////// + // Core Status + /////////////////////// + + string getServerVersion(), + string getWSAddress(), + ServerStatus getServerStatus(), + list<ProgressInfo> getProgressInfo(), + + list<string> getLog(1: i32 offset), + ByteCount freeSpace(), + + void pauseServer(), + void unpauseServer(), + bool togglePause(), + bool toggleReconnect(), + + void quit(), + void restart(), + + /////////////////////// + // Configuration + /////////////////////// + + map<string, ConfigHolder> getConfig(), + string getConfigValue(1: string section, 2: string option), + + // two methods with ambigous classification, could be configuration or addon/plugin related + list<ConfigInfo> getCoreConfig(), + list<ConfigInfo> getPluginConfig(), + list<ConfigInfo> getAvailablePlugins(), + + ConfigHolder loadConfig(1: string name), + + void setConfigValue(1: string section, 2: string option, 3: string value), + void saveConfig(1: ConfigHolder config), + void deleteConfig(1: PluginName plugin), + + /////////////////////// + // Download Preparing + /////////////////////// + + map<PluginName, LinkList> checkURLs(1: LinkList urls), + map<PluginName, LinkList> parseURLs(1: string html, 2: string url), + + // parses results and generates packages + OnlineCheck checkOnlineStatus(1: LinkList urls), + OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data) + + // poll results from previously started online check + OnlineCheck pollResults(1: ResultID rid), + + // packagename -> urls + map<string, LinkList> generatePackages(1: LinkList links), + + /////////////////////// + // Download + /////////////////////// + + 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), // delete the whole folder recursive + + // Modify Downloads + + void restartPackage(1: PackageID pid), + void restartFile(1: FileID fid), + void recheckPackage(1: PackageID pid), + void restartFailed(), + void stopDownloads(1: list<FileID> fids), + void stopAllDownloads(), + + /////////////////////// + // 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 retrieval + //////////////////////////// + + TreeCollection getAllFiles(), + TreeCollection getFilteredFiles(1: DownloadState state), + + // pid -1 for root, full=False only delivers first level in tree + TreeCollection getFileTree(1: PackageID pid, 2: bool full), + TreeCollection getFilteredFileTree(1: PackageID pid, 2: bool full, 3: DownloadState state), + + // 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), + TreeCollection findPackages(1: list<string> tags), + list<string> searchSuggestions(1: string pattern), + + // Modify Files/Packages + + // moving package while downloading is not possible, so they will return bool to indicate success + void updatePackage(1: PackageInfo pack) throws (1: PackageDoesNotExists e), + bool setPackageFolder(1: PackageID pid, 2: string path) 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 = interaction types binary ORed + bool isInteractionWaiting(1: i16 mode), + list<InteractionTask> getInteractionTasks(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), + + /////////////////////// + // Account Methods + /////////////////////// + + list<string> getAccountTypes(), + + list<AccountInfo> getAccounts(), + AccountInfo getAccountInfo(1: PluginName plugin, 2: string loginname, 3: bool refresh), + + AccountInfo updateAccount(1: PluginName plugin, 2: string loginname, 3: string password), + void updateAccountInfo(1: AccountInfo account), + void removeAccount(1: AccountInfo account), + + ///////////////////////// + // Auth+User Information + ///////////////////////// + + bool login(1: string username, 2: string password), + // returns own user data + UserData getUserData(), + + // all user, for admins only + map<UserID, UserData> getAllUserData(), + + 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), + + // works contextual, admin can change every password + bool setPassword(1: string username, 2: string old_password, 3: string new_password), + + /////////////////////// + // Addon Methods + /////////////////////// + + //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), + + + //scheduler + + // TODO + + } + .. [[[end]]] + diff --git a/docs/api/json_api.rst b/docs/api/json_api.rst new file mode 100644 index 000000000..504de20bf --- /dev/null +++ b/docs/api/json_api.rst @@ -0,0 +1,95 @@ +.. _json_api: + +======== +JSON API +======== + +JSON [1]_ is a lightweight object notation and wrappers exists for nearly every programming language. Every +modern browser is able to load JSON objects with JavaScript. Unlike other RPC methods you don't need to generate or precompile +any stub methods. The JSON :class:`Api <pyload.Api.Api>` is ready to be used in most languages and most JSON libraries are lightweight +enough to build very small and performant scripts. Because of the builtin support, JSON is the first choice for all browser +applications. + +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 <pyload.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 <pyload.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..3b65a45b0 --- /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 <pyload.Api.Api>` is to give uniform access to all integral parts +and plugins in pyLoad as well as other clients written in arbitrary programming languages. +Most of the :class:`Api <pyload.Api.Api>` functionality is exposed via HTTP or WebSocktes [2]_ as +simple JSON objects [3]_. In conclusion the :class:`Api <pyload.Api.Api>` is accessible via many programming languages, +over network from remote machines and over browser with javascript. + + +.. rubric:: Contents + +.. toctree:: + + json_api.rst + websocket_api.rst + components.rst + datatypes.rst + + +.. rubric:: Footnotes + +.. [1] http://en.wikipedia.org/wiki/Application_programming_interface +.. [2] http://en.wikipedia.org/wiki/WebSocket +.. [3] http://en.wikipedia.org/wiki/Json
\ No newline at end of file diff --git a/docs/api/websocket_api.rst b/docs/api/websocket_api.rst new file mode 100644 index 000000000..bc5d67fa3 --- /dev/null +++ b/docs/api/websocket_api.rst @@ -0,0 +1,25 @@ +.. _websocket_api: + +============= +WebSocket API +============= + +TODO + + +Login +----- + +Calling Methods +--------------- + +Passing parameters +------------------ + +Example +------- + + +.. rubric:: Footnotes + +.. [1] http://en.wikipedia.org/wiki/WebSocket
\ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 9d2cf98f9..294684120 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,11 +12,11 @@ # serve to show the default. import sys, os -from os.path import dirname, join, abspath +from os.path import dirname, join, abspath, exists dir_name = join(dirname(abspath(""))) sys.path.append(dir_name) -sys.path.append(join(dir_name, "module", "lib")) +sys.path.append(join(dir_name, "pyload", "lib")) # If extensions (or modules to document with autodoc) are in another directory, @@ -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'2013, 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 @@ -60,13 +61,13 @@ copyright = u'2011, pyLoad Team' # # The full version, including alpha/beta/rc tags. """ [[[cog -from pavement import options -v = options.version.split(".") +from pyload import __version__ +v = __version__.split(".") cog.outl("version = '%s'" % ".".join(v[:2])) cog.outl("release = '%s'" % ".".join(v)) ]]]""" version = '0.4' -release = '0.4.9' +release = '0.4.9.9-dev' # [[[end]]] @@ -128,12 +129,12 @@ html_theme = 'default' # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = join(dir_name, "module", "web", "media", "default", "img", "pyload-logo-edited3.5-new-font-small.png") +html_logo = join(dir_name, "pyload", "web", "app", "components", "pyload-common", "images", "logo.png") # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = join(dir_name, "icons", "pyload2.ico") +html_favicon = join(dir_name, "pyload", "web", "app", "components", "pyload-common", "favicon.ico") # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -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/docs.conf b/docs/docs.conf index e197cfa43..d41ad8bc6 100644 --- a/docs/docs.conf +++ b/docs/docs.conf @@ -3,12 +3,12 @@ [epydoc] -modules: pyLoadCore.py, pyLoadCli.py, pyloadGui.py, module +modules: pyLoadCore.py, pyLoadCli.py, pyloadGui.py, pyload output: html target: docs docformat: restructuredtext -exclude: module\.lib|module\.remote\.thriftbackend\.thriftgen|PyQt4|\.pyc|\.pyo|module\.plugins\.(accounts|captcha|container|crypter|hooks|hoster) +exclude: pyload\.lib|pyload\.remote\.thriftbackend\.thriftgen|PyQt4|\.pyc|\.pyo|pyload\.plugins\.(accounts|captcha|container|crypter|hooks|hoster) name: pyLoad Documentation url: http://docs.pyload.org 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..49b880127 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,26 +1,45 @@ -.. 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 -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. +Great that you found your way to the pyLoad [1]_ documentation! -Contents: +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 +.. currentmodule:: pyload + +.. rubric:: Footnotes + +.. [1] http://pyload.org +.. [2] http://pyload.org/irc +.. [3] http://bitbucket.org/spoob/pyload/overview +.. [4] http://forum.pyload.org ================== diff --git a/docs/module_overview.rst b/docs/module_overview.rst index d51202c88..770265198 100644 --- a/docs/module_overview.rst +++ b/docs/module_overview.rst @@ -1,17 +1,23 @@ + Module Overview =============== -You can find an overview of some important classes here: +A little selection of most important modules in pyLoad. .. autosummary:: - :toctree: module + :toctree: pyload - module.Api.Api - module.plugins.Plugin.Base - module.plugins.Plugin.Plugin - module.plugins.Crypter.Crypter - module.plugins.Account.Account - module.plugins.Hook.Hook - module.HookManager.HookManager - module.PyFile.PyFile - module.PyPackage.PyPackage + pyload.Api.Api + pyload.plugins.Base.Base + pyload.plugins.Hoster.Hoster + pyload.plugins.internal.SimpleHoster.SimpleHoster + pyload.plugins.Crypter.Crypter + pyload.plugins.internal.SimpleCrypter.SimpleCrypter + pyload.plugins.Addon.Addon + pyload.plugins.Account.Account + pyload.plugins.MultiHoster.MultiHoster + pyload.AddonManager.AddonManager + pyload.interaction.EventManager.EventManager + pyload.interaction.InteractionManager.InteractionManager + pyload.interaction.InteractionTask.InteractionTask + pyload.remote.apitypes 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/plugins/addon_plugin.rst b/docs/plugins/addon_plugin.rst new file mode 100644 index 000000000..8f1adf39a --- /dev/null +++ b/docs/plugins/addon_plugin.rst @@ -0,0 +1,163 @@ +.. _write_addons: + +Addon - Add new functionality +============================= + +A Hook is a python file which is located at :file:`pyload/plugins/hooks`. +The :class:`HookManager <pyload.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 <pyload.HookManager.HookManager>`, +do something completely autonomic and has full access to the :class:`Api <pyload.Api.Api>` and every detail of pyLoad. +The UpdateManager, CaptchaTrader, UnRar and many more are implemented as hooks. + +Hook header +----------- + +Your hook needs to subclass :class:`Hook <pyload.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: :: + + from pyload.plugins.Hook import Hook + + class YourHook(Hook): + __name__ = "YourHook" + __version__ = "0.1" + __description__ = "Does really cool stuff" + __config__ = [ ("activated" , "bool" , "Activated" , "True" ) ] + __threaded__ = ["downloadFinished"] + __author_name__ = ("Me") + __author_mail__ = ("me@has-no-mail.com") + +All meta-data is defined in the header, you need at least one option at ``__config__`` so the user can toggle your +hook on and off. Don't overwrite the ``init`` method if not necessary, use ``setup`` instead. + +Using the Config +---------------- + +We are taking a closer look at the ``__config__`` parameter. +You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``. +When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name,value``. + + +Interacting on Events +--------------------- + +The next step is to think about where your Hook action takes place. + +The easiest way is to overwrite specific methods defined by the :class:`Hook <pyload.plugins.Hook.Hook>` base class. +The name is indicating when the function gets called. +See :class:`Hook <pyload.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 <pyload.PyFile.PyFile>` +or :class:`PyPackage <pyload.PyPackage.PyPackage>` you should read the relevant documentation to know how to access it's great power and manipulate them. + +What a basic excerpt would look like: :: + + from pyload.plugins.Hook import Hook + + class YourHook(Hook): + """ + Your Hook code here. + """ + + def activate(self): + print "Yay, the core is ready let's do some work." + + def downloadFinished(self, pyfile): + print "A Download just finished." + +Another 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 kinds of long lived processing tasks. + +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 <pyload.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 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 pyload.plugins.Hook import Hook + + class YourHook(Hook): + """ + Your Hook code here. + """ + event_map = {"downloadFinished" : "doSomeWork", + "allDownloadsFnished": "someMethod", + "coreReady": "initialize"} + + def initialize(self): + print "Initialized." + + def doSomeWork(self, pyfile): + print "This is equivalent to the above example." + + def someMethod(self): + print "The underlying event (allDownloadsFinished) for this method is not available through the base class" + +An advantage of the event listener is that you are able to register and remove the listeners at runtime. +Use `self.manager.listenTo("name", function)`, `self.manager.removeEvent("name", function)` and see doc for +:class:`HookManager <pyload.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 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 +---------------------- + +You may have noticed that pyLoad has an :class:`Api <pyload.Api.Api>`, which can be used internal or called by clients via RPC. +So probably clients want to be able to interact with your hook to request it's state or invoke some action. + +Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: :: + + from pyload.plugins.Hook import Hook, Expose + + class YourHook(Hook): + """ + Your Hook code here. + """ + + @Expose + def invoke(self, arg): + print "Invoked with", arg + +Thats all, it's available via the :class:`Api <pyload.Api.Api>` now. If you want to use it read :ref:`access_api`. +Here is a basic example: :: + + #Assuming client is a ThriftClient or Api object + + print client.getServices() + print client.call(ServiceCall("YourHook", "invoke", "an argument")) + +Providing status information +---------------------------- +Your hook can store information in a ``dict`` that can easily be retrievied via the :class:`Api <pyload.Api.Api>`. + +Just store everything in ``self.info``. :: + + from pyload.plugins.Hook import Hook + + class YourHook(Hook): + """ + Your Hook code here. + """ + + def setup(self): + self.info = {"running": False} + + def activate(self): + self.info["running"] = True + +Usable with: :: + + #Assuming client is a ThriftClient or Api object + + print client.getAllInfo() + +Example +------- + Sorry but you won't find an example here ;-) + + Look at :file:`pyload/plugins/hooks` and you will find plenty examples there. diff --git a/docs/plugins/base_plugin.rst b/docs/plugins/base_plugin.rst new file mode 100644 index 000000000..5fa110fe7 --- /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:`pyload/plugins/`. +All different plugin types inherit from :class:`Base <pyload.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 <pyload.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 <pyload.plugins.Crypter.Crypter>` and :class:`Hoster <pyload.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 <pyload.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 pyload.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 an addon plugin should have it, so the users know what it is 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 <pyload.plugins.Base.Base>`. To note some important ones: + +The pyload core instance is accessible at ``self.core`` attribute +and the :class:`Api <pyload.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..b10dd27f9 --- /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 <pyload.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 <pyload.plugins.Crypter.Crypter>`. :: + + from pyload.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 pyload.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 <pyload.plugins.internal.SimpleCrypter.SimpleCrypter>` class which handles most of the workflow. Only the regexp +pattern has to be defined. + +Exmaple:: + + from pyload.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..55a973463 --- /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 pyload.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 <pyload.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 <pyload.plugins.Addon.Addon>` plugins + + +Online status fetching +---------------------- + +SimpleHoster +------------ + + +Testing +------- + + +Examples +-------- + +The best examples are the already existing plugins in :file:`pyload/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/resources/pyload_logo-outline.png b/docs/resources/pyload_logo-outline.png Binary files differnew file mode 100644 index 000000000..d20084847 --- /dev/null +++ b/docs/resources/pyload_logo-outline.png diff --git a/docs/resources/pyload_logo-title.png b/docs/resources/pyload_logo-title.png Binary files differnew file mode 100644 index 000000000..441036a3c --- /dev/null +++ b/docs/resources/pyload_logo-title.png diff --git a/docs/resources/pyload_logo.svg b/docs/resources/pyload_logo.svg new file mode 100644 index 000000000..9346f09ad --- /dev/null +++ b/docs/resources/pyload_logo.svg @@ -0,0 +1,317 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:version="0.48.2 r9819" + sodipodi:version="0.32" + sodipodi:docname="pyload_logo.svg" + x="0px" + y="0px" + width="1000" + height="1000" + viewBox="0 0 1000 1000" + enable-background="new 0 0 1000 1000" + xml:space="preserve" + inkscape:export-filename="/Users/christian/Development/pyload/docs/resources/pyload_logo.png" + inkscape:export-xdpi="9.2647057" + inkscape:export-ydpi="9.2647057"><title + id="title3794">pyLoad Logo</title><metadata + id="metadata33"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>pyLoad Logo</dc:title><dc:creator><cc:Agent><dc:title>pyLoad Team</dc:title></cc:Agent></dc:creator><cc:license + rdf:resource="http://creativecommons.org/licenses/by-nc-sa/3.0/" /><dc:subject><rdf:Bag><rdf:li>pyload logo</rdf:li></rdf:Bag></dc:subject><dc:description>Official pyLoad logo with some optional filters</dc:description></cc:Work><cc:License + rdf:about="http://creativecommons.org/licenses/by-nc-sa/3.0/"><cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires + rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires + rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits + rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires + rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><defs + id="defs31"><color-profile + name="Generic-RGB-Profile" + xlink:href="/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" + id="color-profile3792" /><linearGradient + gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)" + y2="752.55798" + x2="783.30792" + y1="134.5405" + x1="1220.123" + gradientUnits="userSpaceOnUse" + id="linearGradient3778"> + <stop + id="stop3780" + style="stop-color:#ffd856;stop-opacity:1;" + offset="0" /> + <stop + id="stop3782" + style="stop-color:#ffc836;stop-opacity:1;" + offset="1" /> + </linearGradient><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3778" + id="linearGradient3005" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0266298,0,0,1,-7.2192914,0)" + spreadMethod="pad" + x1="271.09799" + y1="499.06601" + x2="1001.7615" + y2="499.06601" /><linearGradient + inkscape:collect="always" + xlink:href="#path1948_3_" + id="linearGradient3783" + x1="25.991835" + y1="239.76074" + x2="763.24133" + y2="239.76074" + gradientUnits="userSpaceOnUse" /><filter + id="filter3020" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"><feGaussianBlur + id="feGaussianBlur3022" + in="SourceAlpha" + stdDeviation="25" + result="blur" /><feColorMatrix + id="feColorMatrix3024" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /><feOffset + id="feOffset3026" + in="bluralpha" + dx="15" + dy="30" + result="offsetBlur" /><feMerge + id="feMerge3028"><feMergeNode + id="feMergeNode3030" + in="offsetBlur" /><feMergeNode + id="feMergeNode3032" + in="SourceGraphic" /></feMerge></filter><filter + id="filter3023" + inkscape:label="Outline" + inkscape:menu="Morphology" + inkscape:menu-tooltip="Draws a colored outline around" + width="1.5" + height="1.5" + x="-0.25" + y="-0.25" + color-interpolation-filters="sRGB"><feGaussianBlur + id="feGaussianBlur3025" + in="SourceAlpha" + stdDeviation="10" + result="blur" /><feColorMatrix + id="feColorMatrix3027" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1 " + result="result4" /><feFlood + id="feFlood3029" + result="result1" + flood-color="rgb(0,0,0)" + in="blur" + flood-opacity="1" /><feComposite + id="feComposite3031" + in2="result4" + result="result2" + in="result1" + operator="atop" /><feComposite + id="feComposite3033" + in2="result2" + in="SourceGraphic" + result="result3" + operator="atop" /></filter><filter + id="filter3140" + inkscape:label="3D Effect" + inkscape:menu="Bevels" + inkscape:menu-tooltip="Basic specular bevel to use for building textures" + color-interpolation-filters="sRGB"><feGaussianBlur + id="feGaussianBlur3142" + stdDeviation="6" + in="SourceGraphic" + result="result0" /><feDiffuseLighting + id="feDiffuseLighting3144" + lighting-color="rgb(255,255,255)" + diffuseConstant="1" + surfaceScale="4" + result="result5"><feDistantLight + id="feDistantLight3146" + elevation="45" + azimuth="235" /></feDiffuseLighting><feComposite + id="feComposite3148" + in2="SourceGraphic" + k1="1.4" + in="result5" + result="fbSourceGraphic" + operator="arithmetic" /><feGaussianBlur + id="feGaussianBlur3150" + result="result0" + in="fbSourceGraphic" + stdDeviation="6" /><feSpecularLighting + id="feSpecularLighting3152" + specularExponent="25" + specularConstant="1" + surfaceScale="4" + lighting-color="rgb(255,255,255)" + result="result1" + in="result0"><feDistantLight + id="feDistantLight3154" + azimuth="235" + elevation="45" /></feSpecularLighting><feComposite + id="feComposite3156" + in2="result91" + k3="1" + k2="1" + operator="arithmetic" + result="result4" + in="fbSourceGraphic" /><feComposite + id="feComposite3158" + in2="SourceGraphic" + operator="in" + result="result2" + in="result4" /></filter></defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1352" + inkscape:window-height="766" + id="namedview29" + showgrid="false" + inkscape:zoom="0.5" + inkscape:cx="556.17192" + inkscape:cy="487.74566" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="g2303_1_" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showborder="true" + borderlayer="false" + showguides="true" + inkscape:guide-bbox="true" /> +<g + id="g2303" + display="none" + style="display:none" + transform="translate(-21.799529,1.0000288)"> + + <linearGradient + id="path1948_2_" + gradientUnits="userSpaceOnUse" + x1="-338.45169" + y1="1559.3135" + x2="627.55139" + y2="728.19678" + gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"> + <stop + offset="0" + style="stop-color:#5A9FD4" + id="stop5" /> + <stop + offset="1" + style="stop-color:#306998" + id="stop7" /> + </linearGradient> + <path + id="path1948" + display="inline" + d="M 493.674,-0.868 H 256.098 166.937 c -69.042,0 -129.5,41.498 -148.412,120.447 -21.813,90.495 -22.783,146.964 0,241.457 16.887,70.339 57.219,120.447 126.26,120.447 h 81.686 V 372.939 c 0,-78.413 67.856,-147.585 148.416,-147.585 h 237.298 c 66.057,0 118.787,-54.385 118.787,-120.727 L 730.846,-0.89" + inkscape:connector-curvature="0" + style="fill:url(#path1948_2_);display:inline" /> + + <linearGradient + id="path1950_2_" + gradientUnits="userSpaceOnUse" + x1="1186.2559" + y1="133.9731" + x2="749.44067" + y2="751.99072" + gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"> + <stop + offset="0" + style="stop-color:#FFD43B" + id="stop11" /> + <stop + offset="1" + style="stop-color:#FFE873" + id="stop13" /> + </linearGradient> + <path + id="path1950" + display="inline" + d="m 755.602,0.132 v 105.496 c 0,81.799 -69.346,150.631 -148.418,150.631 H 369.886 c -65.002,0 -118.788,55.632 -118.788,120.727 v 226.221 c 0,64.39 55.985,102.262 118.788,120.727 C 412.959,736.601 500,1000 500,1000 c 0,0 68.736,-264.907 107.184,-276.066 59.809,-17.313 118.787,-52.168 118.787,-120.727 V 512.666 H 488.673 V 482.483 H 725.97 844.761 c 69.049,0 94.783,-48.163 118.787,-120.447 24.809,-74.417 23.752,-145.985 0,-241.457 C 946.486,51.85 913.898,0.132 844.762,0.132 h -89.16 z M 622.137,573.026 c 24.627,0 44.578,20.179 44.578,45.137 0,25.041 -19.951,45.409 -44.578,45.409 -24.541,0 -44.58,-20.368 -44.58,-45.409 0,-24.958 20.039,-45.137 44.58,-45.137 z" + inkscape:connector-curvature="0" + style="fill:url(#path1950_2_);display:inline" /> +</g> +<g + id="g2303_1_" + transform="translate(-21.799529,1.0000288)"> + + <linearGradient + id="path1948_3_" + gradientUnits="userSpaceOnUse" + x1="-303.33109" + y1="1560.5684" + x2="662.672" + y2="729.45172" + gradientTransform="matrix(0.57252813,0,0,-0.568,127.65196,708.7776)"> + <stop + offset="0" + style="stop-color:#3778b0;stop-opacity:1;" + id="stop18" /> + <stop + offset="1" + style="stop-color:#356b97;stop-opacity:1;" + id="stop20" /> + </linearGradient> + <path + style="fill:url(#linearGradient3783);fill-opacity:1;" + d="M 187.6338,-0.828786 C 142.54691,-0.4970408 97.509189,20.00164 69.766894,55.506872 50.404869,79.267481 40.287236,108.86774 34.426911,138.63795 22.416163,189.64033 17.78885,242.92961 25.780677,294.93728 c 5.106096,32.23417 11.057028,64.60794 22.392916,95.27781 13.479342,35.90012 38.974607,69.46226 76.144837,82.91501 27.93718,10.93473 54.60541,6.72414 83.9641,7.53695 13.63487,0 24.96913,0 38.604,0 0.2713,-39.75457 -0.23921,-79.53817 0.7,-119.27522 4.44911,-60.56565 57.97765,-113.83582 116.49553,-129.10577 28.59598,-7.98504 58.75298,-3.97369 88.12613,-4.95196 65.78932,-0.1699 131.59413,0.33658 197.37399,-0.24727 46.56452,-2.36951 89.41253,-33.74388 105.96031,-76.7788 8.4927,-20.25194 9.04154,-42.50123 8.36849,-64.136866 -0.0327,-28.519022 -0.0719,-58.538168 -0.10062,-87.0571928 -160.2708,-0.8222611 -320.54247,-0.1692627 -480.8154,0.027 -31.78694,0.021047 -63.57472,-0.0424765 -95.36116,0.0302428 z" + id="path1948_1_" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccccccccccc" /> + + <linearGradient + id="path1950_3_" + gradientUnits="userSpaceOnUse" + x1="1220.123" + y1="134.5405" + x2="783.30792" + y2="752.55798" + gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)" + xlink:href="#linearGradient3778"> + <stop + offset="0" + style="stop-color:#fae13f;stop-opacity:1;" + id="stop24" /> + <stop + offset="1" + style="stop-color:#facc2b;stop-opacity:1;" + id="stop26" /> + </linearGradient> + <path + id="path1950_1_" + d="m 789.03685,-0.868 v 105.496 c 0,81.799 -71.19267,150.631 -152.37034,150.631 H 393.0493 c -66.73298,0 -121.9513,55.632 -121.9513,120.727 v 226.221 c 0,64.39 255.53022,396.793 255.53022,396.793 0,0 231.98858,-328.234 231.98858,-396.793 V 511.666 H 515.00061 V 481.483 H 758.6168 880.57116 c 70.88777,0 97.30706,-48.163 121.95034,-120.447 25.4697,-74.417 24.3844,-145.985 0,-241.457 C 985.00406,50.85 951.54825,-0.868 880.57116,-0.868 H 789.03685 z M 652.0177,572.026 c 25.28282,0 45.76511,20.179 45.76511,45.137 0,25.041 -20.48229,45.409 -45.76511,45.409 -25.19452,0 -45.76716,-20.368 -45.76716,-45.409 0,-24.958 20.57264,-45.137 45.76716,-45.137 z" + style="fill:url(#linearGradient3005);fill-opacity:1;" + inkscape:connector-curvature="0" /> +</g> +</svg>
\ No newline at end of file diff --git a/docs/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..09e3bc857 --- /dev/null +++ b/docs/system/overview.rst @@ -0,0 +1,26 @@ +.. _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 + webserver_evaluation.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..80e5fa4cc --- /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 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 Binary files differnew file mode 100644 index 000000000..98ab31a69 --- /dev/null +++ b/docs/system/pyload_DataLayout.png diff --git a/docs/system/pyload_PluginHierarchy.png b/docs/system/pyload_PluginHierarchy.png Binary files differnew file mode 100644 index 000000000..118d3a7a8 --- /dev/null +++ b/docs/system/pyload_PluginHierarchy.png diff --git a/docs/system/pyload_ad_Hoster.png b/docs/system/pyload_ad_Hoster.png Binary files differnew file mode 100644 index 000000000..0ee064edc --- /dev/null +++ b/docs/system/pyload_ad_Hoster.png diff --git a/docs/system/pyload_sd_Hoster.png b/docs/system/pyload_sd_Hoster.png Binary files differnew file mode 100644 index 000000000..e629a1949 --- /dev/null +++ b/docs/system/pyload_sd_Hoster.png diff --git a/docs/system/webserver_evaluation.rst b/docs/system/webserver_evaluation.rst new file mode 100644 index 000000000..607ef1dad --- /dev/null +++ b/docs/system/webserver_evaluation.rst @@ -0,0 +1,88 @@ +.. _webserver_evaluation: + +==================== +Webserver Evaluation +==================== + +pyLoad supports all kind of webserver that are usable with bottle.py [1]_. +For this reason we evaluted each of them to find the ones that are worth to be supported in pyLoad. The results of this +evaluation make sure pyload can select the most suited webserver with its auto selecting algorithm. + +First selection +--------------- + +The first step was to take a short look at every webserver. For some it was not needed to further inspect them, +since they don't meet our requirements. + +================== ================================================================== +Disregarded server Reason +================== ================================================================== +paste threaded server, no improvement to bundled one +twisted Too heavy (30 MB RAM min), far more complex as what we need +diesel Problems with setup, no default packages, Not working in tests +gunicorn Preforking server, messes many things up in our use-case +gevent Not usuable with several threads +gae Google App Engine, not for personal maschines +rocket threaded server, seems not better than bundled one +waitress no large improvement to bundled threaded +================== ================================================================== + +pyLoad has an threaded server bundled itself. All threaded server that were tested seems not better than this +implementation and thus were not further benchmarked. "flup", known as "fastcgi" in pyload, serves a different +use-case and is not taken into consideration here. + +Comparision +----------- + +The remaining servers, were evaluated for different criteria. We ran the following benchmark with different options: + + ab -n 15000 -c 1 http://127.0.0.1:8001/login + +This benchmark was ran with -c 1, -c 5, as well as -k option to test performance with more concurrency and keep-alive +feature, we use time per request (mean, across all concurrent requests) for comparision. + +Additionally we collected RAM usage statistic before (b.) (only 2-3 pages retrieved) and after (a.) the benchmarks were run. +The comparision also includes some notes and available packages or features, especially SSL is of interest. + +========== ======== ======== ====== ====== ======= ======= === ============= ================================ +Server RAM (b.) RAM (a.) -c 1 -c 5 -c 1 -k -c 5 -k SSL Packages Notes +========== ======== ======== ====== ====== ======= ======= === ============= ================================ +wsgiref 21.7 22.6 1.240 1.179 1.312 1.513 No Included +threaded 25.5 28.0 0.912 1.139 0.656 0.784 Yes Included +tornado 23.8 25.9 0.874 0.935 - - Yes mac,deb,arch + freebsd +fapws3 22.3 23.8 0.740 0.733 0.786 0.594 No pip Very reliable under load, + problem with shutdown, will + need patches for integration +meinheld 22.3 23.7 0.622 1.001 1.076 1.388 Yes pip Segfaults when shutdown +eventlet 25.0 26.0 1.021 1.031 0.755 0.740 Yes mac,deb Struggles a bit under load + freebsd More ram with -k (27.6) + +bjoern 21.7 23.2 0.623 0.513 - - No git, freebsd memory-leak with faulty -k + packages out of date +========== ======== ======== ====== ====== ======= ======= === ============= ================================ + +"wsgiref" is a standard implementation shipped with python and within pyLoad known as builtin. +"threaded" is taken from cherryPy and also included with pyLoad. +The keep-alive implementation of "ab" is not 100% compliant, some server struggle with it. + +Conclusion +---------- + +The wsgiref server is known to show strange performance on some system and is therefore not selected by default anymore. +The included threaded server has all needed functions, including SSL, and is usable without any other packages. +Threaded will be selected in case none of the other server is installed. + +Our auto-select will favor RAM usage over performance too choose the most lightweight server as possible. +Activating SSL will decrease the options, many lightweight servers don't include SSL by choice. +They suggest tools like pound [2]_, stunnel [3]_, or any other reverse proxy capable server. Also these that are capable +of SSL suggest using other tools, their SSL performance was not tested here. + +pyLoad will select a server in following order: +fapws3 -> meinheld -> bjoern -> tornado -> eventlet + +.. rubric:: Footnotes + +.. [1] https://bitbucket.org/spoob/pyload/src/127adb41465712548949ea872a5453e4b0b0fbb8/module/lib/bottle.py?at=default#cl-2555 +.. [2] http://www.apsis.ch/pound/ +.. [3] https://www.stunnel.org/index.html
\ No newline at end of file diff --git a/docs/write_hooks.rst b/docs/write_hooks.rst deleted file mode 100644 index dd60367b7..000000000 --- a/docs/write_hooks.rst +++ /dev/null @@ -1,162 +0,0 @@ -.. _write_hooks: - -Hooks -===== - -A Hook is a python file which is located at :file:`module/plugins/hooks`. -The :class:`HookManager <module.HookManager.HookManager>` will load it automatically on startup. Only one instance exists -over the complete lifetime of pyload. Your hook can interact on various events called by the :class:`HookManager <module.HookManager.HookManager>`, -do something complete autonomic and has full access to the :class:`Api <module.Api.Api>` and every detail of pyLoad. -The UpdateManager, CaptchaTrader, UnRar and many more are realised as hooks. - -Hook header ------------ - -Your hook needs to subclass :class:`Hook <module.plugins.Hook.Hook>` and will inherit all of its method, make sure to check its documentation! - -All Hooks should start with something like this: :: - - from module.plugins.Hook import Hook - - class YourHook(Hook): - __name__ = "YourHook" - __version__ = "0.1" - __description__ = "Does really cool stuff" - __config__ = [ ("activated" , "bool" , "Activated" , "True" ) ] - __threaded__ = ["downloadFinished"] - __author_name__ = ("Me") - __author_mail__ = ("me@has-no-mail.com") - -All meta-data is defined in the header, you need at least one option at ``__config__`` so the user can toggle your -hook on and off. Dont't overwrite the ``init`` method if not neccesary, use ``setup`` instead. - -Using the Config ----------------- - -We are taking a closer look at the ``__config__`` parameter. -You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``. -When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name,value``. - - -Interacting on Events ---------------------- - -The next step is to think about where your Hook action takes places. - -The easiest way is to overwrite specific methods defined by the :class:`Hook <module.plugins.Hook.Hook>` base class. -The name is indicating when the function gets called. -See :class:`Hook <module.plugins.Hook.Hook>` page for a complete listing. - -You should be aware of the arguments the Hooks are called with, whether its a :class:`PyFile <module.PyFile.PyFile>` -or :class:`PyPackage <module.PyPackage.PyPackage>` you should read its related documentation to know how to access her great power and manipulate them. - -A basic excerpt would look like: :: - - from module.plugins.Hook import Hook - - class YourHook(Hook): - """ - Your Hook code here. - """ - - def coreReady(self): - print "Yay, the core is ready let's do some work." - - def downloadFinished(self, pyfile): - print "A Download just finished." - -Another important feature to mention can be seen at the ``__threaded__`` parameter. Function names listed will be executed -in a thread, in order to not block the main thread. This should be used for all kind of longer processing tasks. - -Another and more flexible and powerful way is to use event listener. -All hook methods exists as event and very useful additional events are dispatched by the core. For a little overview look -at :class:`HookManager <module.HookManager.HookManager>`. Keep in mind that you can define own events and other people may listen on them. - -For your convenience it's possible to register listeners automatical via the ``event_map`` attribute. -It requires a `dict` that maps event names to function names or a `list` of function names. It's important that all names are strings :: - - from module.plugins.Hook import Hook - - class YourHook(Hook): - """ - Your Hook code here. - """ - event_map = {"downloadFinished" : "doSomeWork", - "allDownloadsFnished": "someMethod", - "coreReady": "initialize"} - - def initialize(self): - print "Initialized." - - def doSomeWork(self, pyfile): - print "This is equivalent to the above example." - - def someMethod(self): - print "The underlying event (allDownloadsFinished) for this method is not available through the base class" - -An advantage of the event listener is that you are able to register and remove the listeners at runtime. -Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name", function)` and see doc for -:class:`HookManager <module.HookManager.HookManager>`. Contrary to ``event_map``, ``function`` has to be a reference -and **not** a `string`. - -We introduced events because it scales better if there a a huge amount of events and hooks. So all future interaction will be exclusive -available as event and not accessible through overwriting hook methods. However you can safely do this, it will not be removed and is easier to implement. - - -Providing RPC services ----------------------- - -You may noticed that pyLoad has an :class:`Api <module.Api.Api>`, which can be used internal or called by clients via RPC. -So probably clients want to be able to interact with your hook to request it's state or invoke some action. - -Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: :: - - from module.plugins.Hook import Hook, Expose - - class YourHook(Hook): - """ - Your Hook code here. - """ - - @Expose - def invoke(self, arg): - print "Invoked with", arg - -Thats all, it's available via the :class:`Api <module.Api.Api>` now. If you want to use it read :ref:`access_api`. -Here is a basic example: :: - - #Assuming client is a ThriftClient or Api object - - print client.getServices() - print client.call(ServiceCall("YourHook", "invoke", "an argument")) - -Providing status information ----------------------------- -Your hook can store information in a ``dict`` that can easily be retrievied via the :class:`Api <module.Api.Api>`. - -Just store everything in ``self.info``. :: - - from module.plugins.Hook import Hook - - class YourHook(Hook): - """ - Your Hook code here. - """ - - def setup(self): - self.info = {"running": False} - - def coreReady(self): - self.info["running"] = True - -Usable with: :: - - #Assuming client is a ThriftClient or Api object - - print client.getAllInfo() - -Example -------- - Sorry but you won't find an example here ;-) - - Look at :file:`module/plugins/hooks` and you will find plenty examples there. diff --git a/docs/write_plugins.rst b/docs/write_plugins.rst 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 Binary files differdeleted file mode 100644 index 66170aae7..000000000 --- a/icons/abort.png +++ /dev/null diff --git a/icons/add_small.png b/icons/add_small.png Binary files differdeleted file mode 100644 index 4dc88b09b..000000000 --- a/icons/add_small.png +++ /dev/null diff --git a/icons/clipboard.png b/icons/clipboard.png Binary files differdeleted file mode 100644 index 9ba608eba..000000000 --- a/icons/clipboard.png +++ /dev/null diff --git a/icons/close.png b/icons/close.png Binary files differdeleted file mode 100644 index 66170aae7..000000000 --- a/icons/close.png +++ /dev/null diff --git a/icons/edit_small.png b/icons/edit_small.png Binary files differdeleted file mode 100644 index eb76e21b4..000000000 --- a/icons/edit_small.png +++ /dev/null diff --git a/icons/logo-gui.png b/icons/logo-gui.png Binary files differdeleted file mode 100644 index 5994b274d..000000000 --- a/icons/logo-gui.png +++ /dev/null diff --git a/icons/logo.png b/icons/logo.png Binary files differdeleted file mode 100644 index 72a95b740..000000000 --- a/icons/logo.png +++ /dev/null diff --git a/icons/pull_small.png b/icons/pull_small.png Binary files differdeleted file mode 100644 index 432ad321f..000000000 --- a/icons/pull_small.png +++ /dev/null diff --git a/icons/push_small.png b/icons/push_small.png Binary files differdeleted file mode 100644 index 701fc69e3..000000000 --- a/icons/push_small.png +++ /dev/null diff --git a/icons/pyload-gui.ico b/icons/pyload-gui.ico Binary files differdeleted file mode 100644 index 00a1a53ff..000000000 --- a/icons/pyload-gui.ico +++ /dev/null diff --git a/icons/pyload.ico b/icons/pyload.ico Binary files differdeleted file mode 100644 index 58b1f4b89..000000000 --- a/icons/pyload.ico +++ /dev/null diff --git a/icons/pyload2.ico b/icons/pyload2.ico Binary files differdeleted file mode 100644 index c2b497986..000000000 --- a/icons/pyload2.ico +++ /dev/null diff --git a/icons/refresh1_small.png b/icons/refresh1_small.png Binary files differdeleted file mode 100644 index ce4f24efc..000000000 --- a/icons/refresh1_small.png +++ /dev/null diff --git a/icons/refresh_small.png b/icons/refresh_small.png Binary files differdeleted file mode 100644 index 1ffd18d97..000000000 --- a/icons/refresh_small.png +++ /dev/null diff --git a/icons/remove_small.png b/icons/remove_small.png Binary files differdeleted file mode 100644 index bf99763e8..000000000 --- a/icons/remove_small.png +++ /dev/null diff --git a/icons/toolbar_add.png b/icons/toolbar_add.png Binary files differdeleted file mode 100644 index 17003e9f0..000000000 --- a/icons/toolbar_add.png +++ /dev/null diff --git a/icons/toolbar_pause.png b/icons/toolbar_pause.png Binary files differdeleted file mode 100644 index b7a727b71..000000000 --- a/icons/toolbar_pause.png +++ /dev/null diff --git a/icons/toolbar_remove.png b/icons/toolbar_remove.png Binary files differdeleted file mode 100644 index 1e9c00e16..000000000 --- a/icons/toolbar_remove.png +++ /dev/null diff --git a/icons/toolbar_start.png b/icons/toolbar_start.png Binary files differdeleted file mode 100644 index 1123266e6..000000000 --- a/icons/toolbar_start.png +++ /dev/null diff --git a/icons/toolbar_stop.png b/icons/toolbar_stop.png Binary files differdeleted file mode 100644 index b388e3d72..000000000 --- a/icons/toolbar_stop.png +++ /dev/null diff --git a/locale/README.md b/locale/README.md new file mode 100644 index 000000000..215b98a6e --- /dev/null +++ b/locale/README.md @@ -0,0 +1,55 @@ +# Localization + +The localization process take place on Crowdin: + +http://translate.pyload.org + +or + +http://crowdin.net/project/pyload + +## Add a tip for translators +If you want to explain a translatable string to make the translation process easier you can do that using comment block starting with `L10N:`. For example: + +```python +# L10N: Here the tip for translators +# Thanks +print _("A translatable string") +``` + +Translators will see: + +``` +L10N: Here the tip for translators +Thanks +``` + +## Updating templates + +To update POT files run: + +`paver generate_locale` + +to automatically upload the updated POTs on Crowdin for the localization process just run: + +`paver upload_translations -k [api_key]` + +the API Key can be retrieved in the Settings panel of the project on Crowdin. + +## Retrieve updated PO files + +Updated PO files can be automatically download from Crowdin using: + +`paver download_translations -k [api_key]` + +This is allowed only to administrators, users can download the last version of the translations using the Crowdin web interface. + +## Compile PO files + +MO files can be generated using: + +`paver compile_translations` + +To compile a single file just use `msgfmt`. For example to compile a core.po file run: + +`msgfmt -o core.mo core.po` diff --git a/locale/af/LC_MESSAGES/cli.po b/locale/af/LC_MESSAGES/cli.po new file mode 100644 index 000000000..57be6fd9e --- /dev/null +++ b/locale/af/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Afrikaans\n" +"Language: af_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/af/LC_MESSAGES/core.po b/locale/af/LC_MESSAGES/core.po new file mode 100644 index 000000000..0d4e3c017 --- /dev/null +++ b/locale/af/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Afrikaans\n" +"Language: af_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/af/LC_MESSAGES/plugins.po b/locale/af/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..35666b8c9 --- /dev/null +++ b/locale/af/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Afrikaans\n" +"Language: af_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/af/LC_MESSAGES/setup.po b/locale/af/LC_MESSAGES/setup.po new file mode 100644 index 000000000..6858d2427 --- /dev/null +++ b/locale/af/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Afrikaans\n" +"Language: af_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/af/LC_MESSAGES/webUI.po b/locale/af/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..b30f982c3 --- /dev/null +++ b/locale/af/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Afrikaans\n" +"Language: af_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/ar/LC_MESSAGES/cli.po b/locale/ar/LC_MESSAGES/cli.po new file mode 100644 index 000000000..a5822f84f --- /dev/null +++ b/locale/ar/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Arabic\n" +"Language: ar_SA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "إضافة حزمة:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "أدخل اسماً للحزمة الجديدة" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "الحزمة: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "تحليل الروابط التي تريد إضافتها." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "اكتب %s عند الانتهاء." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "تم اضافة الروابط: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " العودة إلى القائمة الرئيسية" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "إدارة الحزم:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "إدارة الروابط:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "ما اللذي تريد نقلة ؟" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "ما اللذي تريد حذفة ؟" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "ما اللذي تريد اعادة تشغيلة ؟" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "اختر ما تريد أن تفعل، أو إدخال رقم الحزمة." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "حذف" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "نقل" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "إعادة تشغيل" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " -السابقة" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " -التالي" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " واجهة سطر الأوامر" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s التنزيلات:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " السرعة: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " الحجم: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " الانتهاء في: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " معرف: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "في انتظار: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "الحالة:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "تم إيقاف مؤقتاً" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "تشغيل" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "السرعة الإجمالية" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "الملفات الموجودة في قائمة الانتظار" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "المجموع" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "القائمة:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " اضافة روابط" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " إدارة قائمة الانتظار" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " إدارة جامع الروابط" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " (عدم) ايقاف مؤقت للخادم" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " قتل الخادم" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " إنهاء" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "الرجاء استخدام بناء الجملة التالي: إضافة < اسم الحزمة > <رابط><رابط2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "التحقق %d من الروابط:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "الملف غير موجود." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "تم انهاء باي لود" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "اطبع حالة الخادم" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "اكتب التنزيلات اللتي في قائمة الانتظار" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "اكتب التنزيلات اللتي في جامع الروابط" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "اضافة حزمة الى قائمة الانتظار" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "اضافة حزمة إلى جامع الروابط" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "حذف الملفات من قائمة الانتظار/جامع الروابط" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "حذف حزم من قائمة الانتظار/جامع الروابط" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "نقل الحزم من قائمة انتظار إلى جامع الروابط أو العكس بالعكس" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "إعادة تشغيل الملفات" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "إعادة تشغيل الحزم" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "تحقق من حالة الاتصال، يعمل مع المحتوى المحلي" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "التحقق من حالة الاتصال من الملف المحتوى" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "إيقاف الخادم مؤقتا" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "متابعة التحميل" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "تبديل إيقاف/عدم ايقاف الإيقاف المؤقت" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "قتل الخادم" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "قائمة الأوامر:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "لا يمكن كتابة ملف اعدادات المستخدم" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "أنت بحاجة إلى py-openssl للاتصال بهذا الاساس من باي لود." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "العنوان: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "المنفذ: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "اسم المستخدم: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "كلمة المرور: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "بيانات تسجيل الدخول خاطئة." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "لا يمكن انشاء اتصال %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "الوضع التبادلي تم تجاهله حيث انك مررت بعض الاوامر." + diff --git a/locale/ar/LC_MESSAGES/core.po b/locale/ar/LC_MESSAGES/core.po new file mode 100644 index 000000000..95e13f4d6 --- /dev/null +++ b/locale/ar/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Arabic\n" +"Language: ar_SA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "خطأ عند التنفيذ %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "فشل التفعيل %(اسم)" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "الاضافات المفعلة: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "الاضافات المعطلة: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "تفعيل الاضافات..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "الغاء تفعيل الاضافات..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "شهادة SSL غير موجوده." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "بناء واجهة مستخدم الويب غير متوفر" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "تشغيل واحهة مستخدم الويب في وضع التطوير" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "فشل بدء خادم الويب: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "فشل في استيراد خادم الويب: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "هذا المخدم لا يقدم خدمة SSL، يرجى النظر في استخدام بديلاً من ذلك" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "بدء %(name)s خادم ويب: %(host)s :%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "عن بعد" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "الوصف" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "وصف مطول" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "تم التفعيل" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "المنفذ" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "عنوان" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "سجل" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "الحجم بالكيلو بايت" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "مجلد" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "ملف السجل" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "العد" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "تدوير سجل" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "الصلاحيات" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "اسم المجموعة" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "تغير المجموعة و المستخدم للتنزيلات" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "تغيير وضع ملف التنزيل" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "اسم المستخدم" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "ملف النمط للتنزيلات" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "تغير المجموعة للعمليات قيد التشغيل" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "وضع مجلد الصلاحيات" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "تغير المستخدم للعمليات قيد التشغيل" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "عام" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "اللغة" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "مجلد التنزيلات" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "استخدم المجموع الاختباري" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "إنشاء مجلد لكل حزمة" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "وضع التصحيح" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "الحد الادنى للمساحة الحرة (ميغابايت)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "اولوية وحدة المعالجة المركزية" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "شهادة SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "مفتاح SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "واجهة ويب" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "النماذج" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "بادئة المسار" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "خادم" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "الخادم المحدد الافضل" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "عنوان انترنت" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "استخدام HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "وضع التطوير" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "البروكسى" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "استخدام البروكسى" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "كلمة المرور" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "البروتوكول" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "إعادة الاتصال" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "نهاية" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "استخدام إعادة الاتصال" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "الأسلوب" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "بدء" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "تنزيل" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "العدد الاقصى التنزيلات المتوازيية (التنزيلات في نفس الوقت)" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "حد الاقصى لسرعة التنزيل" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "واحهة التنزيل لربط (موقع الانترنت او الاسم)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "تخطي الملفات الموجودة حاليا" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "سرعة التنزيل القصوى في كيلو بايت/ثانية" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "السماح IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "الحد الاعلى للاتصالات للتنزيل الواحد" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "اعادة تشغيل التنزيلات الفاشلة عند بدى التشغيل" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "وقت التنزيل" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "فشل التنزيل على اجزاء, العودة الى التنزيل على اتصال واحد | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "اضافة حزمة %(name)s كمجلد %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "اضافة %d روابط للحزمة" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "اضافة حساب غير معروفة %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "الاستبيان" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "طلب كلمة التحقق" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "الرجاء حل كلمة التحقق." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "خطاء في الواجهة الخلفيه للبرنامج " + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "بدء %(اسم): %(عنوان): %(منفذ)" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "فشل في تحميل الواجهه الخلفيه للبرنامج %(اسم) | %(خطاء)" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "لا شيء" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "غير متصل" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "متصل" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "في قائمة الانتظار" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "تم إيقاف مؤقتاً" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "تم الانتهاء" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "تخطي" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "فشل" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "البدء" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "انتظار" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "جاري التنزيل" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "مؤقت. غير متصل" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "الغاء" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "فك تشفير" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "تجهيز" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "مخصص" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "غير معروف" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "انتهت الحزمه: %" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "مستخدم '%s' يحاول تسجيل دخول" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "تم تساتقبال اشارة انهاء" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "باي لود يعمل حاليا بمعرف العملية %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "فشل في تغيير المجموعه: %" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "فشل في تغيير المستخدم: %" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "بدء" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "استخدام مجلد المنزل: %" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "تم مسح جميع الروابط" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "وقت التنزيل: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "المساحه الحره: %" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "تفعيل الحسابات..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "اعادة تشغيل التنزيلات الفاشلة..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "باي ولد محدث و يعمل" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "اعادة تشغيل باي لود" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "اغلاق باي لود" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "جاري ايقاف التشغيل..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "خطاء اثناء ايقاف التشغيل " + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "تم انهاء باي لود من الطرفيه" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "تم حذف قاعدة البيانات بسبب إصدار غير متوافق." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "فشل في فك تشفير" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "تم فك تشفير %(count)d الروابط في الحزمة %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "لا يوجد روابط تم فك تشفيرها" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "فشل جلب المعلومات ل %(name)s | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "فشل في اعادة الاتصال: %" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "البرنامج النصي لاعادة الاتصال غير متوفر!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "بدء اعادة الاتصال" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "فشل في تنفيذ البرنامج النصي لاعادة الاتصال!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "تمت اعادة الاتصال, IP جديد: %" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "لم يتبقى مساحة كافية على الجهاز" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "بدء التنزيل: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "تم الانتهاء من التنزيل: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "الاضافة %s تفتقد الى وظيفة." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "تم الغاء التنزيل: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "تم اعادة تشغيل التنزيل: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "التنزيل غير متصل: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "التنزيل غير متصل مؤقتا: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "فشل التنزيل: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "لايمكن الوصول الى الخادم او اعادة الاتصال, الانتظار 1 دقيقة و اعادة المحاولة." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "تم تخطي التنزيل: %(name)s بسبب %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "خطأ داخلي في الخادم" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "حدث خطأ" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "خطأ في استيراد %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "لم يتم العثور على محرك js, يرجى تثبيت Spidermonkey, ossp-js, pyv8 او rhino" + diff --git a/locale/ar/LC_MESSAGES/plugins.po b/locale/ar/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..bd8b7fa78 --- /dev/null +++ b/locale/ar/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Arabic\n" +"Language: ar_SA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "فشل التنزيل على اجزاء, العودة الى التنزيل على اتصال واحد | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "pil و tesseract غير مثبت ولايوجد عميل متصل لفك كلمة التحقق" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "لم يتم الحصول على نتيجة لكلمة التحقق في الوقت المحدد." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "فشل في اعداد المستخدم و المجموعه:%s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "ملف غير موجود او بروتوكول غير مدعوم" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: بيانات المشاركة (تحميل مباشر)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "جاري التحميل من عنوان الانترنت هذا, انتظر ل 60 ثانية" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "رمز تاكيد غير صالح, سوف يتم اعادة التنزيل" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: لا يوجد مساحات فارغة" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "تحناج لحساب مدفوع من اجل هذا الملف" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "تم الابلاغ عن اسم الملف غير صالح" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "الرجاء إدخال حسابك في %s أو إلغاء تنشيط هذة الاضافة" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "فشل في فك التشفير" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "لا يوجد مفتاح ملف مقدم في عنوان الانترنت" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "رمز خطاء:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "خطاء تنزيلات متوازية, الان انتظر 60 ثانية." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "لم يتم تسجيل الدخول." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "مفتاح واحهة برمجة التطبيقات غير صالح" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: لم يتبقى لديك بيانات كافية" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "تم تجاوز البيانات المتوفرة" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "مطلوب تصريح (اسم مستخدم: كلمة مرور)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "الملف غير متوفر مؤقتا" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "ضغط الشبكة: الانتظار بين التنزيلات %d." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "ضغط الشبكة: الانتظار من اجل كلمة التحقق%d." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "الملف اللذي تم تنزيلة فارغ" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "هناك رمز HTML في الملفات اللتي تم تحميلها (%s)...خطاء اعادة توجية؟سوف يتم اعادة تشغيل التنزيل." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "رابط_طويل: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "لا يمكن تسجيل الدخول بهذا الحساب %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "كلمة مرور خاطئة" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "الحصول على معلومات الحساب ل %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "خطاء: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "الوقت لديك %s مهيء بطريقة خاطئة, استخدم: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "الحساب %s ليس فية كمية بيانات كافية, اعد التحقق خلال 30 دقيقة" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "انتهت مدة الحساب %s, تحقق مرة اخرى في 1 ساعة" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "تسجيل الدخول بستخدام %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "خطاء في تنفيذ الاضافات: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "قم بتفعيل التحميل المباشر في حساب Bitshare لديك" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "لقد وصلت الى الحد الاقصى للتنزيل" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "يرجى اضافة حساب rehost.to اولا ثم اعاده تشغيل باي لود" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "سكربت مثبت ل %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "سكربت غير قابل للتنفيذ:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "خطاء في %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%sرصيد متبقي" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "لايمكن ارسال رد." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "حسابك في CaptchaTrader لا يحتوي رصيد كافي" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "هوية تحقق جديدة من upload: %s :%s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "حسابك للتحقق 9kw.eu لا يحتوي على رصيد كافي" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "يرجى اضافة حساب rehost.to اولا ثم اعاده تشغيل باي لود" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "اضافه %s من HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "ضغط و تحميل: منفذ 9666 قيد الاستخدام" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "انتهت الحزمه: %" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "انتهى التحميل: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "لم يتبقى لديك رصيد كافي في حساب ExpertDecoders لديك" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** تم تحديث الاضافات, يرجى اعادة تشغيل باي لود ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "تم تحديث الاضافات و اعادة تشغيلها" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "لا يوجد تحديثات للاضافات" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "لايوجد تحديثات ل باي لود" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** نسخه جديده %s من باي لود متوفره ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** احصل عليها من هنا: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "غير قادر على الاتصال بالخادم من اجل التحديث" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "نسخه جديدة من %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "حصل خطاء عند التحديث %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "نسخة غير متطابقة" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "لا %s مثبت" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "لا يمكن تفعيل %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "تم التفعيل" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "لا اضافات استخراج مفعلة" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "الحزمه %s في قائمة الانتظار للاستخراج لاحقا" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "تفحص الحزم %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "استخراج الى %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "لم يتم العثور على ملفات للاستخراج" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "فك الظغط" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "محمي بكلمة مرور" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "كلمة مرور خاطئة" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "حذف %s الملفات" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "تم الانتهاء من الاستخراج" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "خطأ في الأرشيف" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC غير متطابق" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "خطاء غير معروف" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "فشل في تحديد المستخدم و المجموعة" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "لم يتم العثور على قائمة Crypter" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "قائمة Crypter فارغة" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "انتهى التنزيل: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "طلب كلمة تحقق جديد: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "اجب ب 'c %s نص على كلمة التحقق'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "الرجاء إضافة حساب premiumize.me صالح أولاً وإعادة تشغيل باي لود." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d رصيد متبقي" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "تم التفعيل %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "لا مستضيف محمل" + diff --git a/locale/ar/LC_MESSAGES/setup.po b/locale/ar/LC_MESSAGES/setup.po new file mode 100644 index 000000000..ea8bc9f24 --- /dev/null +++ b/locale/ar/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Arabic\n" +"Language: ar_SA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "هل تريد اعداد باي لود عبر واجهة ويب؟" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "تحتاج متصفح واتصال على هذا الكمبيوتر لذلك." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "وسيكون العنوان Url: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "بدء تشغيل اعداد واجهة الويب الاولي؟" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "مرحبا بكم في مساعد اعداد باي لود ." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "سوف يتم فحص نظامك وعمل تثبيت اولي من اجل تشغيل باي لود." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "القيمة الموجودة في الأقواس [] هي دائماً القيمة الافتراضية،" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "في حال كنت لا ترغب في تغييرها أو كنت غير متأكد من ما تختار، فقط اضغط أدخال." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "لا تنسى: يمكنك دائماً إعادة تشغيل هذا المساعد بستخدام--setup أو-s المعلمة، عند بدء تشغيل pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "إذا كان لديك أي مشاكل مع هذا المساعد اضغط CTRL + C،" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "للانهاء، وعدم السماح له البدئ مع pyLoadCore تلقائياً بعد الآن." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "عندما تكون على استعداد للتحقق من النظام، اضغط مفتاح الادخال." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "مميزات مفقودة: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "لا تتوفر py-تشفير" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "أنت بحاجة إلى هذا إذا كنت تريد فك تشفير الملفات المحتواة." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "لا تتوفر خدمة SSL" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "وهذا مطلوب إذا كنت ترغب في تأسيس اتصال أمن إلى مركز البرنامج أو واحهة الويب." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "إذا كنت ترغب فقط في الوصول محلياً إلى باي لود SSL ليست مفيدة." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "لا يوجد تاكيد كلمة تحقق متوفر" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "مطلوب فقط من اجل بعض الخوادم و كمستخدم مجاني." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "لا يوجد محرك نصوص جافا" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "سوف تحتاج هذا من اجل بعض روابط اضغط لتحمل.تثبيت Spidermonkey,ossp-js, pyv8 or rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "تستطيع انهاء التثبيت الان و اصلاح بعض الاعتماديات ان اردت." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "الاستمرار في التنصيب؟" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "هل تريد تغير مكان الاعدادات؟ الحالي هو %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "اذا كنت تستخدم باي لود على خادم او على مجلد المنزل الواقع على فلاش داخلية قد تكون فكره جيدة لتغيرة." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "تغيير مسار الاعداد؟" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "هل تريد اعداد الاعدادات الأساسية وبيانات الدخول؟" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "هذا مستحسن من اجل التشغيلة الاولى." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "صنع الاعدادات الاساسية؟" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "هل تريد اعداد ssl؟" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "اعداد ssl؟" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "هل تريد اعداد واحهة الويب؟" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "اعداد واجهة الويب؟" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "تم انهاء التثبيت بنجاح." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "اضغظ مفتاح الادخال للانهاء و اعادة تشغيل باي لود" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "واحهة الويب تعمل من اجل الاعداد." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "# # الإعداد الأساسي # #" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "بيانات تسجيل الدخول التالية صالحة لواجهة سطر الاوامر, واجهة المستخدم و واجهة الويب." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "اسم المستخدم" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "العملاء الخارجين (واجهة المستخدم الرسومية, واجهة سطر الاوامر او غيرها) تحتاج الى الوصول عن بعد لكي تعمل من خلال الشبكة." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "على اي حال, اذا اردت ان تستخدم فقط واجهة الويب يمكنك تعطيلة لحفظ ذاكرة الوصول العشوائي." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "تفعيل الوصول عن بعد" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "اللغة" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "مجلد التنزيلات" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "العدد الاقصى التنزيلات المتوازيية (التنزيلات في نفس الوقت)" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "استخدام إعادة الاتصال؟" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "موقع البرنامج النصي لاعادة الاتصال" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "# # اعداد واجهة الويب # #" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "تنشيط واجهة الويب؟" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "عنوان الواجهة, اذا قمت بستخدام 127.0.0.1 او localhost, واجهة الويب سوف تكون قابلة للوصول محليا فقط." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "عنوان" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "المنفذ" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "يقدم باي لود خوادم متعددة للواجهة الخلفية، الآن بعد شرحاً موجزاً." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "الخادم الافتراضي، هذا الخادم يوفر SSL وهو بديل جيد ل builtin." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "يمكن استخدامها بواسطة أباتشي، lighttpd، يتطلب منك اعدادهم, وهي ليست مهمة سهلة جداً." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "بديل سريع جداً مكتوبة في C، يتطلب libev و معرفة لنكس." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "يمكنك الحصول علية من هنا : https://github.com/jonashaag/bjoern, قم بتجميعة برمجيا" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "وقم بنسخ bjoern.so الى pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "انتباه: في بعض الحالات النادرة الخادم المدمج لا يعمل، إذا لاحظت وجود مشاكل مع واجهة الويب" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "عد إلى هنا وقم بتغير الخادم مدمج لذالك المذكور هنا." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "خادم" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "# # إعداد SSL # #" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "تنفيذ هذه الأوامر من مجلد اعداد باي لود لانتاج شهادات ssl:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "اذا انتهيت وكل شيء جرى بشكل صحيح, تستطيع تفعيل ssl الان." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "تفعيل SSL؟" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "حدد الإجراء" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1-إنشاء/تحرير المستخدم" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2-قائمة المستخدمين" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3-إزالة المستخدم" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4-إنهاء" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "المستخدمين" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "تعيين مكان اعداد جديد، الاعداد الحالي لن يتم نقلة!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "مسار الاعداد" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "مسار الاعداد تغير، سوف يتم اغلاق التنصيب الان، الرجاء إعادة تشغيل للمتابعة." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "اضغط Enter للإنهاء." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "فشل الإعداد مسار التكوين: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "y" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "كلمة المرور: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "كلمة المرور قصيرة جداً. استخدام 4 رموز على الاقل." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "كلمة المرور (مرة أخرى): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "كلمات المرور غير متطابقتين." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "نعم" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "صحيح" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "t" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "لا" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "خاطئة" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "الإدخال غير صالح" + diff --git a/locale/ar/LC_MESSAGES/webUI.po b/locale/ar/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..77b76de44 --- /dev/null +++ b/locale/ar/LC_MESSAGES/webUI.po @@ -0,0 +1,145 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Arabic\n" +"Language: ar_SA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "غير متوفر" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "غير محدود" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "المشرف" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "برنامج الإعداد" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "إضافة حساب" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "الحسابات" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "محلي" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "بحث" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "نوع" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "الجميع" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "تم النتهاء" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "لم ينتهي" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "فشل" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 حزمة\n" +"صيغة الجمع\n" +"%d حزم" +msgstr[1] "1 حزمة %d" +msgstr[2] "1 حزمة\n" +"1 حزمة %d" +msgstr[3] "1 حزم %d" +msgstr[4] "" +msgstr[5] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "مفرد\n" +"1 ملف\n" +"صيغة الجمع\n" +"%d ملفات" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "إضافة حساب" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "يرجى ادخال بيانات حسابك" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "اختار الاضافة" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "يرجى اختيار الاضافة التي تريد اعدادها" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "إضافة" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "إغلاق" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "يرجى التأكيد" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "هل تريد حذف العناصر المحددة؟" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "حذف" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "الغاء" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "اقترح" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "قيد التشغيل..." + diff --git a/locale/bn/LC_MESSAGES/cli.po b/locale/bn/LC_MESSAGES/cli.po new file mode 100644 index 000000000..68f32bb9c --- /dev/null +++ b/locale/bn/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Bengali\n" +"Language: bn_BD\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/bn/LC_MESSAGES/core.po b/locale/bn/LC_MESSAGES/core.po new file mode 100644 index 000000000..1201f5e76 --- /dev/null +++ b/locale/bn/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Bengali\n" +"Language: bn_BD\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/bn/LC_MESSAGES/plugins.po b/locale/bn/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..9a4216b83 --- /dev/null +++ b/locale/bn/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Bengali\n" +"Language: bn_BD\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/bn/LC_MESSAGES/setup.po b/locale/bn/LC_MESSAGES/setup.po new file mode 100644 index 000000000..dbc008d23 --- /dev/null +++ b/locale/bn/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Bengali\n" +"Language: bn_BD\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/bn/LC_MESSAGES/webUI.po b/locale/bn/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..a9a8a2688 --- /dev/null +++ b/locale/bn/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Bengali\n" +"Language: bn_BD\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/ca/LC_MESSAGES/cli.po b/locale/ca/LC_MESSAGES/cli.po new file mode 100644 index 000000000..74fdd242f --- /dev/null +++ b/locale/ca/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Catalan\n" +"Language: ca_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Afegeix Paquet:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Introdueix el nom per el nou paquet" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paquet: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analitza dels enllaços que vols introduir." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Introdueix %s en acabar." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Enllaços afegits: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " torna al menú principal" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Gestiona Paquets:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Gestiona Enllaços:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Què vols moure?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Què vols eliminar?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Què vols reiniciar?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Tria el que vols fer o introdueix un nombre de paquet." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "elimina" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "mou" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "reinicia" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - anterior" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - següent" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Línia de Comandes" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Baixades:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Velocitat: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Mida: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Acabarà en: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "esperant: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Estat:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "pausat" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "executant" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "velocitat Total" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Fitxers en cua" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Total" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menú:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Afegeix enllaços" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Gestiona Cua" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Gestiona Col·lector" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Reprèn/Pausa Servidor" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Mata Servidor" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Surt" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Si us plau empreu aquesta sintaxi: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Comprovant %d enllaç(os):" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "El fitxer no existeix." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad ha finalitzat" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Mostra estat del servidor" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Mostra descàrregues en cua" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Mostra descàrreges en el col·lector" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Afegeix paquet a la cua" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Afegeix paquet al col·lector" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Elimina Fitxers de la Cua/Col·lector" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Elimina Paquets de la Cua/Col·lector" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Mou Paquets de la Cua al Col·lector o viceversa" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Reinicia fitxers" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Reinicia paquets" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Comprova l'estat online, funciona amb contenidor local" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Comprova l'estat online d'un fitxer contenidor" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Pausa el servidor" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "continua les descàrregues" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Canvia pausa/reprèn" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "mata servidor" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Llista de comandes:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "No s'ha pogut escriure el fitxer de configuració d'usuari" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Necessites py-openssl per connectar-te al nucli del pyLoad." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adreça: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Nom d'usuari: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Contrasenya: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Dades d'accés incorrectes." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "No s'ha pogut establir la connexió a %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Mode interactiu ignorat ja que has especificat algunes comandes." + diff --git a/locale/ca/LC_MESSAGES/core.po b/locale/ca/LC_MESSAGES/core.po new file mode 100644 index 000000000..5ce3840b4 --- /dev/null +++ b/locale/ca/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Catalan\n" +"Language: ca_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Error quan s'executa %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "L'activació de %(name)s ha fallat" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Complements activats: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Complementos desactivados: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Activant Plugins..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Desactivant Plugins..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "No s'han trobat els certificats SSL." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "L'interficie web no està disponible" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Executant WebUI en mode de desenvolupament" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Iniciació del servidor web fallat: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Importació del servidor web fallat: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Aquest servidor no ofereix SSL, si us plau considera l'opció d'emprar el servidor amb fils" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Iniciant servidor web %(name)s: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Remot" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Descripció" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Descripció llarga" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Activat" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Registre" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Mida en kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Fitxer de registre" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Compte" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Rotar el Registre" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Permisos" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Nom del grup" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Canviar Grup i Usuari de descàrregues" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Canviar el mode de fixter de descárregues" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Mode de fitxer per descàrregues" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Canviar el grup del procés en execució" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Mode de Permís Carpeta" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Canviar usuari del procés en execució" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Carpeta de Descárregues" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Utilitza Checksum" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Crear una carpeta per a cada paquet" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Mode Depuració" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Mínim Espai Lliure (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Prioritat CPU" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Certificat SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Clau SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Interfície Web" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Plantilla" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Prefix de ruta" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Afavorir servidor específic" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Utilitza HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Mode Desenvolupament" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Utilitza Proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protocol" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Reconnectar" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Final" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Utilitzar Reconnectar" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Mètode" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Iniciar" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Descarregar" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Màxim descàrregues paral·les" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Limitar la velocitat de descàrrega" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Interfície de Descàrrega a associar (IP o Nom)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Ometre fitxers ja existents" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Velocitat de Descàrrega Máx. en kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Permetre IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Connexions Màx. per una descàrrega" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Reiniciar descàrregues fallides en l'arrencada" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Temps de Descàrrega" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Descàrrega per trossos fallida, tornant a la connexió única | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Afegit paquet %(name)s com a carpeta %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Afegits %d enllaços al paquet" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Conta desconeguda del plugin %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Consulta" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Sol·licitud Captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Si us plau resol el captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Error al backend remot: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Iniciant %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "S'ha fallat la carrega del backend %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "cap" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "fora de línia" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "en línia" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "en cua" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "pausat" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "finalitzat" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "saltat" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "fallit" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "començant" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "esperant" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "descarregant" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "temporalment fora de línia" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "avortat" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "desxifrant" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "processant" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "personalitzat" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "deconegut" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paquet finalitzat: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Usuari '%s' intenta iniciar sessió" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "S'ha rebut la senyal de Sortida" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "ja s'estat executant pyLoad amb el pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Ha fallat el canvi de el grup: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Ha fallat el canvi d'usuari: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Començant" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Emprant el directori d'inici: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Tots els enllaços eliminats" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Temps de Descàrrega: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Espai lliure: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Activant Comptes..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Reiniciant descàrregues fallides..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad està en funcionament" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "reiniciant pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad està sortint" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "apagant..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "ha succeït un error mentre s'apagava" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad tancat pel terminal" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Base de dades suprimit a causa de versió incompatible." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Desxifrar fracassat" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Desxifrat %(count)d enllaços en paquet %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Cap enllaç desxifrat" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Obtenint informació per %(name)s failed | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Reconnexió fallida: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "No s'ha trobat l'script de reconnexió!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Iniciant reconnexió" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "L'execució de l'script de reconnexió ha fallat!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Reconnectat, nova IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "No queda suficient espai lliure al dispositiu" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Iniciant descàrrega: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Descàrrega finalitzada: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "El plugin %s troba a faltar una funció." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Descàrrega avortada: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Descàrrega reiniciada: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "La descàrrega està fora de línia: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "La descàrrega està temporalment fora de línia: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Descàrrega fallida: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "No s'ha pogut connectar amb el servidor o la connexió s'ha reiniciat, esperant 1 minuts per tornar-ho a provar." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Descàrrega omitida: %(name)s due to %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Error de Servidor Intern" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "S'ha produït un Error" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Error important %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "No s'ha detectat cap motor js, si us plau instal·la Spidermonkey, ossp-js, pyv8 o rhino" + diff --git a/locale/ca/LC_MESSAGES/plugins.po b/locale/ca/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..cc8f5bf93 --- /dev/null +++ b/locale/ca/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Catalan\n" +"Language: ca_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Descàrrega per trossos fallida, tornant a la connexió única | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil i tesseract no estan instal·lats i no hi ha cap Client connectat per desxifrar captchas" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Cap resultat de captcha obtingut en el temps apropiat." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Aplicació d'Usuari i el Grup ha fallat: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "No existeix el fitxer o protocol no suportat" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Tràfic Compartit (descàrrega directa)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Ja s'està descarregant des de aquesta adreça IP, esperant 60 segons" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Codi d'Autenticació invàlid, la descàrrega serà reiniciada" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: No hi ha espais lliures" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Necessites un compte premium per aquest fitxer" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Nom de fitxer informat invàlid" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Si us plau, introdueixi el seu compte de %s o desactivar aquest plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Desxifrat ha fallat" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Cap clau de fitxer proporcionat a la URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Codi d'error:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Error de descàrrega paral·lela, esperant 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "No connectat." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Clau de API invàlida" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: No queda suficient tràfic" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Tràfic excedit" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Autorització requerida (usuari:contrasenya)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Fitxer temporalment no disponible" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: esperant entre descàrregues %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: esperant per captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "El fitxer descarregat estava buit" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Hi havia Codi HTML en el fitxer (%s) descarregat... error de redirecció? Se reiniciarà la descàrrega." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "No s'ha pogut iniciar sessió amb el compte %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Contrasenya Errònia" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Obtenir Informació de la Compte per %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Error: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "L'hora %s està en un format incorrecte, empra: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "El compte %s no te suficient tràfic, es tornarà a comprovar d'aquí 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "El compte %s està caducat, es tornarà a comprovar d'aquí 1h" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Inicieu la sessió amb %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Error executant complements: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Activa la descàrrega directa al teu compte de Bitshare" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Límit de descàrrega assolit" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Si us plau afegir el seu compte de premium.to primer i reinicieu pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Scripts instal·lats per %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script no executable:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Error en %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s crèdits restants" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "No s'ha pogut enviar la resposta." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "El teu compte de CaptchaTrader no te crèdits suficients" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nou CaptchaID de càrrega: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "El teu compte de 9kw.eu no te crèdits suficients" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Si us plau afegir el seu compte de rehost.to primer i reinicieu pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Afegit %s des de HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: El Port 9666 ja s'està en ús" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paquet finalitzat: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Descàrrega finalitzada : %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "El teu compte de ExpertDecoders no te crèdits suficients" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "* * * Plugins s'han actualitzat, si us plau reinicieu pyLoad * * *" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins actualitzats i recarregats" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "No hi ha actualitzacions de plugins disponibles" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "No hi ha actualitzacions del pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** La nova Versió %s de pyLoad està disponible ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Obtengui'l aquí: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "No es pot connectar al servidor per actualitzacions" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Activat" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/ca/LC_MESSAGES/setup.po b/locale/ca/LC_MESSAGES/setup.po new file mode 100644 index 000000000..4fb47836d --- /dev/null +++ b/locale/ca/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Catalan\n" +"Language: ca_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Benvingut a l'Assistent de Configuració de pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Es comprovarà el teu equip i es farà una configuració bàsica per executar pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "El valor entre claudàtors [] sempre és el valor per defecte," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "en cas de no voler canviar-ho o si estas insegur de que triar simplement pitja enter." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "No oblidis: Sempre pots tornar a l'assistent amb els paràmetres --setup o -s quan inicies el pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "per avortar i no permetre que s'iniciï automàticament amb pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Quan estiguis llest per la comprovació del sistema pitja enter." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crytop no disponible" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Això es necessari si vols desxifrar els contenidors de fitxers." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL no disponible" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Això es necessari si vols establir connexions segures amb el nucli o la interfície web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "reconeixedor de Captchas no disponible" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Només necessari per alguns proveïdors com a usuari gratuït." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "no s'ha trobat el motor JavaScript" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Es necessari algun d'aquests paquets per els enllaços Click'N'Load. Instal.la Spidermonkey, ossp-js, pyv8 o rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Continuar amb la instal·lació?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Vols canviar la ruta de configuració? Actualment és %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Canviar la ruta de configuració?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Vols configurar les dades d'accés i la configuració bàsica?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Això és recomanant en la primera execució." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Establir una configuració bàsica?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Vols configurar el SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Configurar SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Vols configurar l'interíicie web?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Configurar interfície web?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Configuració finalitzada satisfactòriament." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Pitja enter i reinicia pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Configuració bàsica ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Les dades d'accés següents són vàlides per CLI, GUI i la interfície web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Els clients externs (GUI, CLI i altres) necessiten accés remot a través de la xarxa per funcionar." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "No obstant, si només vos emprar la interfície web pots deshabilitar-lo per estalviar memòria ram." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Activar accés remot" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Nombre màxim de descàrregues paral·leles" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Emprar el reconnectar?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Ubicació de l'script de reconnexió" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Configuració de la interfície Web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Activar interfície web?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Adreça d'escolta, si empres 127.0.0.1 o localhost la interfície web només serà accessible localment." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad ofereix uns quants backends, ara se'n farà una breu explicació." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Pots emprar apache, lighttpd, però requereixen ser configurats i no sempre és una tasca fàcil." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Aconsegueix-lo aquí: https://github.com/jonashaag/bjoern, compila'l" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Atenció: En alguns casos estranys el servidor integrat no funciona, ho notareu amb problemes a la interfície web" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "torna aquí i canvia el servidor integrat per el servidor amb fils." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Configuració SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Executa aquesta comanada des de la carpeta de configuració de pyLoad per fer els certificats SSL:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Si has acabat i tot ha anat bé ara podràs activar el SSL." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Activar SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Tria una opció" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Crear/editar usuari" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Llistar usuaris" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Eliminar usuari" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Sortir" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Usuaris" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Pitja enter per sortir." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "L'ajustament de la ruta de configuració ha fallat: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "s" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Contrasenya: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Contrasenya (altre cop): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Les contrasenyes no coincideixen." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "sí" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "cert" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "c" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "fals" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Entrada invàlida" + diff --git a/locale/ca/LC_MESSAGES/webUI.po b/locale/ca/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..575ae7846 --- /dev/null +++ b/locale/ca/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Catalan\n" +"Language: ca_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/cli.pot b/locale/cli.pot index 646c6c70e..c39b9f0d8 100644 --- a/locale/cli.pot +++ b/locale/cli.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: pyLoad 0.4.9\n" +"Project-Id-Version: pyload 0.4.9.9-dev\n" "Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" -"POT-Creation-Date: 2011-12-07 19:21+0100\n" +"POT-Creation-Date: 2013-10-13 18:16+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -17,283 +17,279 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: pyLoadCli.py:75 pyLoadCli.py:133 +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 msgid " Command Line Interface" msgstr "" -#: pyLoadCli.py:165 +#: pyload/cli/Cli.py:165 #, python-format msgid "%s Downloads:" msgstr "" -#: pyLoadCli.py:177 +#: pyload/cli/Cli.py:177 msgid " Speed: " msgstr "" -#: pyLoadCli.py:177 +#: pyload/cli/Cli.py:177 msgid " Size: " msgstr "" -#: pyLoadCli.py:178 +#: pyload/cli/Cli.py:178 msgid " Finished in: " msgstr "" -#: pyLoadCli.py:179 +#: pyload/cli/Cli.py:179 msgid " ID: " msgstr "" -#: pyLoadCli.py:184 +#: pyload/cli/Cli.py:184 msgid "waiting: " msgstr "" -#: pyLoadCli.py:191 pyLoadCli.py:193 +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 msgid "Status:" msgstr "" -#: pyLoadCli.py:191 +#: pyload/cli/Cli.py:191 msgid "paused" msgstr "" -#: pyLoadCli.py:193 +#: pyload/cli/Cli.py:193 msgid "running" msgstr "" -#: pyLoadCli.py:196 +#: pyload/cli/Cli.py:196 msgid "total Speed" msgstr "" -#: pyLoadCli.py:196 +#: pyload/cli/Cli.py:196 msgid "Files in queue" msgstr "" -#: pyLoadCli.py:197 +#: pyload/cli/Cli.py:197 msgid "Total" msgstr "" -#: pyLoadCli.py:203 +#: pyload/cli/Cli.py:203 msgid "Menu:" msgstr "" -#: pyLoadCli.py:205 +#: pyload/cli/Cli.py:205 msgid " Add Links" msgstr "" -#: pyLoadCli.py:206 +#: pyload/cli/Cli.py:206 msgid " Manage Queue" msgstr "" -#: pyLoadCli.py:207 +#: pyload/cli/Cli.py:207 msgid " Manage Collector" msgstr "" -#: pyLoadCli.py:208 +#: pyload/cli/Cli.py:208 msgid " (Un)Pause Server" msgstr "" -#: pyLoadCli.py:209 +#: pyload/cli/Cli.py:209 msgid " Kill Server" msgstr "" -#: pyLoadCli.py:210 +#: pyload/cli/Cli.py:210 msgid " Quit" msgstr "" -#: pyLoadCli.py:289 pyLoadCli.py:296 +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 msgid "Please use this syntax: add <Package name> <link> <link2> ..." msgstr "" -#: pyLoadCli.py:315 +#: pyload/cli/Cli.py:315 #, python-format msgid "Checking %d links:" msgstr "" -#: pyLoadCli.py:324 +#: pyload/cli/Cli.py:324 msgid "File does not exists." msgstr "" -#: pyLoadCli.py:385 +#: pyload/cli/Cli.py:385 msgid "pyLoad was terminated" msgstr "" -#: pyLoadCli.py:443 +#: pyload/cli/Cli.py:443 msgid "Prints server status" msgstr "" -#: pyLoadCli.py:444 +#: pyload/cli/Cli.py:444 msgid "Prints downloads in queue" msgstr "" -#: pyLoadCli.py:445 +#: pyload/cli/Cli.py:445 msgid "Prints downloads in collector" msgstr "" -#: pyLoadCli.py:446 +#: pyload/cli/Cli.py:446 msgid "Adds package to queue" msgstr "" -#: pyLoadCli.py:447 +#: pyload/cli/Cli.py:447 msgid "Adds package to collector" msgstr "" -#: pyLoadCli.py:448 +#: pyload/cli/Cli.py:448 msgid "Delete Files from Queue/Collector" msgstr "" -#: pyLoadCli.py:449 +#: pyload/cli/Cli.py:449 msgid "Delete Packages from Queue/Collector" msgstr "" -#: pyLoadCli.py:450 +#: pyload/cli/Cli.py:450 msgid "Move Packages from Queue to Collector or vice versa" msgstr "" -#: pyLoadCli.py:451 +#: pyload/cli/Cli.py:451 msgid "Restart files" msgstr "" -#: pyLoadCli.py:452 +#: pyload/cli/Cli.py:452 msgid "Restart packages" msgstr "" -#: pyLoadCli.py:453 +#: pyload/cli/Cli.py:453 msgid "Check online status, works with local container" msgstr "" -#: pyLoadCli.py:454 +#: pyload/cli/Cli.py:454 msgid "Checks online status of a container file" msgstr "" -#: pyLoadCli.py:455 +#: pyload/cli/Cli.py:455 msgid "Pause the server" msgstr "" -#: pyLoadCli.py:456 +#: pyload/cli/Cli.py:456 msgid "continue downloads" msgstr "" -#: pyLoadCli.py:457 +#: pyload/cli/Cli.py:457 msgid "Toggle pause/unpause" msgstr "" -#: pyLoadCli.py:458 +#: pyload/cli/Cli.py:458 msgid "kill server" msgstr "" -#: pyLoadCli.py:460 +#: pyload/cli/Cli.py:460 msgid "List of commands:" msgstr "" -#: pyLoadCli.py:473 +#: pyload/cli/Cli.py:473 msgid "Couldn't write user config file" msgstr "" -#: pyLoadCli.py:548 -msgid "You need py-openssl to connect to this pyLoad Core." +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." msgstr "" -#: pyLoadCli.py:555 +#: pyload/cli/Cli.py:555 msgid "Address: " msgstr "" -#: pyLoadCli.py:556 +#: pyload/cli/Cli.py:556 msgid "Port: " msgstr "" -#: pyLoadCli.py:557 +#: pyload/cli/Cli.py:557 msgid "Username: " msgstr "" -#: pyLoadCli.py:561 +#: pyload/cli/Cli.py:561 msgid "Password: " msgstr "" -#: pyLoadCli.py:566 pyLoadCli.py:575 +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 msgid "Login data is wrong." msgstr "" -#: pyLoadCli.py:568 pyLoadCli.py:577 +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 #, python-format msgid "Could not establish connection to %(addr)s:%(port)s." msgstr "" -#: pyLoadCli.py:580 -msgid "You need py-openssl to connect to this pyLoad core." -msgstr "" - -#: pyLoadCli.py:582 +#: pyload/cli/Cli.py:582 msgid "Interactive mode ignored since you passed some commands." msgstr "" - -#: module/cli/ManageFiles.py:97 -msgid "Manage Packages:" -msgstr "" - -#: module/cli/ManageFiles.py:99 -msgid "Manage Links:" -msgstr "" - -#: module/cli/ManageFiles.py:104 -msgid "What do you want to move?" -msgstr "" - -#: module/cli/ManageFiles.py:106 -msgid "What do you want to delete?" -msgstr "" - -#: module/cli/ManageFiles.py:108 -msgid "What do you want to restart?" -msgstr "" - -#: module/cli/ManageFiles.py:113 -msgid "Choose what yout want to do or enter package number." -msgstr "" - -#: module/cli/ManageFiles.py:115 -msgid "delete" -msgstr "" - -#: module/cli/ManageFiles.py:115 -msgid "move" -msgstr "" - -#: module/cli/ManageFiles.py:115 -msgid "restart" -msgstr "" - -#: module/cli/ManageFiles.py:148 -msgid " - previous" -msgstr "" - -#: module/cli/ManageFiles.py:148 -msgid " - next" -msgstr "" - -#: module/cli/ManageFiles.py:149 module/cli/AddPackage.py:64 -msgid " back to main menu" -msgstr "" - -#: module/cli/AddPackage.py:48 -msgid "Add Package:" -msgstr "" - -#: module/cli/AddPackage.py:53 -msgid "Enter a name for the new package" -msgstr "" - -#: module/cli/AddPackage.py:57 -#, python-format -msgid "Package: %s" -msgstr "" - -#: module/cli/AddPackage.py:58 -msgid "Parse the links you want to add." -msgstr "" - -#: module/cli/AddPackage.py:59 -#, python-format -msgid "Type %s when done." -msgstr "" - -#: module/cli/AddPackage.py:60 -msgid "Links added: " -msgstr "" diff --git a/locale/core.pot b/locale/core.pot index 546f0e4d3..0fc068d26 100644 --- a/locale/core.pot +++ b/locale/core.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: pyLoad 0.4.9\n" +"Project-Id-Version: pyload 0.4.9.9-dev\n" "Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" -"POT-Creation-Date: 2011-12-07 19:21+0100\n" +"POT-Creation-Date: 2013-10-13 18:16+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -17,850 +17,630 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: pyLoadCore.py:185 -msgid "Received Quit signal" -msgstr "" - -#: pyLoadCore.py:301 -#, python-format -msgid "pyLoad already running with pid %s" -msgstr "" - -#: pyLoadCore.py:315 +#: pyload/AddonManager.py:62 #, python-format -msgid "Failed changing group: %s" -msgstr "" - -#: pyLoadCore.py:325 -#, python-format -msgid "Failed changing user: %s" -msgstr "" - -#: pyLoadCore.py:327 -msgid "folder for logs" -msgstr "" - -#: pyLoadCore.py:338 -msgid "Starting" +msgid "Error when executing %s" msgstr "" -#: pyLoadCore.py:339 +#: pyload/AddonManager.py:93 #, python-format -msgid "Using home directory: %s" -msgstr "" - -#: pyLoadCore.py:348 -msgid "pycrypto to decode container files" -msgstr "" - -#: pyLoadCore.py:351 -msgid "folder for temporary files" -msgstr "" - -#: pyLoadCore.py:356 -msgid "folder for downloads" -msgstr "" - -#: pyLoadCore.py:359 -msgid "OpenSSL for secure connection" -msgstr "" - -#: pyLoadCore.py:363 -msgid "Moving old user config to DB" -msgstr "" - -#: pyLoadCore.py:366 -msgid "Please check your logindata with ./pyLoadCore.py -u" -msgstr "" - -#: pyLoadCore.py:369 -msgid "All links removed" +msgid "Failed activating %(name)s" msgstr "" -#: pyLoadCore.py:400 +#: pyload/AddonManager.py:96 #, python-format -msgid "Downloadtime: %s" +msgid "Activated addons: %s" msgstr "" -#: pyLoadCore.py:410 +#: pyload/AddonManager.py:97 #, python-format -msgid "Free space: %s" -msgstr "" - -#: pyLoadCore.py:430 -msgid "Activating Accounts..." +msgid "Deactivated addons: %s" msgstr "" -#: pyLoadCore.py:436 +#: pyload/AddonManager.py:153 msgid "Activating Plugins..." msgstr "" -#: pyLoadCore.py:439 -msgid "pyLoad is up and running" -msgstr "" - -#: pyLoadCore.py:458 -msgid "restarting pyLoad" -msgstr "" - -#: pyLoadCore.py:462 -msgid "pyLoad quits" -msgstr "" - -#: pyLoadCore.py:519 -#, python-format -msgid "Install %s" -msgstr "" - -#: pyLoadCore.py:555 -#, python-format -msgid "could not find %(desc)s: %(name)s" +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." msgstr "" -#: pyLoadCore.py:557 -#, python-format -msgid "could not create %(desc)s: %(name)s" -msgstr "" - -#: pyLoadCore.py:578 -msgid "shutting down..." +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." msgstr "" -#: pyLoadCore.py:595 -msgid "error while shutting down" +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" msgstr "" -#: pyLoadCore.py:659 -msgid "killed pyLoad from Terminal" +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" msgstr "" -#: module/common/JsEngine.py:156 -msgid "" -"No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or " -"rhino" +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " msgstr "" -#: module/remote/ThriftBackend.py:39 -msgid "Using SSL ThriftBackend" +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " msgstr "" -#: module/remote/RemoteManager.py:35 -#, python-format -msgid "Remote backend error: %s" +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" msgstr "" -#: module/remote/RemoteManager.py:82 +#: pyload/web/ServerThread.py:139 #, python-format -msgid "Starting %(name)s: %(addr)s:%(port)s" +msgid "Starting %(name)s webserver: %(host)s:%(port)d" msgstr "" -#: module/remote/RemoteManager.py:84 -#, python-format -msgid "Failed loading backend %(name)s | %(error)s" +#: pyload/config/default.py:14 +msgid "Remote" msgstr "" -#: module/ThreadManager.py:137 -#, python-format -msgid "Reconnect Failed: %s" +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" msgstr "" -#: module/ThreadManager.py:176 -msgid "Reconnect script not found!" +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" msgstr "" -#: module/ThreadManager.py:182 -msgid "Starting reconnect" +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" msgstr "" -#: module/ThreadManager.py:196 -msgid "Failed executing reconnect script!" +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" msgstr "" -#: module/ThreadManager.py:208 -#, python-format -msgid "Reconnected, new IP: %s" +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" msgstr "" -#: module/ThreadManager.py:288 -msgid "Not enough space left on device" +#: pyload/config/default.py:21 +msgid "Log" msgstr "" -#: module/HookManager.py:90 module/plugins/Hook.py:102 -#, python-format -msgid "Error executing hooks: %s" +#: pyload/config/default.py:23 +msgid "Size in kb" msgstr "" -#: module/HookManager.py:140 -#, python-format -msgid "Failed activating %(name)s" +#: pyload/config/default.py:24 +msgid "Folder" msgstr "" -#: module/HookManager.py:144 -#, python-format -msgid "Activated plugins: %s" +#: pyload/config/default.py:25 +msgid "File Log" msgstr "" -#: module/HookManager.py:145 -#, python-format -msgid "Deactivate plugins: %s" +#: pyload/config/default.py:26 +msgid "Count" msgstr "" -#: module/CaptchaManager.py:78 module/interaction/InteractionManager.py:82 -msgid "No Client connected for captcha decrypting" +#: pyload/config/default.py:27 +msgid "Log Rotate" msgstr "" -#: module/web/ServerThread.py:35 -msgid "SSL certificates not found." +#: pyload/config/default.py:30 +msgid "Permissions" msgstr "" -#: module/web/ServerThread.py:39 -#, python-format -msgid "Sorry, we dropped support for starting %s directly within pyLoad" +#: pyload/config/default.py:32 +msgid "Groupname" msgstr "" -#: module/web/ServerThread.py:40 -msgid "You can use the threaded server which offers good performance and ssl," +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" msgstr "" -#: module/web/ServerThread.py:41 -#, python-format -msgid "" -"of course you can still use your existing %s with pyLoads fastcgi server" +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" msgstr "" -#: module/web/ServerThread.py:42 -msgid "sample configs are located in the module/web/servers directory" +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" msgstr "" -#: module/web/ServerThread.py:49 -#, python-format -msgid "Can't use %(server)s, python-flup is not installed!" +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" msgstr "" -#: module/web/ServerThread.py:56 -#, python-format -msgid "Error importing lightweight server: %s" +#: pyload/config/default.py:37 +msgid "Change group of running process" msgstr "" -#: module/web/ServerThread.py:57 -msgid "" -"You need to download and compile bjoern, https://github.com/jonashaag/bjoern" +#: pyload/config/default.py:38 +msgid "Folder Permission mode" msgstr "" -#: module/web/ServerThread.py:58 -msgid "Copy the boern.so to module/lib folder or use setup.py install" +#: pyload/config/default.py:39 +msgid "Change user of running process" msgstr "" -#: module/web/ServerThread.py:59 -msgid "" -"Of course you need to be familiar with linux and know how to compile software" +#: pyload/config/default.py:42 +msgid "General" msgstr "" -#: module/web/ServerThread.py:63 -msgid "Server set to threaded, due to known performance problems on windows." +#: pyload/config/default.py:44 +msgid "Language" msgstr "" -#: module/web/ServerThread.py:80 module/web/ServerThread.py:103 -msgid "This server offers no SSL, please consider using threaded instead" +#: pyload/config/default.py:45 +msgid "Download Folder" msgstr "" -#: module/web/ServerThread.py:82 -#, python-format -msgid "Starting builtin webserver: %(host)s:%(port)d" +#: pyload/config/default.py:46 +msgid "Use Checksum" msgstr "" -#: module/web/ServerThread.py:87 -#, python-format -msgid "Starting threaded SSL webserver: %(host)s:%(port)d" +#: pyload/config/default.py:47 +msgid "Create folder for each package" msgstr "" -#: module/web/ServerThread.py:91 -#, python-format -msgid "Starting threaded webserver: %(host)s:%(port)d" +#: pyload/config/default.py:48 +msgid "Debug Mode" msgstr "" -#: module/web/ServerThread.py:97 -#, python-format -msgid "Starting fastcgi server: %(host)s:%(port)d" +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" msgstr "" -#: module/web/ServerThread.py:105 -#, python-format -msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d" +#: pyload/config/default.py:50 +msgid "CPU Priority" msgstr "" -#: module/web/pyload_app.py:125 -msgid "You dont have permission to access this page." +#: pyload/config/default.py:53 +msgid "SSL" msgstr "" -#: module/web/pyload_app.py:193 -msgid "Download directory not found." +#: pyload/config/default.py:55 +msgid "SSL Certificate" msgstr "" -#: module/web/pyload_app.py:260 module/web/pyload_app.py:267 -msgid "unlimited" +#: pyload/config/default.py:57 +msgid "SSL Key" msgstr "" -#: module/web/pyload_app.py:262 module/web/pyload_app.py:269 -msgid "not available" +#: pyload/config/default.py:60 +msgid "Webinterface" msgstr "" -#: module/web/pyload_app.py:509 -msgid "Run pyLoadCore.py -s to access the setup." +#: pyload/config/default.py:62 +msgid "Template" msgstr "" -#: module/web/json_app.py:60 -#, python-format -msgid "waiting %s" +#: pyload/config/default.py:64 +msgid "Path Prefix" msgstr "" -#: module/Api.py:329 -#, python-format -msgid "Added package %(name)s containing %(count)d links" +#: pyload/config/default.py:65 +msgid "Server" msgstr "" -#: module/Api.py:592 -#, python-format -msgid "Added %(count)d links to package #%(package)d " +#: pyload/config/default.py:66 +msgid "Favor specific server" msgstr "" -#: module/plugins/crypter/SerienjunkiesOrg.py:125 -msgid "Downloadlimit reached" +#: pyload/config/default.py:67 +msgid "IP" msgstr "" -#: module/plugins/hooks/ClickAndLoad.py:74 -msgid "Click'N'Load: Port 9666 already in use" +#: pyload/config/default.py:68 +msgid "Use HTTPS" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:91 -#, python-format -msgid "No %s installed" +#: pyload/config/default.py:70 +msgid "Development mode" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:93 -#: module/plugins/hooks/ExtractArchive.py:98 -#, python-format -msgid "Could not activate %s" +#: pyload/config/default.py:73 +msgid "Proxy" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:103 -msgid "Activated" +#: pyload/config/default.py:76 +msgid "Use Proxy" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:105 -msgid "No Extract plugins activated" +#: pyload/config/default.py:78 +msgid "Password" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:117 -#, python-format -msgid "Package %s queued for later extracting" +#: pyload/config/default.py:79 +msgid "Protocol" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:142 -#, python-format -msgid "Check package %s" +#: pyload/config/default.py:83 +msgid "Reconnect" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:179 -#, python-format -msgid "Extract to %s" +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:198 -msgid "extracting" +#: pyload/config/default.py:86 +msgid "Use Reconnect" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:209 -msgid "Password protected" +#: pyload/config/default.py:87 +msgid "Method" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:229 -msgid "Wrong password" +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:237 -#, python-format -msgid "Deleting %s files" +#: pyload/config/default.py:91 +msgid "Download" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:242 -msgid "Extracting finished" +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:249 -msgid "Archive Error" +#: pyload/config/default.py:94 +msgid "Limit Download Speed" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:251 -msgid "CRC Mismatch" +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:255 -msgid "Unknown Error" +#: pyload/config/default.py:96 +msgid "Skip already existing files" msgstr "" -#: module/plugins/hooks/ExtractArchive.py:307 -msgid "Setting User and Group failed" +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" msgstr "" -#: module/plugins/hooks/CaptchaTrader.py:71 -#, python-format -msgid "%s credits left" +#: pyload/config/default.py:98 +msgid "Allow IPv6" msgstr "" -#: module/plugins/hooks/CaptchaTrader.py:132 -msgid "Your CaptchaTrader Account has not enough credits" +#: pyload/config/default.py:99 +msgid "Max connections for one download" msgstr "" -#: module/plugins/hooks/IRCInterface.py:74 -#: module/plugins/hooks/XMPPInterface.py:82 -#: module/database/FileDatabase.py:507 -#, python-format -msgid "Package finished: %s" +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" msgstr "" -#: module/plugins/hooks/IRCInterface.py:81 -#, python-format -msgid "Download finished: %(name)s @ %(plugin)s " -msgstr "" - -#: module/plugins/hooks/IRCInterface.py:93 -#, python-format -msgid "New Captcha Request: %s" +#: pyload/config/default.py:103 +msgid "Download Time" msgstr "" -#: module/plugins/hooks/IRCInterface.py:94 +#: pyload/network/HTTPDownload.py:249 #, python-format -msgid "Answer with 'c %s text on the captcha'" +msgid "Download chunks failed, fallback to single connection | %s" msgstr "" -#: module/plugins/hooks/XMPPInterface.py:90 +#: pyload/api/DownloadApi.py:33 #, python-format -msgid "Download finished: %(name)s @ %(plugin)s" -msgstr "" - -#: module/plugins/hooks/RehostTo.py:32 -msgid "Please add your rehost.to account first and restart pyLoad" +msgid "Added package %(name)s as folder %(folder)s" msgstr "" -#: module/plugins/hooks/HotFolder.py:82 +#: pyload/api/DownloadApi.py:84 #, python-format -msgid "Added %s from HotFolder" +msgid "Added %d links to package" msgstr "" -#: module/plugins/hooks/ExternalScripts.py:54 +#: pyload/AccountManager.py:49 #, python-format -msgid "Installed scripts for %s: " +msgid "Account plugin %s not available" msgstr "" -#: module/plugins/hooks/ExternalScripts.py:70 -msgid "Script not executable:" -msgstr "" - -#: module/plugins/hooks/ExternalScripts.py:80 +#: pyload/AccountManager.py:70 #, python-format -msgid "Error in %(script)s: %(error)s" -msgstr "" - -#: module/plugins/hooks/UpdateManager.py:68 -msgid "No Updates for pyLoad" +msgid "Could not load account %s" msgstr "" -#: module/plugins/hooks/UpdateManager.py:73 -msgid "*** Plugins have been updated, please restart pyLoad ***" +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" msgstr "" -#: module/plugins/hooks/UpdateManager.py:75 -msgid "Plugins updated and reloaded" +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" msgstr "" -#: module/plugins/hooks/UpdateManager.py:78 -msgid "No plugin updates available" +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." msgstr "" -#: module/plugins/hooks/UpdateManager.py:93 +#: pyload/remote/RemoteManager.py:35 #, python-format -msgid "*** New pyLoad Version %s available ***" -msgstr "" - -#: module/plugins/hooks/UpdateManager.py:94 -msgid "*** Get it here: http://pyload.org/download ***" -msgstr "" - -#: module/plugins/hooks/UpdateManager.py:97 -#: module/plugins/hooks/UpdateManager.py:110 -msgid "Not able to connect server for updates" -msgstr "" - -#: module/plugins/hooks/UpdateManager.py:141 -#, python-format -msgid "New version of %(type)s|%(name)s : %(version).2f" +msgid "Remote backend error: %s" msgstr "" -#: module/plugins/hooks/UpdateManager.py:150 -#: module/plugins/hooks/UpdateManager.py:155 +#: pyload/remote/RemoteManager.py:80 #, python-format -msgid "Error when updating %s" -msgstr "" - -#: module/plugins/hooks/UpdateManager.py:155 -msgid "Version mismatch" -msgstr "" - -#: module/plugins/hoster/BasePlugin.py:53 -msgid "Authorization required (username:password)" -msgstr "" - -#: module/plugins/hoster/OronCom.py:135 -msgid "Not enough traffic left" -msgstr "" - -#: module/plugins/hoster/OronCom.py:137 -#: module/plugins/hoster/UploadedTo.py:158 -msgid "Traffic exceeded" -msgstr "" - -#: module/plugins/hoster/MegauploadCom.py:135 -msgid "You should enable direct Download in your Megaupload Account settings" +msgid "Starting %(name)s: %(addr)s:%(port)s" msgstr "" -#: module/plugins/hoster/MegauploadCom.py:158 +#: pyload/remote/RemoteManager.py:82 #, python-format -msgid "Megaupload: waiting %d minutes" -msgstr "" - -#: module/plugins/hoster/MegauploadCom.py:172 -msgid "You need premium to download files larger than 1 GB" -msgstr "" - -#: module/plugins/hoster/MegauploadCom.py:177 -msgid "The file is password protected, enter a password and restart." +msgid "Failed loading backend %(name)s | %(error)s" msgstr "" -#: module/plugins/hoster/MegauploadCom.py:194 -msgid "Megaupload is currently blocking your IP. Try again later, manually." +#: pyload/FileManager.py:53 +msgid "none" msgstr "" -#: module/plugins/hoster/MegauploadCom.py:269 -msgid "" -"Looks like the file is still not available. Retry downloading later, " -"manually." -msgstr "" - -#: module/plugins/hoster/MegauploadCom.py:272 -msgid "Wrong password for download link." +#: pyload/FileManager.py:53 +msgid "offline" msgstr "" -#: module/plugins/hoster/UploadedTo.py:131 -msgid "API key invalid" +#: pyload/FileManager.py:53 +msgid "online" msgstr "" -#: module/plugins/hoster/UploadedTo.py:155 -#, python-format -msgid "%s: Not enough traffic left" +#: pyload/FileManager.py:53 +msgid "queued" msgstr "" -#: module/plugins/hoster/ShareonlineBiz.py:106 -msgid "Parallel download issue" +#: pyload/FileManager.py:53 +msgid "paused" msgstr "" -#: module/plugins/hoster/ShareonlineBiz.py:121 -msgid "Invalid download ticket" +#: pyload/FileManager.py:54 +msgid "finished" msgstr "" -#: module/plugins/hoster/RehostTo.py:26 -msgid "Please enter your rehost.to account or deactivate this plugin" +#: pyload/FileManager.py:54 +msgid "skipped" msgstr "" -#: module/plugins/hoster/FileserveCom.py:87 -msgid "Not logged in." +#: pyload/FileManager.py:54 +msgid "failed" msgstr "" -#: module/plugins/hoster/FileserveCom.py:112 -msgid "Parallel download error, now waiting 60s." +#: pyload/FileManager.py:54 +msgid "starting" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:99 -msgid "Rapidshare: Traffic Share (direct download)" +#: pyload/FileManager.py:55 +msgid "waiting" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:126 -#: module/plugins/hoster/RapidshareCom.py:192 -msgid "Already downloading from this ip address, waiting 60 seconds" +#: pyload/FileManager.py:55 +msgid "downloading" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:130 -msgid "Invalid Auth Code, download will be restarted" +#: pyload/FileManager.py:55 +msgid "temp. offline" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:196 -msgid "RapidShareCom: No free slots" +#: pyload/FileManager.py:55 +msgid "aborted" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:199 -msgid "You need a premium account for this file" +#: pyload/FileManager.py:56 +msgid "decrypting" msgstr "" -#: module/plugins/hoster/RapidshareCom.py:201 -msgid "Filename reported invalid" +#: pyload/FileManager.py:56 +msgid "processing" msgstr "" -#: module/plugins/hoster/FilesMailRu.py:99 -msgid "There was HTML Code in the Downloaded File(" +#: pyload/FileManager.py:56 +msgid "custom" msgstr "" -#: module/plugins/hoster/NetloadIn.py:141 -#: module/plugins/hoster/NetloadIn.py:161 -msgid "File temporarily not available" +#: pyload/FileManager.py:56 +msgid "unknown" msgstr "" -#: module/plugins/hoster/NetloadIn.py:174 +#: pyload/FileManager.py:426 #, python-format -msgid "Netload: waiting between downloads %d s." +msgid "Package finished: %s" msgstr "" -#: module/plugins/hoster/NetloadIn.py:203 +#: pyload/Api.py:150 #, python-format -msgid "Netload: waiting for captcha %d s." -msgstr "" - -#: module/plugins/hoster/NetloadIn.py:242 -msgid "Downloaded File was empty" -msgstr "" - -#: module/plugins/hoster/RealdebridCom.py:37 -msgid "Please enter your Real-debrid account or deactivate this plugin" +msgid "User '%s' tries to log in" msgstr "" -#: module/plugins/container/LinkList.py:54 -msgid "LinkList could not be cleared." -msgstr "" - -#: module/plugins/Plugin.py:381 -msgid "" -"Pil and tesseract not installed and no Client connected for captcha " -"decrypting" -msgstr "" - -#: module/plugins/Plugin.py:385 -msgid "No captcha result obtained in appropiate time by any of the plugins." +#: pyload/Core.py:195 +msgid "Received Quit signal" msgstr "" -#: module/plugins/Plugin.py:490 module/plugins/Plugin.py:520 +#: pyload/Core.py:321 #, python-format -msgid "Setting User and Group failed: %s" -msgstr "" - -#: module/plugins/Container.py:68 -msgid "File not exists." -msgstr "" - -#: module/plugins/accounts/MegauploadCom.py:41 -msgid "Activate direct Download in your MegaUpload Account" -msgstr "" - -#: module/plugins/accounts/FilesonicCom.py:49 -msgid "Invalid login retrieving user details" -msgstr "" - -#: module/plugins/accounts/BitshareCom.py:36 -msgid "Activate direct Download in your Bitshare Account" +msgid "pyLoad already running with pid %s" msgstr "" -#: module/plugins/PluginManager.py:153 +#: pyload/Core.py:335 #, python-format -msgid "%s has a invalid pattern." +msgid "Failed changing group: %s" msgstr "" -#: module/plugins/PluginManager.py:272 +#: pyload/Core.py:345 #, python-format -msgid "Error importing %(name)s: %(msg)s" -msgstr "" - -#: module/plugins/AccountManager.py:88 -msgid "Account settings deleted, due to new config format." +msgid "Failed changing user: %s" msgstr "" -#: module/plugins/internal/MultiHoster.py:60 -msgid "No Hoster loaded" +#: pyload/Core.py:356 +msgid "Starting" msgstr "" -#: module/plugins/Account.py:85 module/plugins/Account.py:91 +#: pyload/Core.py:357 #, python-format -msgid "Could not login with account %(user)s | %(msg)s" -msgstr "" - -#: module/plugins/Account.py:86 -msgid "Wrong Password" +msgid "Using home directory: %s" msgstr "" -#: module/plugins/Account.py:240 -#, python-format -msgid "Your Time %s has wrong format, use: 1:22-3:44" +#: pyload/Core.py:371 +msgid "All links removed" msgstr "" -#: module/plugins/Account.py:266 +#: pyload/Core.py:401 #, python-format -msgid "Account %s has not enough traffic, checking again in 30min" +msgid "Download time: %s" msgstr "" -#: module/plugins/Account.py:273 +#: pyload/Core.py:416 #, python-format -msgid "Account %s is expired, checking again in 1h" -msgstr "" - -#: module/database/FileDatabase.py:47 -msgid "finished" -msgstr "" - -#: module/database/FileDatabase.py:47 -msgid "offline" +msgid "Free space: %s" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "online" +#: pyload/Core.py:436 +msgid "Activating Accounts..." msgstr "" -#: module/database/FileDatabase.py:47 -msgid "queued" +#: pyload/Core.py:441 +msgid "Restarting failed downloads..." msgstr "" -#: module/database/FileDatabase.py:47 -msgid "skipped" +#: pyload/Core.py:449 +msgid "pyLoad is up and running" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "waiting" +#: pyload/Core.py:472 +msgid "restarting pyLoad" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "temp. offline" +#: pyload/Core.py:476 +msgid "pyLoad quits" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "starting" +#: pyload/Core.py:562 +msgid "shutting down..." msgstr "" -#: module/database/FileDatabase.py:47 -msgid "failed" +#: pyload/Core.py:577 +msgid "error while shutting down" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "aborted" +#: pyload/Core.py:659 +msgid "killed pyLoad from terminal" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "decrypting" +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." msgstr "" -#: module/database/FileDatabase.py:47 -msgid "custom" +#: pyload/threads/DecrypterThread.py:30 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "downloading" +#: pyload/threads/DecrypterThread.py:55 +msgid "Decrypting aborted" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "processing" +#: pyload/threads/DecrypterThread.py:57 +msgid "Decrypting failed" msgstr "" -#: module/database/FileDatabase.py:47 -msgid "unknown" +#: pyload/threads/InfoThread.py:143 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" msgstr "" -#: module/database/DatabaseBackend.py:174 -msgid "Filedatabase was deleted due to incompatible version." +#: pyload/threads/ThreadManager.py:148 +#, python-format +msgid "Reconnect Failed: %s" msgstr "" -#: module/database/DatabaseBackend.py:189 -msgid "Filedatabase could NOT be converted." +#: pyload/threads/ThreadManager.py:188 +msgid "Reconnect script not found!" msgstr "" -#: module/database/DatabaseBackend.py:198 -msgid "Database was converted from v2 to v3." +#: pyload/threads/ThreadManager.py:194 +msgid "Starting reconnect" msgstr "" -#: module/database/DatabaseBackend.py:206 -msgid "Database was converted from v3 to v4." +#: pyload/threads/ThreadManager.py:208 +msgid "Failed executing reconnect script!" msgstr "" -#: module/database/DatabaseBackend.py:252 -msgid "Converting old Django DB" +#: pyload/threads/ThreadManager.py:219 +#, python-format +msgid "Reconnected, new IP: %s" msgstr "" -#: module/network/HTTPDownload.py:245 -#, python-format -msgid "Download chunks failed, fallback to single connection | %s" +#: pyload/threads/ThreadManager.py:297 +msgid "Not enough space left on device" msgstr "" -#: module/PluginThread.py:183 +#: pyload/threads/DownloadThread.py:64 #, python-format msgid "Download starts: %s" msgstr "" -#: module/PluginThread.py:189 +#: pyload/threads/DownloadThread.py:70 #, python-format msgid "Download finished: %s" msgstr "" -#: module/PluginThread.py:194 module/PluginThread.py:366 +#: pyload/threads/DownloadThread.py:75 #, python-format msgid "Plugin %s is missing a function." msgstr "" -#: module/PluginThread.py:202 module/PluginThread.py:265 -#: module/PluginThread.py:383 +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 #, python-format msgid "Download aborted: %s" msgstr "" -#: module/PluginThread.py:222 +#: pyload/threads/DownloadThread.py:103 #, python-format msgid "Download restarted: %(name)s | %(msg)s" msgstr "" -#: module/PluginThread.py:231 module/PluginThread.py:374 +#: pyload/threads/DownloadThread.py:113 #, python-format msgid "Download is offline: %s" msgstr "" -#: module/PluginThread.py:234 +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 #, python-format msgid "Download is temporary offline: %s" msgstr "" -#: module/PluginThread.py:237 module/PluginThread.py:304 +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 #, python-format msgid "Download failed: %(name)s | %(msg)s" msgstr "" -#: module/PluginThread.py:254 +#: pyload/threads/DownloadThread.py:136 msgid "" "Couldn't connect to host or connection reset, waiting 1 minute and retry." msgstr "" -#: module/PluginThread.py:290 +#: pyload/threads/DownloadThread.py:171 #, python-format msgid "Download skipped: %(name)s due to %(plugin)s" msgstr "" -#: module/PluginThread.py:362 -#, python-format -msgid "Decrypting starts: %s" +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" msgstr "" -#: module/PluginThread.py:377 module/PluginThread.py:395 -#, python-format -msgid "Decrypting failed: %(name)s | %(msg)s" +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" msgstr "" -#: module/PluginThread.py:389 +#: pyload/PluginManager.py:316 #, python-format -msgid "Retrying %s" +msgid "Error importing %(name)s: %(msg)s" msgstr "" -#: module/PluginThread.py:636 -#, python-format -msgid "Info Fetching for %(name)s failed | %(err)s" +#: pyload/utils/JsEngine.py:188 +msgid "" +"No js engine detected, please install either Spidermonkey, ossp-js, pyv8, " +"nodejs or rhino" +msgstr "" + +#: pyload/utils/packagetools.py:130 +msgid "Unnamed package" msgstr "" diff --git a/locale/crowdin.yaml b/locale/crowdin.yaml new file mode 100644 index 000000000..69936a988 --- /dev/null +++ b/locale/crowdin.yaml @@ -0,0 +1,9 @@ +project_identifier: pyload +preserve_hierarchy: true +api_key: {key} +base_path: {tmp} + +files: + - + source: 'pyLoad/*.pot' + translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/%file_name%.po' diff --git a/locale/cs/LC_MESSAGES/cli.po b/locale/cs/LC_MESSAGES/cli.po new file mode 100644 index 000000000..670db4bec --- /dev/null +++ b/locale/cs/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Czech\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Přidat Balíček:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Zadej název nového balíčku" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Balicek: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analyzuje odkazy ktere chcete pridat." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Pokud jste hotovi, napiste %s" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Pridano odkazu: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " zpet do hlavniho menu" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Správa balíčků:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr " Spravovat Odkazy:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Co chcete přesunout?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Co chcete smazat?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Co chcete restartovat?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Vyberte co chcete udělat, nebo zadejte číslo balíčku." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "smazat" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "přesunout" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "restartovat" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - predchozi" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - dalsi" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Prikazova radka" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Stahovani:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Rychlost: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Velikost: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Dokonceno za: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "ceka: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Stav:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "pozastaveno" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "probíhá" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "celková rychlost" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Souborů ve frontě" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Celken" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menu:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Pridat Odkazy" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Správa fronty" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Správa Sběrače" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Pozastavit/Rozbehnout server" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Ukoncit server" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Konec" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Prosim použijte tuto syntaxi: add <Jmeno balicku> <odkaz> <odkaz2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Kontroluji %d odkazů:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Soubor neexistuje." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad byl ukoncen" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Vypise stav serveru" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Vypise stahovani ve fronte" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Vypise stahovani ve sberaci" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Prida balicek do fronty" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Přidá balíček do sběrače" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Smazat soubory z fronty/sberace" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Smazat balicky z fronty/sberace" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Přesun balíčku z fronty do sběrače nebo naopak" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Restart souborů" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Restart balíčků" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Zkontrolovat stav online - funguje s místním zásobníkem" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Zkontroluje online stav souboru ze zásobníku" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Pozastavit server" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "pokracovat ve stahovani" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Prepnout pozastaveni/beh" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "ukoncit server" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Seznam prikazu:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Nemohu zapsat nastavení uživatele" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "K pripojeni na toto pyLoad jadro potrebujete py-openssl." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adresa: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Uzivatelske jmeno: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Heslo: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Prihlasovaci udaje jsou chybne." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Nelze vytvorit pripojeni k %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Byly zadany prikazy, interaktivni mod je ignorovan." + diff --git a/locale/cs/LC_MESSAGES/core.po b/locale/cs/LC_MESSAGES/core.po new file mode 100644 index 000000000..1047d81ba --- /dev/null +++ b/locale/cs/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Czech\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Chyba při provádění %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Aktivace %(name)s selhala" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Aktivovaná rozšíření: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Deaktivovaná rozšíření: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Aktivuji Pluginy..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Deaktivuju pluginy..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Certifikaty SSL nenalezeny." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "WebUI není k dispozici" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Nepodařilo se spustit webový server: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Nepodařilo se importovat webový server: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Tento server nenabizi SSL, zvazte pouziti rezimu s vlakny" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Vzdálený" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Dlouhý popis" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Aktivní" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adresa" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Log" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Velikost v kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Složka" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Počet" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Oprávnění" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Název skupiny" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Uživatelské jméno" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Obecné" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Jazyk" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Použít kontrolní součet" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Minimální volné místo (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "CPU priorita" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL certifikát" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL klíč" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Webové rozhraní" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Server" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Použít HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Heslo" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protokol" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Spustit" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Stáhnout" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Maximum souběžných stahování" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Omezení rychlosti stahování" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Přeskočit již existující soubory" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Maximální rychlost stahování v kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Podpora IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Maximální počet spojení na jedno stahování" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Restartovat selhaná stahování po startu" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Čas stahování" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Stahování chunků selhalo, přecházím na jediné přepojení | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Oveřovací kód" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Prosím vyplňte ověřovací kód." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Vzdalena chyba systemu: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Zahajuji %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Spousteni backendu %(name)s selhalo | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "žádný" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "offline" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "online" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "ve fronte" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "pozastaveno" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "hotovo" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "preskoceno" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "selhalo" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "spoustim" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "ceka" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "stahuji" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "doc. nedostupne" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "zruseno" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "dekoduji" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "zpracovavam" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "vlastni" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "neznamy" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Balicek dokoncen: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Uživatel '%s' se snažil přihlásit" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Prijat Quit signal" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad je jiz spusten pod pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Zmena skupiny selhala: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Zmena uzivatele selhala: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Spoustim" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Nastaven domovsky adresar: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Všechny odkazy odstraněny" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Čas stahování: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Volne misto: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Aktivuji Ucty..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Restartuji neúspěšná stahování..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad je spusten" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "restartuji pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad se ukonci" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "vypinani..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "chyba pri vypinani" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad byl zastaven z terminálu" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Databáze byla odstraněna z důvodu nekompatibilní verze." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Rozšifrování selhalo" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Rozšifrováno %(count)d odkazů a vloženo do balíčku %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Nerozšifrován žádný odkaz" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Ziskavani informaci pro %(name)s selhalo | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Reconnect selhal: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Skript pro reconnect nenalezen!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Spoustim reconnect" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Provadeni skriptu pro Reconnect selhalo!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Reconnectnuto, nova IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Není dostatek místa na zařízení" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Zahajuji stahovani: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Stahovani dokonceno: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "V pluginu %s chybi funkce." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Stahovani zruseno: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Stahovani obnoveno: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Zdroj stahovani je offline: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Zdroj stahovani je docasne nedostupny: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Stahovani selhalo: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "K hostiteli se nelze pripojit, nebo bylo pripojeni resetovano, cekam 1 minutu do dalsiho pokusu." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Stahovani preskoceno: %(name)s v dusledku %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Vnitřní chyba serveru" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Nastala chyba" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Chyba pri importu %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Nebyl detekován žádný JS engine. Prosím nainstalujte Spidermonkey, ossp-js, pyv8, nodejs, nebo rhino" + diff --git a/locale/cs/LC_MESSAGES/django.mo b/locale/cs/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 3d771c127..000000000 --- a/locale/cs/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/cs/LC_MESSAGES/plugins.po b/locale/cs/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..e157f9382 --- /dev/null +++ b/locale/cs/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Czech\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Stahování chunků selhalo, přecházím na jediné přepojení | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Nastavení uživatelů a skupin se nezdařilo: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Špatný autentizační kód, stahování bude restartováno" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Žádné volné sloty" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Pro tento soubor potřebujete prémiový účet" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Dešifrování selhalo" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Kód chyby:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Nejste přihlášeni." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Neplatný API klíč" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Soubor není dočasně k dispozici" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Stažený soubor byl prázdný" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Špatné heslo" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Chyba: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Skript není spustitelný:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s kreditů zbývá" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Nelze odeslat odpověď." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Přidán %s ze sledované složky" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Balicek dokoncen: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Pluginy byly aktualizovány, prosím restartujte pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Pluginy aktualizovány a znovu načteny" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Žádné aktualizace rozšíření" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Žádné aktualizace pro pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Je dostupná nové verze pyLoad (%s) ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Získejte ji zde: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Chyba při aktualizaci %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Nesprávná verze" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Nelze aktivovat %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Aktivní" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Kontrola balíčku %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Rozbalit do %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "rozbaluji" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Chráněno heslem" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Chybné heslo" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Mažu %s souborů" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Chyba archivu" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC nesouhlasí" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Neznámá chyba" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Aktivováno %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/cs/LC_MESSAGES/pyLoad.mo b/locale/cs/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index a0c01d4e9..000000000 --- a/locale/cs/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/cs/LC_MESSAGES/pyLoadCli.mo b/locale/cs/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 6f8b94b67..000000000 --- a/locale/cs/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/cs/LC_MESSAGES/pyLoadGui.mo b/locale/cs/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index c913703f1..000000000 --- a/locale/cs/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/cs/LC_MESSAGES/setup.mo b/locale/cs/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 91ab321ec..000000000 --- a/locale/cs/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/cs/LC_MESSAGES/setup.po b/locale/cs/LC_MESSAGES/setup.po new file mode 100644 index 000000000..c69af58dd --- /dev/null +++ b/locale/cs/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Czech\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Chcete konfigurovat pyLoad pomocí webového rozhraní?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Potřebujete prohlížeč a připojení k tomuto počítači." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Vitejte v konfiguracnim pruvodci programu pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Zkontroluje vas system a provede zakladni nastaveni pro spusteni programu pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Hodnota v zavorkach [] je vzdy vychozi," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "v pripade, ze ji nechcete zmenit nebo si nejste jisti co vybrat, jen stisknete enter." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Nezapomente: Tohoto pruvodce muzete kdykoli sputit znovu pouzitim parametru --setup nebo -s pri startu pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Pokud máte nějaké problémy s tímto pomocníkem, použijte zkratku CTRL + C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "pro zruseni a zamezeni jeho automatickeho spusteni pri dalsim startu pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Az budete pripraveni na kontrolu systemu, stisknete enter." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Chybějící funkce: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto nedostupne" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Toto potrebujede k dekodovani souboru kontejneru." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL nedostupne" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Je potreba k vytvoreni zabezpeceneho pripojeni k jadru nebo webovemu rozhrani." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "rozpoznavani Caprtchy nedostupne" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Je potreba jen pro nektere filehostingy pro neplaceny pristup." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "nenalezeno zadne jadro JavaScriptu" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Toto budete potrebovat pro nektere Click'N'Load linky. Naistalujte Spidermonkey, ossp-js, pyv8 nebo rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Pokracovat v instalaci?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Chcete zmenit cestu k nastaveni? Stavajici je %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Zmenit cestu k nastaveni?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Chcete nastavit prihlasovaci udaje a zakladni volby?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Doporuceno pri prvnim spusteni." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Vytvorit zakladni nastaveni?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Prejete si nastavit ssl?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Nastavit ssl?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Prejete si nastavit webove rozhrani?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Nastavit webove rozhrani?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Nastaveni uspesne dokonceno." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Stiknete enter pro ukonceni a restartujte pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Zakladni nastaveni ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Nasledujici prihlasovaci udaje jsou platne pro CLI, GUI a webove rozhrani." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Uživatelské jméno" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Externí klientské aplikace (např. GUI a CLI) vyžadují vzdálený přístup pro fungovaní přes síť." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Pokud však chcete používat pouze webové rozhraní, můžete jej zakázat a ušetřit tak paměť." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Povolit vzdálený přístup" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Jazyk" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Max soubeznych stahovani" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Pouzivat Reconnect?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Umisteni skriptu pro Reconnect" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Nastaveni Weboveho rozhrani ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Aktivovat webove rozhrani?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Adresa pro naslouchani, pokud pouzijete 127.0.0.1 nebo localhost, bude webove rozhrani pristupne pouze lokalne." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adresa" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad nabizi mnoho vezri administrace, nize kratke vysvetleni." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Muze byt pouzit apachem, lighttpd, vyzaduje vsak nastaveni, ktere nemusi byt snadne." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Stahnete jej zde: https://github.com/jonashaag/bjoern a zkompilujte" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Pozor: V nekterych zvlastnich pripadech neni vestaveny server funkcni. Pokud mate problemy s webovym rozhranim," + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "vratte se sem a zmente vestaveny server za server s vlakny." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Server" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Nastaveni SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Pro vytvoreni ssl certifikatu spustte tyto prikazy v konfiguracni slozce pyLoad:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Pokud jste skoncili a vse probehlo v poradku, muzete aktivovat SSL." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Aktivovat SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Zvolte akci" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Vytvorit/Upravit uzivatele" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Seznam uzivatelu" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Odstranit uzivatele" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Konec" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Uzivatele" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Stisknete enter pro ukonceni." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Nastaveni cesty ke konfiguracnim souborum selhalo: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "a" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Heslo: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Heslo je příliš krátké. Použijte nejméně 4 symboly." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Heslo (znovu): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Hesla se neshoduji." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "ano" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "pravda" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "p" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "ne" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "nepravda" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "n" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Neplatne zadani" + diff --git a/locale/cs/LC_MESSAGES/webUI.po b/locale/cs/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..43fa763e8 --- /dev/null +++ b/locale/cs/LC_MESSAGES/webUI.po @@ -0,0 +1,133 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Czech\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "neomezený" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Účty" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Přidat" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Zavřít" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Smazat" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Zrušit" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Odeslat" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/da/LC_MESSAGES/cli.po b/locale/da/LC_MESSAGES/cli.po new file mode 100644 index 000000000..b02d2d75f --- /dev/null +++ b/locale/da/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Danish\n" +"Language: da_DK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Indsæt de links du ønsker at tilføje" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Skriv %s når du er færdig." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Links tilføjet:" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "tilbage til hovedmenu" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Håndtér pakker:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Håndtér Links:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Hvad vil du flytte?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Hvad vil du slette?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Hvad vil du genstarte?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "slet" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "flyt" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "genstart" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "- forrige " + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "- næste" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Hentes:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Hastighed:" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Størrelse:" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Færdig om:" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "ID:" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "Venter:" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "Tilføj Links" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Genoptag/Pause Server" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "Afslut Server" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "Afslut" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Brug venligst denne syntaks: add <Pakkens Navn> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad var afslutte" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Udskriver server status" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Flyt pakker fra kø til samler og opmvendt" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Genstart filer" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Genstart pakker" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Pause serveren" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "Fortsæt hentning" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Pause/Genoptag" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "Afslut server" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Kommando liste" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Kunne ikke skrive til bruger config filen" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Du mangler py-openssl for at tilslutte til denne pyLoad Kerne" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adresse:" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port:" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Brugernavn:" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Kode:" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Login oplysninger er forkerte" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Can ikke etablere forbindelse til %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Interaktiv tilstand ignoreres da du gik nogle kommandoer." + diff --git a/locale/da/LC_MESSAGES/core.po b/locale/da/LC_MESSAGES/core.po new file mode 100644 index 000000000..538483535 --- /dev/null +++ b/locale/da/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Danish\n" +"Language: da_DK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "Starter" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Starter" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Fri plads: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Aktiverer konti..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "genstarter pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad afslutter" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "lukker ned..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "fejl ved nedlukning" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/da/LC_MESSAGES/plugins.po b/locale/da/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..8e2504dbe --- /dev/null +++ b/locale/da/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Danish\n" +"Language: da_DK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/da/LC_MESSAGES/setup.po b/locale/da/LC_MESSAGES/setup.po new file mode 100644 index 000000000..91a9379fd --- /dev/null +++ b/locale/da/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Danish\n" +"Language: da_DK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Kode:" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/da/LC_MESSAGES/webUI.po b/locale/da/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..c86222cd9 --- /dev/null +++ b/locale/da/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Danish\n" +"Language: da_DK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/de/LC_MESSAGES/cli.po b/locale/de/LC_MESSAGES/cli.po new file mode 100644 index 000000000..0fd393395 --- /dev/null +++ b/locale/de/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Paket hinzufügen:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Gib einen Namen für das neue Paket ein" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paket: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Kopiere die Links, die du hinzufügen willst." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Drücke %s wenn du fertig bist." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Links hinzugefügt: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " Zurück zum Menü" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Pakete verwalten:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Links verwalten:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Was möchtest du verschieben?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Was möchtest du löschen?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Was möchtest du neustarten?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Entscheide was du tun willst oder gebe eine Paket Nummer ein." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "löschen" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "verschieben" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "neustarten" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - vorige Seite" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - nächste Seite" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Kommandozeile" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Downloads:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Geschwindigkeit: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Größe: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Fertig in: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "wartend: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Status:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "pausiert" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "läuft" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Gesamtgeschwindigkeit" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Dateien in Warteschlange" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Gesamt" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menü:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Links hinzufügen" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Warteschlange verwalten" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Linksammler anpassen" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Server fortsetzen/pausieren" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Server beenden" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Beenden" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Bitte benutze folgenden Syntax: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Überprüfe %d Links:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Datei existiert nicht." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad wurde beendet" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Gibt den Server Status aus" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Gibt die Warteschlange aus" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Gibt die Links im Linksammler aus" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Fügt Packete der Warteschlange hinzu" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Fügt ein Paket zum Linksammler hinzu" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Löscht Dateien aus der Warteschlange/Linksammler" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Löscht Packete aus der Warteschlange/Linksammler" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Verschiebt Pakete aus der Warteschlange in den Linksammler oder umgekehrt" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Dateien neustarten" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Pakete neustarten" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Überprüfe Onlinestatus, funktioniert mit lokalen Containern" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Onlinestatus eines Containers prüfen" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "pausiert den Server" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "Downloads fortfahren" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Pause/Fortsetzen" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "Server beenden" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Liste aller Befehle:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Konnte Benutzer-Konfigurationsdatei nicht schreiben" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Du benötigst py-openssl um dich zu diesem pyLoad Server verbinden zu können." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adresse: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Benutzername: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Passwort: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Logindaten sind falsch." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Konnte keine Verbindung zu %(addr)s:%(port)s aufbauen." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Befehle im interaktiven Modus werden ignoriert, wenn zusätzliche Befehle übergeben wurden." + diff --git a/locale/de/LC_MESSAGES/core.po b/locale/de/LC_MESSAGES/core.po new file mode 100644 index 000000000..b8d7a2f54 --- /dev/null +++ b/locale/de/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Fehler beim Ausführen von %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Konnte %(name)s nicht aktivieren" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Aktivierte Addons: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Deaktivierte Erweiterungen: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Aktiviere Plugins..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Deaktiviere Plugins..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL Zertifikat nicht gefunden." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "Webschnittstelle nicht verfügbar" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Die Webschnittstelle läuft im Entwicklungsmodus" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Start des Webservers fehlgeschlagen: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Import des Webservers fehlgeschlagen: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "SSL funktioniert mit diesem Server nicht, benutze bitte alternativ den 'threaded' Server" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Starte %(name)s Webserver: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Fernsteuerung" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Beschreibung" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Ausführliche Beschreibung" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Aktiviert" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adresse" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Log" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Größe in kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Verzeichnis" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Datei Log" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Anzahl" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Archiviere Log -Daten" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Berechtigungen" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Gruppenname" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Gruppe und Benutzer der Downloads ändern" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Datei Modus der Downloads ändern" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Benutzername" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Datei Modus der Downloads" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Gruppe der laufenden Prozesse ändern" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Berechtigungsmodus für Verzeichnisse" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Benutzer des laufenden Prozesses ändern" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Allgemein" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Sprache" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Download Verzeichnis" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Nutze Prüfsummen" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Erzeuge ein Verzeichnis für jedes Paket" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Debug Modus" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Minimal verbleibender Speicher (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "CPU-Priorität" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL Zertifikat" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL Schlüssel" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Webschnittstelle" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Vorlage" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Pfadpräfix" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Server" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Bevorzuge bestimmte Server" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Nutze HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Entwicklungsmodus" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Nutze Proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Passwort" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protokoll" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Neuverbinden" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Ende" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Erneuerung der Verbindung benutzen" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Methode" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Start" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Download" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Max. parallele Downloads" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Begrenze die Downloadgeschwindigkeit" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Zu verwendende Download Schnittstelle (Ip oder Name)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Überspringe vorhandene Dateien" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Max. Downloadgeschwindigkeit in kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "IPv6 zulassen" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Max. Verbindungen je Download" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Starte fehlgeschlagene Downloads beim Start neu" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Downloadzeit" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Eine Download Verbindung ist fehlggeschlagen, Falle zurück auf eine Verbindung | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Paket %(name)s als Ordner %(folder)s hinzugefügt" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Links %d zu Paket hinzugefügt" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Unbekanntes Account Plugin %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Abfrage" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Captcha-Anfrage" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Bitte geben Sie das Captcha ein." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Remote Backend Fehler: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Starte %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Konnte Backend %(name)s nicht laden | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "keine" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "Offline" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "Online" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "eingereiht" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "pausiert" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "Fertig" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "übersprungen" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "fehlgeschlagen" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "starte" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "wartend" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "downloade" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "kurzzeitig offline" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "abgebrochen" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "entschlüsseln" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "verarbeite" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "benutzerdefiniert" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "unbekannt" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paket fertiggestellt: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Benuzer '%s' versucht sich einzuloggen" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Beenden Signal erhalten" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad ist bereits gestartet mit der pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Fehler beim Ändern der Gruppe: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Fehler beim Ändern des Benutzers: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "starte" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Benutze Home-Verzeichnis: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Alle Links entfernt" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Downloadzeit: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Freier Speicher: %sGB" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Aktiviere Accounts..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Neustart von fehlgeschlagenen Downloads..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad ist gestartet und läuft" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "starte pyLoad neu" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad wird beendet" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "Beenden..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "Fehler beim Beenden" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad wurde vom Terminal beendet" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Die Datenbank wurde wegen inkompatibler Version gelöscht." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Entschlüsseln fehlgeschlagen" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "%(count)d entschlüsselte Links in Paket %(name)s abgelegt" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Keine Links entschlüsselt" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Info Holen für %(name)s fehlgeschlagen | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Reconnect fehlgeschlagen: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Reconnect Skript nicht gefunden!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Starte Reconnect" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Fehler beim Ausführen des Reconnect Skripts!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Neue Verbindung aufgebaut, IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Nicht genug Speicherplatz vorhanden" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Starte Download: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Download fertiggestellt: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Plugin %s fehlt eine Funktion." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Download abgebrochen: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Download erneut gestartet: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Download ist nicht verfügbar: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Download ist aktuell offline: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Download fehlgeschlagen: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Verbindungsaufbau zum Host fehlgeschlagen oder die Verbindung wurde zurückgesetzt. Erneuter Versuch in 1 Minute..." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Download übersprungen: %(name)s wegen %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Interner Serverfehler" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Ein Fehler ist aufgetreten" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Fehler beim importieren von %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Keine Javascript-Engine erkannt. Bitte installieren Sie entweder Spidermonkey, ossp-js, pyv8 oder rhino" + diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 017f373dc..000000000 --- a/locale/de/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/de/LC_MESSAGES/plugins.po b/locale/de/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..486061a58 --- /dev/null +++ b/locale/de/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Eine Download Verbindung ist fehlggeschlagen, Falle zurück auf eine Verbindung | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil und Tesseract sind nicht installiert und kein Client ist verbunden für Captcha decrypting" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Kein Captcha Ergebnis in angemessener Zeit bekommen." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Benutzer und Gruppe setzen fehlgeschlagen: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Nicht vorhandene Datei oder fehlerhaftes Protokoll" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Traffic Share (direkter Download)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Von dieser Ip Adresse wird schon gedownloaded, warte 60 Sekunden" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Ungültiger Auth Code, Download wird neugestartet" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Keine freien Slots" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Du brauchst einen premium Account für diese Datei" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Dateiname als ungültig gemeldet" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Bitte geben Sie Ihre %s Kontodaten ein oder deaktivieren Sie dieses Plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Entschlüsselung fehlgeschlagen" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Für die Datei wurde kein Schlüssel in der URL übertragen" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Fehler Code:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Parallel Download Fehler, warte 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Nicht eingeloggt." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "API Schlüssel ungültig" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Nicht genügend Traffic übrig" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Traffic überschritten" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Authorisierung benötigt (Benutzername:Passwort)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Datei zur Zeit nicht verfügbar" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: warte zwischen den Downloads %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: Warte auf Captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Die heruntergeladene Datei ist leer" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Der HTML Code in der heruntergeladenen Datei(%s) führte zu einem Umleitungsfehler? Der Download wird neugestartet." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Konnte nicht mit Account %(user)s | %(msg)s einloggen" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Falsches Passwort" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Hole Account Info für %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Fehler: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Deine Uhrzeit %s hat ein falsches Format, benutze: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Account %s hat nicht genügend Traffic, versuche es erneut in 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Account %s ist abgelaufen, versuche es erneut in 1Std" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Login mit %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Fehler beim ausführen von Addon: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Activiere direkten Download in deinem Bitshare Account" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Downloadlimit erreicht" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Bitte ergänzen Sie zuerst Ihr premium.to Konto und starten Sie pyLoad neu" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Installierte Scripte für %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script nicht ausführbar:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Fehler in %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s Credits verbleibend" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Konnte keine Antwort senden." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Dein CaptchaTrader Account hat nicht genügend Credits" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Neue CaptchaID von Upload: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Dein Captcha 9kw.eu Account hat nicht genügend Credits" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Bitte ergänzen Sie zuerst Ihr rehost.to Konto und starten Sie pyLoad neu" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "%s aus HotFolder hinzugefügt" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Port 9666 wird bereits benutzt" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paket fertiggestellt: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Download abgeschlossen: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Dein ExpertDecoders Account hat nicht genug Credits" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Plugins wurden aktualisiert, bitte starten Sie PyLoad neu ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins aktualisiert und neu geladen" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Keine Plugin Updates verfügbar" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Keine Updates für pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Neue pyLoad Version %s verfügbar ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Downloade es hier: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Konnte nicht zum Server verbinden, um auf Updates zu überprüfen" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Neue Version von %(type)s | %(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Fehler beim Aktualisieren von %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Versionskonflikt" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Kein %s installiert" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Konnte %s nicht aktivieren" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Aktiviert" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Keine Entpacken Plugins aktiviert" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Paket %s eingereiht für späteres entpacken" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Checke Paket %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Entpacke nach %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Keine Dateien zum entpacken gefunden" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "entpacke" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Passworgeschützt" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Lösche %s Dateien" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Entpacken abgeschlossen" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Archiv Fehler" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC Fehler" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Unbekannter Fehler" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Setzen von Benutzer und Gruppe fehlgeschlagen" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Liste von Cryptern nicht gefunden" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Liste von Cryptern ist leer" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Download fertig: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Neue Captcha Anfrage: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Beantworte Captcha mit \"c %s Text\"" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Bitte ergänzen Sie zuerst Ihr premiumize.me Konto und starten Sie pyLoad neu." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d Credits verbleibend" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "%s aktiviert" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Kein Hoster geladen" + diff --git a/locale/de/LC_MESSAGES/pyLoad.mo b/locale/de/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index e1e29cebc..000000000 --- a/locale/de/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/de/LC_MESSAGES/pyLoadCli.mo b/locale/de/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index e46503121..000000000 --- a/locale/de/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/de/LC_MESSAGES/pyLoadGui.mo b/locale/de/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 8ead8383c..000000000 --- a/locale/de/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/de/LC_MESSAGES/setup.mo b/locale/de/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 80ca3553e..000000000 --- a/locale/de/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/de/LC_MESSAGES/setup.po b/locale/de/LC_MESSAGES/setup.po new file mode 100644 index 000000000..1414e0a9f --- /dev/null +++ b/locale/de/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Möchtest du pyLoad über das Webinterface konfigurieren?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Du brauchst dafür einen Browser und eine Verbindung zu diesem PC." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "Die URL wäre: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Initiales Webinterface zur Konfiguration starten?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Willkommen im pyLoad Konfigurations Assistenten." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Er wird jetzt dein System überprüfen und Grundeinstellungen vornehmen." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Die Werte in Klammer sind die Standard Werte," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "Falls du sie nicht ändern möchtest oder unsicher bist, drücke einfach Enter." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Beachte: Du kannst diesen Assistenten jederzeit wieder mit dem --setup oder -s Parameter starten." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Wenn du mit diesem Assistenten irgendwelche Probleme hast drücke STRG+C" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "um abzubrechen und ihn nicht mehr automatisch zu starten." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Wenn du für den System-Check bereit bist, drücke enter." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Fehlende Features: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "kein py-crypto verfügbar" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Du brauchst es, um Container Dateien zu öffnen." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "Kein SSL verfügbar" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Wird gebraucht falls du eine SSL Verbindung zu Core oder Webinterface einstellen willst." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Falls du nur lokal zugreifen willst, ist SSL überflüssig." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "keine Captcha Erkennung verfügbar" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Wird für einige Hoster als Freeuser benötigt." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "keine JavaScript Engine gefunden" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Du benötigst das für einige Click'n'Load links. Installiere Spidermonkey, ossp-js, pyv8 oder rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Wenn du möchtest, kannst du das Setup nun abbrechen und Abhängigkeiten auflösen." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Mit Setup fortfahren?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Möchtest du den Configordner ändern? Jetziger ist %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Wenn du pyLoad auf einem Server nutzt oder wenn die home Partition auf einem internen Flashspeicher liegt wäre es eine gute Idee dies zu ändern." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Config Pfad ändern?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Wollen Sie die Login-Daten und Grundeinstellungen festlegen?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Wird für den ersten Start empfohlen." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Erstelle Grundeinstellungen?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Willst du SSL konfigurieren?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Konfiguriere SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Willst du das Webinterface konfigurieren?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Konfiguriere Webinterface?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Setup erfolgreich beendet." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Drücke Enter zum beenden und starte pyLoad neu" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Das Webinterface wird für das Setup ausgeführt." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Grundeinstellungen ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Die folgenden Anmeldedaten sind für CLI, GUI und Webinterface gültig." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Benutzername" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Externe Clients (GUI, CLI und andere) benötigen Fernzugriff, um via Netzwerk zugreifen zu können." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Solltest Du jedoch nur das Webinterface nutzen, kannst Du Ihn deaktivieren, um den Speicherverbrauch zu verringern." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Aktiviere Fernzugriff" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Sprache" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Download Ordner" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Maximale parallele Downloads" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Benutze Reconnect?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Reconnect Script Pfad" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Webinterface Setup ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Aktiviere Webinterface?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Listen Adresse. Falls du 127.0.0.1 oder localhost einträgst wird das Webinterface nur lokal erreichbar sein." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adresse" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad verfügt über verschiedene Webserver, eine kurze Erklärung folgt." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Standard-Server, dieser Server bietet SSL und ist eine gute Alternative zum builtin-Server." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Kann von apache, lighttpd benutzt werden. Muss konfiguriert werden, welches aber nicht sehr einfach ist." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Sehr schnelle Alternative, die in C geschrieben ist aber libev und Linux-Kenntnisse erfordert." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Downloaden von: https://github.com/jonashaag/bjoern, danach kompilieren." + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "und bjoern.so nach Pyload/Lib kopieren" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Achtung: In manchen Fällen funktioniert der builtin Server nicht, wenn du Probleme mit dem Webinterface bemerkst." + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "komme zurück und ändere den builtin server zu threaded hier" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Server" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL Setup ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Führen Sie die folgenden Kommandos im pyLoad Konfigurationsordner aus, um ein SSL-Zertifikate zu erstellen:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Falls du fertig bist und alles erfolgreich war, kannst du nun SSL aktivieren." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "SSL aktivieren?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Wähle Aktion" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Erstelle/Bearbeite Nutzer" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Liste Nutzer auf" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Entferne Nutzer" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Verlassen" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Nutzer" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Setze neuen Config Pfad, momentane Konfiguration wird nicht übernommen!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Einstellungs Pfad" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Einstellungs Pfad geändert, Setup wird jetzt geschlossen, bitte neustarten zum fortsetzen." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Drücke Enter zum Beenden." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Config Pfad setzen fehlgeschlagen: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "j" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Passwort: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Passwort zu kurz. Benutze mindestens 4 Symbole." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Password (nochmal): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Passwörter stimmen nicht überein." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "ja" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "wahr" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "w" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "nein" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "falsch" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Ungültige Eingabe" + diff --git a/locale/de/LC_MESSAGES/webUI.po b/locale/de/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..8bae5ada7 --- /dev/null +++ b/locale/de/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "nicht verfügbar" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "unbegrenzt" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Admin" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Einrichtung" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Konto hinzufügen" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Konten" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Lokal" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Suche" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Typ" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Alle" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Fertig" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "nicht abgeschlossen" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Fehlgeschlagen" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 Paket" +msgstr[1] "%d Pakete" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 Datei" +msgstr[1] "%d Dateien" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Konto hinzufügen" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Bitte gib deine Kontodaten ein" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Wähle ein Plugin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Bitte wähle ein Plugin, das du konfigurieren möchtest" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Hinzufügen" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Schließen" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Bitte bestätigen" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Möchtest du die ausgewählten Elemente löschen?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Löschen" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Abbrechen" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Übernehmen" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "Aktiv..." + diff --git a/locale/django.pot b/locale/django.pot deleted file mode 100644 index 81c9c7b6b..000000000 --- a/locale/django.pot +++ /dev/null @@ -1,686 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR pyLoad Team -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: pyLoad 0.4.9\n" -"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" -"POT-Creation-Date: 2011-12-07 19:21+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: module/web/translations.js:1 module/web/templates/default/base.html:123 -#: module/web/templates/default/base.html:124 -#: module/web/templates/default/settings_item.html:14 -msgid "on" -msgstr "" - -#: module/web/translations.js:2 module/web/templates/default/captcha.html:7 -msgid "Please read the text on the captcha." -msgstr "" - -#: module/web/translations.js:3 -msgid "Settings saved." -msgstr "" - -#: module/web/translations.js:4 module/web/templates/default/base.html:123 -#: module/web/templates/default/base.html:124 -#: module/web/templates/default/settings_item.html:16 -msgid "off" -msgstr "" - -#: module/web/translations.js:5 -msgid "Success" -msgstr "" - -#: module/web/translations.js:6 -msgid "Passwords did not match." -msgstr "" - -#: module/web/translations.js:7 -msgid "Delete Link" -msgstr "" - -#: module/web/translations.js:8 -msgid "pyLoad restarted" -msgstr "" - -#: module/web/translations.js:9 -msgid "You are really sure you want to quit pyLoad?" -msgstr "" - -#: module/web/translations.js:10 -msgid "Please Enter a packagename." -msgstr "" - -#: module/web/translations.js:11 -msgid "Please click on the right captcha position." -msgstr "" - -#: module/web/translations.js:12 -msgid "Error occured." -msgstr "" - -#: module/web/translations.js:13 -msgid "New Captcha Request" -msgstr "" - -#: module/web/translations.js:14 -msgid "Failed" -msgstr "" - -#: module/web/translations.js:15 -msgid "No Captchas to read." -msgstr "" - -#: module/web/translations.js:16 -#: module/web/templates/default/filemanager.html:65 -#: module/web/templates/default/folder.html:14 -msgid "Folder is empty" -msgstr "" - -#: module/web/translations.js:17 -msgid "Restart Link" -msgstr "" - -#: module/web/translations.js:18 -msgid "New folder" -msgstr "" - -#: module/web/translations.js:19 -msgid "Are you sure you want to restart pyLoad?" -msgstr "" - -#: module/web/pyload_app.py:125 -msgid "You dont have permission to access this page." -msgstr "" - -#: module/web/pyload_app.py:193 -msgid "Download directory not found." -msgstr "" - -#: module/web/pyload_app.py:260 module/web/pyload_app.py:267 -msgid "unlimited" -msgstr "" - -#: module/web/pyload_app.py:262 module/web/pyload_app.py:269 -msgid "not available" -msgstr "" - -#: module/web/pyload_app.py:509 -msgid "Run pyLoadCore.py -s to access the setup." -msgstr "" - -#: module/web/json_app.py:60 -#, python-format -msgid "waiting %s" -msgstr "" - -#: module/web/templates/default/info.html:14 -#: module/web/templates/default/info.html:15 -#: module/web/templates/default/home.html:239 -msgid "Information" -msgstr "" - -#: module/web/templates/default/info.html:18 -msgid "News" -msgstr "" - -#: module/web/templates/default/info.html:21 -msgid "Support" -msgstr "" - -#: module/web/templates/default/info.html:37 -msgid "System" -msgstr "" - -#: module/web/templates/default/info.html:40 -msgid "Python:" -msgstr "" - -#: module/web/templates/default/info.html:44 -msgid "OS:" -msgstr "" - -#: module/web/templates/default/info.html:48 -msgid "pyLoad version:" -msgstr "" - -#: module/web/templates/default/info.html:52 -msgid "Installation Folder:" -msgstr "" - -#: module/web/templates/default/info.html:56 -msgid "Config Folder:" -msgstr "" - -#: module/web/templates/default/info.html:60 -msgid "Download Folder:" -msgstr "" - -#: module/web/templates/default/info.html:64 -msgid "Free Space:" -msgstr "" - -#: module/web/templates/default/info.html:68 -msgid "Language:" -msgstr "" - -#: module/web/templates/default/info.html:72 -msgid "Webinterface Port:" -msgstr "" - -#: module/web/templates/default/info.html:76 -msgid "Remote Interface Port:" -msgstr "" - -#: module/web/templates/default/downloads.html:6 -#: module/web/templates/default/base.html:93 -#: module/web/templates/default/home.html:220 -msgid "Downloads" -msgstr "" - -#: module/web/templates/default/filemanager.html:19 -msgid "FileManager" -msgstr "" - -#: module/web/templates/default/admin.html:8 -#: module/web/templates/default/admin.html:9 -#: module/web/templates/default/base.html:59 -msgid "Administrate" -msgstr "" - -#: module/web/templates/default/admin.html:13 -msgid "Quit pyLoad" -msgstr "" - -#: module/web/templates/default/admin.html:14 -msgid "Restart pyLoad" -msgstr "" - -#: module/web/templates/default/admin.html:18 -msgid "To add user or change passwords use:" -msgstr "" - -#: module/web/templates/default/admin.html:19 -msgid "Important: Admin user have always all permissions!" -msgstr "" - -#: module/web/templates/default/admin.html:25 -#: module/web/templates/default/settings.html:91 -#: module/web/templates/default/queue.html:82 -#: module/web/templates/default/window.html:7 -#: module/web/templates/default/home.html:237 -msgid "Name" -msgstr "" - -#: module/web/templates/default/admin.html:28 -#: module/web/templates/default/admin.html:67 -msgid "Change Password" -msgstr "" - -#: module/web/templates/default/admin.html:31 -msgid "Admin" -msgstr "" - -#: module/web/templates/default/admin.html:34 -msgid "Permissions" -msgstr "" - -#: module/web/templates/default/admin.html:41 -msgid "change" -msgstr "" - -#: module/web/templates/default/admin.html:61 -#: module/web/templates/default/admin.html:91 -#: module/web/templates/default/settings.html:167 -#: module/web/templates/default/queue.html:97 -#: module/web/templates/default/captcha.html:33 -msgid "Submit" -msgstr "" - -#: module/web/templates/default/admin.html:69 -msgid "Enter your current and desired Password." -msgstr "" - -#: module/web/templates/default/admin.html:70 -msgid "User" -msgstr "" - -#: module/web/templates/default/admin.html:71 -#: module/web/templates/default/settings.html:179 -msgid "Your username." -msgstr "" - -#: module/web/templates/default/admin.html:75 -msgid "Current password" -msgstr "" - -#: module/web/templates/default/admin.html:76 -#: module/web/templates/default/settings.html:184 -msgid "The password for this account." -msgstr "" - -#: module/web/templates/default/admin.html:80 -msgid "New password" -msgstr "" - -#: module/web/templates/default/admin.html:81 -msgid "The new password." -msgstr "" - -#: module/web/templates/default/admin.html:85 -msgid "New password (repeat)" -msgstr "" - -#: module/web/templates/default/admin.html:86 -msgid "Please repeat the new password." -msgstr "" - -#: module/web/templates/default/admin.html:92 -#: module/web/templates/default/settings.html:198 -#: module/web/templates/default/queue.html:98 -#: module/web/templates/default/window.html:41 -msgid "Reset" -msgstr "" - -#: module/web/templates/default/settings.html:3 -#: module/web/templates/default/settings.html:4 -#: module/web/templates/default/base.html:102 -#: module/web/templates/default/home.html:229 -msgid "Config" -msgstr "" - -#: module/web/templates/default/settings.html:16 -msgid "General" -msgstr "" - -#: module/web/templates/default/settings.html:17 -msgid "Plugins" -msgstr "" - -#: module/web/templates/default/settings.html:18 -msgid "Accounts" -msgstr "" - -#: module/web/templates/default/settings.html:45 -#: module/web/templates/default/settings.html:74 -msgid "Choose a section from the menu" -msgstr "" - -#: module/web/templates/default/settings.html:90 -msgid "Plugin" -msgstr "" - -#: module/web/templates/default/settings.html:92 -#: module/web/templates/default/settings.html:183 -#: module/web/templates/default/login.html:19 -#: module/web/templates/default/queue.html:92 -#: module/web/templates/default/window.html:21 -msgid "Password" -msgstr "" - -#: module/web/templates/default/settings.html:93 -#: module/web/templates/default/home.html:238 -msgid "Status" -msgstr "" - -#: module/web/templates/default/settings.html:94 -msgid "Premium" -msgstr "" - -#: module/web/templates/default/settings.html:95 -msgid "Valid until" -msgstr "" - -#: module/web/templates/default/settings.html:96 -msgid "Traffic left" -msgstr "" - -#: module/web/templates/default/settings.html:97 -msgid "Time" -msgstr "" - -#: module/web/templates/default/settings.html:98 -msgid "Max Parallel" -msgstr "" - -#: module/web/templates/default/settings.html:99 -msgid "Delete?" -msgstr "" - -#: module/web/templates/default/settings.html:121 -msgid "valid" -msgstr "" - -#: module/web/templates/default/settings.html:124 -msgid "not valid" -msgstr "" - -#: module/web/templates/default/settings.html:131 -msgid "yes" -msgstr "" - -#: module/web/templates/default/settings.html:134 -msgid "no" -msgstr "" - -#: module/web/templates/default/settings.html:168 -#: module/web/templates/default/settings.html:197 -#: module/web/templates/default/base.html:117 -msgid "Add" -msgstr "" - -#: module/web/templates/default/settings.html:176 -msgid "Add Account" -msgstr "" - -#: module/web/templates/default/settings.html:177 -msgid "Enter your account data to use premium features." -msgstr "" - -#: module/web/templates/default/settings.html:178 -#: module/web/templates/default/login.html:3 -msgid "Login" -msgstr "" - -#: module/web/templates/default/settings.html:188 -msgid "Type" -msgstr "" - -#: module/web/templates/default/settings.html:189 -msgid "Choose the hoster for your account." -msgstr "" - -#: module/web/templates/default/pathchooser.html:39 -#: module/web/templates/default/pathchooser.html:41 -msgid "Path" -msgstr "" - -#: module/web/templates/default/pathchooser.html:39 -#: module/web/templates/default/pathchooser.html:41 -msgid "absolute" -msgstr "" - -#: module/web/templates/default/pathchooser.html:39 -#: module/web/templates/default/pathchooser.html:41 -msgid "relative" -msgstr "" - -#: module/web/templates/default/pathchooser.html:46 -msgid "name" -msgstr "" - -#: module/web/templates/default/pathchooser.html:47 -msgid "size" -msgstr "" - -#: module/web/templates/default/pathchooser.html:48 -msgid "type" -msgstr "" - -#: module/web/templates/default/pathchooser.html:49 -msgid "last modified" -msgstr "" - -#: module/web/templates/default/pathchooser.html:54 -msgid "parent directory" -msgstr "" - -#: module/web/templates/default/pathchooser.html:70 -msgid "no content" -msgstr "" - -#: module/web/templates/default/setup.html:3 -#: module/web/templates/default/setup.html:4 -msgid "Setup" -msgstr "" - -#: module/web/templates/default/login.html:14 -msgid "Username" -msgstr "" - -#: module/web/templates/default/login.html:29 -msgid "Your username and password didn't match. Please try again." -msgstr "" - -#: module/web/templates/default/login.html:30 -msgid "To reset your login data or add an user run:" -msgstr "" - -#: module/web/templates/default/base.html:20 -#: module/web/templates/default/base.html:139 -msgid "Webinterface" -msgstr "" - -#: module/web/templates/default/base.html:39 -msgid "pyLoad Update available!" -msgstr "" - -#: module/web/templates/default/base.html:46 -msgid "Plugins updated, please restart!" -msgstr "" - -#: module/web/templates/default/base.html:52 -msgid "Captcha waiting" -msgstr "" - -#: module/web/templates/default/base.html:57 -msgid "Logout" -msgstr "" - -#: module/web/templates/default/base.html:61 -msgid "Info" -msgstr "" - -#: module/web/templates/default/base.html:65 -msgid "Please Login!" -msgstr "" - -#: module/web/templates/default/base.html:84 -#: module/web/templates/default/home.html:211 -msgid "Home" -msgstr "" - -#: module/web/templates/default/base.html:87 -#: module/web/templates/default/queue.html:15 -#: module/web/templates/default/window.html:34 -#: module/web/templates/default/home.html:214 -msgid "Queue" -msgstr "" - -#: module/web/templates/default/base.html:90 -#: module/web/templates/default/queue.html:17 -#: module/web/templates/default/window.html:36 -#: module/web/templates/default/home.html:217 -msgid "Collector" -msgstr "" - -#: module/web/templates/default/base.html:99 -#: module/web/templates/default/logs.html:3 -#: module/web/templates/default/logs.html:4 -#: module/web/templates/default/home.html:226 -msgid "Logs" -msgstr "" - -#: module/web/templates/default/base.html:114 -#: module/web/templates/default/logs.html:12 -msgid "Start" -msgstr "" - -#: module/web/templates/default/base.html:115 -msgid "Stop" -msgstr "" - -#: module/web/templates/default/base.html:116 -msgid "Cancel" -msgstr "" - -#: module/web/templates/default/base.html:123 -msgid "Download:" -msgstr "" - -#: module/web/templates/default/base.html:124 -msgid "Reconnect:" -msgstr "" - -#: module/web/templates/default/base.html:125 -msgid "Speed:" -msgstr "" - -#: module/web/templates/default/base.html:126 -msgid "Active:" -msgstr "" - -#: module/web/templates/default/base.html:127 -msgid "Reload page" -msgstr "" - -#: module/web/templates/default/base.html:157 -msgid "loading" -msgstr "" - -#: module/web/templates/default/base.html:166 -msgid "Back to top" -msgstr "" - -#: module/web/templates/default/logs.html:12 -msgid "prev" -msgstr "" - -#: module/web/templates/default/logs.html:12 -msgid "next" -msgstr "" - -#: module/web/templates/default/logs.html:12 -msgid "End" -msgstr "" - -#: module/web/templates/default/logout.html:8 -msgid "You were successfully logged out." -msgstr "" - -#: module/web/templates/default/queue.html:25 -msgid "Delete Finished" -msgstr "" - -#: module/web/templates/default/queue.html:26 -msgid "Restart Failed" -msgstr "" - -#: module/web/templates/default/queue.html:65 -msgid "Folder:" -msgstr "" - -#: module/web/templates/default/queue.html:65 -msgid "Password:" -msgstr "" - -#: module/web/templates/default/queue.html:79 -msgid "Edit Package" -msgstr "" - -#: module/web/templates/default/queue.html:80 -msgid "Edit the package detais below." -msgstr "" - -#: module/web/templates/default/queue.html:83 -msgid "The name of the package." -msgstr "" - -#: module/web/templates/default/queue.html:87 -msgid "Folder" -msgstr "" - -#: module/web/templates/default/queue.html:88 -msgid "Name of subfolder for these downloads." -msgstr "" - -#: module/web/templates/default/queue.html:93 -msgid "List of passwords used for unrar." -msgstr "" - -#: module/web/templates/default/window.html:5 -#: module/web/templates/default/window.html:40 -msgid "Add Package" -msgstr "" - -#: module/web/templates/default/window.html:6 -msgid "Paste your links or upload a container." -msgstr "" - -#: module/web/templates/default/window.html:8 -msgid "The name of the new package." -msgstr "" - -#: module/web/templates/default/window.html:12 -msgid "Links" -msgstr "" - -#: module/web/templates/default/window.html:13 -msgid "Paste your links here or any text and press the filter button." -msgstr "" - -#: module/web/templates/default/window.html:14 -msgid "Filter urls" -msgstr "" - -#: module/web/templates/default/window.html:22 -msgid "Password for RAR-Archive" -msgstr "" - -#: module/web/templates/default/window.html:26 -msgid "File" -msgstr "" - -#: module/web/templates/default/window.html:27 -msgid "Upload a container." -msgstr "" - -#: module/web/templates/default/window.html:31 -msgid "Destination" -msgstr "" - -#: module/web/templates/default/home.html:206 -msgid "Active Downloads" -msgstr "" - -#: module/web/templates/default/home.html:240 -msgid "Size" -msgstr "" - -#: module/web/templates/default/home.html:241 -msgid "Progress" -msgstr "" - -#: module/web/templates/default/captcha.html:6 -msgid "Captcha reading" -msgstr "" - -#: module/web/templates/default/captcha.html:13 -msgid "Captcha" -msgstr "" - -#: module/web/templates/default/captcha.html:14 -msgid "The captcha." -msgstr "" - -#: module/web/templates/default/captcha.html:20 -msgid "Text" -msgstr "" - -#: module/web/templates/default/captcha.html:21 -msgid "Input the text on the captcha." -msgstr "" - -#: module/web/templates/default/captcha.html:34 -msgid "Close" -msgstr "" diff --git a/locale/el/LC_MESSAGES/cli.po b/locale/el/LC_MESSAGES/cli.po new file mode 100644 index 000000000..add06684b --- /dev/null +++ b/locale/el/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Greek\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Προσθήκη Πακέτου:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Εισάγετε όνομα για το καινούργιο πακέτο" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Πακέτο: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Ανάλυση των συνδέσμων που θέλετε να προσθέσετε. " + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Πληκτρολογείστε %s όταν τελειώσετε. " + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Σύνδεσμοι που προστέθηκαν:" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "επιστροφή στο αρχικό μενού" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Διαχείριση πακέτων:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Διαχείριση Συνδέσμων:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Τι θέλετε να μετακινήσετε;" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Τί θέλετε να διαγράψετε;" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Τί θέλετε να επανεκκινήσετε;" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Επιλέξτε ενέργεια ή εισάγετε αριθμό πακέτου." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "διαγραφή" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "μετακίνηση" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "επανεκκίνηση" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - προηγούμενο" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - επόμενο" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "Διασύνδεση Γραμμής Εντολών" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Λήψεις:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Ταχύτητα:" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Μέγεθος:" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Ολοκλήρωση σε: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "αναμονή: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Κατάσταση:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "σε παύση" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "εκτελείται" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "συνολική ταχύτητα" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Αρχεία στην ουρά" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Σύνολο" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr " Μενού:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Προσθήκη Συνδέσμων" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Διαχείριση Ουράς" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Διαχείριση Συλλέκτη " + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Ενεργοποίηση/Αδρανοποίηση" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Τερματισμός Διακομιστή" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Έξοδος" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Χρήση με: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Έλεγχος %d συνδέσμων:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Το αρχείο δεν υπάρχει." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "Το pyLoad τερματίστηκε" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Εμφανίζει την κατάσταση του διακομιστή" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Εμφανίζει τις λήψεις σε ουρά" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Εμφανίζει τις λήψεις στο συλλέκτη" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Προσθέτει το πακέτο στην ουρά" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Προσθέτει το πακέτο στο συλλέκτη" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Διαγραφή αρχείου από Ουρά/Συλλεκτη" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Διαγραφή πακέτων από Ουρά/Συλλέκτη" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Μετακίνηση πακέτων από την Ουρά στο Συλλέκτη και αντίστροφα" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Επανεκκίνηση αρχείων" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Επανεκκίνηση πακέτων" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Έλεγχος κατάστασης σύνδεσης. Λειτουργεί με τοπικά container" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Έλεγχος κατάστασης σύνδεσης ενός αρχείου container" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Παύση του διακομιστή" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "συνέχιση λήψεων" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Εναλλαγή παύσης/εκτέλεσης" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "τερματισμός διακομιστή" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Λίστα εντολών:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Αδυναμία εγγραφής του αρχείου ρυθμίσεων του χρήστη" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Απαιτείται το py-openssl για σύνδεση σε αυτόν τον pyLoad Core." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Διεύθυνση:" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Θύρα:" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Όνομα χρήστη:" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Κωδικός χρήστη:" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Τα στοιχεία σύνδεσης είναι λάθος." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Αδυναμία σύνδεσης στη διεύθυνση %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "H αλληλεπιδραστική λειτουργία αγνοήθηκε εφόσον δώσατε κάποιες εντολές." + diff --git a/locale/el/LC_MESSAGES/core.po b/locale/el/LC_MESSAGES/core.po new file mode 100644 index 000000000..066fa727d --- /dev/null +++ b/locale/el/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Greek\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Σφάλμα κατά την εκτέλεση του %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Αποτυχία ενεργοποίησης %(name)s " + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Ενεργοποιημένα πρόσθετα: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Απενεργοποιημένα πρόσθετα: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Ενεργοποίηση Πρόσθετων..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Απενεργοποίηση πρόσθετων..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Δεν βρέθηκαν πιστοποιητικά SSL." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "Η έκδοση του WebUI δεν είναι διαθέσιμη" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Το WebUI εκτελείται σε λειτουργία ανάπτυξης κώδικα" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Αποτυχία έναρξης του διακομιστή web: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Αποτυχία εισαγωγής του διακομιστή web: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Αυτός ο διακομιστής δεν προσφέρει SSL, αντί για αυτόν παρακαλώ σκεφτείτε την περίπτωση χρήσης του threaded" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Εκκινείται ο διακομιστής web %(name)s : %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Απομακρυσμένo" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Περιγραφή" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Πλήρης περιγραφή" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Ενεργοποιήθηκε" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Θύρα" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Διεύθυνση" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Αρχείο καταγραφής" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Μέγεθος σε kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Φάκελος" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Αρχείο καταγραφής" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Αρίθμηση" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Ανακύκλωση αρχείου καταγραφής" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Δικαιώματα" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Όνομα ομάδας" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Αλλαγή ομάδας και χρήστη των λήψεων" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Αλλαγή της κατάστασης αρχείου των λήψεων" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Όνομα Χρήστη" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Λειτουργία αρχείου για τις λήψεις" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Αλλαγή ομάδας για τις διαδικασίες που εκτελούνται" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Λειτουργία δικαιωμάτων φακέλων" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Αλλαγή του χρήστη για τις διαδικασίες που εκτελούνται" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Γενικά" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Γλώσσα" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Φάκελος για λήψεις" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Χρήση ελέγχου Checksum" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Δημιουργία φακέλου για κάθε πακέτο" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Λειτουργία αποσφαλμάτωσης" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Ελέχιστος ελεύθερος χώρος (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Προτεραιότητα CPU" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Πιστοποιητικό SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Κλειδί SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Web περιβάλλον διαχείρισης" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Πρότυπο" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Πρόθεμα διαδρομής" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Διακομιστής" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Προτίμηση σε συγκεκριμένο διακομιστή" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "Διεύθυνση IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Χρήση HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Λειτουργία ανάπτυξης λογισμικού" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Διακομιστής μεσολάβησης" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Χρήση διακομιστή μεσολάβησης" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Κωδικός πρόσβασης" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Πρωτόκολλο" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Επανασύνδεση" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Τέλος" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Χρήση Επανασύνδεσης" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Μέθοδος" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Έναρξη" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Λήψη" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Μέγιστος αριθμός ταυτόχρονων λήψεων" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Περιορισμός ταχύτητας λήψης" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Διεπαφή λήψεων για δέσμευση (διεύθυνση ΙΡ ή όνομα)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Παράλειψη αρχείων που υπάρχουν ήδη" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Μέγιστη ταχύτητα λήψης σε kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Να επιτρέπεται το IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Μέγιστο όριο συνδέσεων για μία λήψη" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Επανεκκίνηση αποτυχημένων λήψεων κατά την εκκίνηση" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Χρόνος λήψης" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Η λήψη των κομματιών απέτυχε, επιστροφή σε κατάσταση μονής σύνδεσης | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Προστέθηκε το πακέτο %(name)s σαν φάκελος %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Προστέθηκαν %d σύνδεσμοι στο πακέτο" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Άγνωστος λογαριασμός στο πρόσθετο %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Ερώτημα" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Αίτηση Captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Παρακαλώ επιλύστε το captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Σφάλμα απομακρυσμένου backend: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Γίνεται εκκίνηση %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Απέτυχε η φόρτωση του backend %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "κανένα" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "αποσυνδεμένος" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "συνδεμένος" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "στην ουρά" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "σε παύση" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "τελείωσε" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "προσπεράστηκε" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "απέτυχε" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "ξεκινάει" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "σε αναμονή" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "σε λήψη" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "προσωρινά χωρίς σύνδεση" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "ματαιώθηκε" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "αποκρυπτογραφείται" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "σε εξέλιξη" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "προσαρμοσμένο" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "άγνωστο" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Ολοκλήρωση πακέτου: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Ο χρήστης '%s' προσπαθεί να συνδεθεί" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Ελήφθη σήμα εγκατάλειψης" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "Το pyLoad εκτελείται ήδη με pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Αποτυχία αλλαγής ομάδας: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Αποτυχία αλλαγής χρήστη: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Σε εκκίνηση" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Χρήση γονικού καταλόγου: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Όλοι οι συνδεσμοι αφαιρέθηκαν" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Χρόνος λήψης: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Ελέυθερος χώρος στο δίσκο: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Ενεργοποίηση Λογαριασμών..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Επανεκκίνηση αποτυχημένων λήψεων..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "Το pyLoad είναι ενεργό και εκτελείται " + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "το pyLoad επανεκκινείται" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "το pyLoad σταματάει" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "κλείσιμο..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "σφάλμα κατα το κλείσιμο" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "το pyLoad σταματήθηκε απο το τερματικό" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Βάση δεδομένων διαγράφηκε λόγω ασυμβατότητας της έκδοσης." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Η αποκρυπτογράφηση απέτυχε" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Αποκρυπτογραφήθηκαν %(count)d σύνδεσμοι στο πακέτο %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Δεν αποκρυπτογραφήθηκε κανένας σύνδεσμος" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Η ανάκτηση πληροφοριών για το %(name)s απέτυχε | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Επανασύνδεση απέτυχε %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Δεν βρέθηκε αρχείο εντολών για επανασύνδεση!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Ξεκινάει η επανασύνδεση" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Αποτυχία εκτέλεσης αρχείου εντολών επανασύνδεσης!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Επανασυνδέθηκε, νέα διέυθυνση IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Δεν έχει απομείνει αρκετός χώρος στην συσκευή" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Η λήψη ξεκινά: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Η λήψη ολοκληρώθηκε: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Στο πρόσθετο %s λείπει μια λειτουργία." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Η λήψη ματαιώθηκε: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Η λήψη επανεκκινήθηκε: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Η λήψη είναι εκτός σύνδεσης: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Η λήψη είναι προσωρινά εκτός σύνδεσης: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Η λήψη απέτυχε: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Αποτυχία σύνδεσης με το διακομιστή ή διακοπή σύνδεσης, αναμονή 1 λεπτό πριν γίνει κι άλλη προσπάθεια." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Έγινε παράλειψη στη λήψη: %(name)s εξαιτίας του %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Εσωτερικό σφάλμα διακομιστή" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Προέκυψε ένα σφάλμα" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Σφάλμα κατά την εισαγωγή %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Καμία μηχανή js δεν βρέθηκε, παρακαλώ εγκαταστήσετε το Spidermonkey, ή το ossp-js, ή το pyv8, ή το rhino" + diff --git a/locale/el/LC_MESSAGES/plugins.po b/locale/el/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..67131b06f --- /dev/null +++ b/locale/el/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Greek\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Η λήψη των κομματιών απέτυχε, επιστροφή σε κατάσταση μονής σύνδεσης | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Το PIL και το tesseract δεν είναι εγκαταστημένα και δεν υπάρχει πρόγραμμα-πελάτης συνδεμένος για αποκρυπτογράφηση captcha" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Δεν ανακτήθηκε αποτέλεσμα captcha εντός του χρονικού ορίου." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Η ρύθμιση χρηστών και ομάδων απέτυχε: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Δεν υπάρχει το αρχείο ή το πρωτόκολλο δεν υποστηρίζεται" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Traffic Share (απευθείας λήψη)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Κατεβάζετε ήδη από αυτή τη διεύθυνση ΙΡ, αναμονή 60 δευτερολέπτων" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Μη έγκυρος κώδικας , η λήψη θα γίνει επανεκκίνηση" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Δεν υπάρχουν ελέυθερα slots" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Χρειάζεστε έναν λογαριασμό premium για αυτό το αρχείο" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Το όνομα αρχείου αναφέρθηκε ως μη έγκυρο" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Παρακαλώ εισάγετε το λογαριασμό σας %s ή απενεργοποιήστε αυτό το πρόσθετο" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Η αποκρυπτογράφηση απέτυχε" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Η διεύθυνση URL δεν παρείχε κανένα κλειδί αρχείου" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Κωδικός σφάλματος:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Σφάλμα ταυτόχρονης λήψης, αναμονή 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Δεν έχετε συνδεθεί." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Το κλειδί API δεν είναι έγκυρο" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Δεν απέμεινε αρκετή κίνηση δεδομένων" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Υπέρβαση ορίου κίνησης δεδομένων" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Απαιτείται πιστοποίηση (όνομα χρήστη:κωδικός)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Το αρχείο είναι προσωρινά μη διαθέσιμο" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: αναμονή μεταξύ των λήψεων %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: αναμονή για captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Το ληφθέν αρχείο ήταν άδειο" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Υπήρχε κώδικας HTML στο αρχείο που έγινε λήψη(%s)... σφάλμα ανακατεύθυνσης; Η λήψη θα γίνει επανεκκίνηση." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Αποτυχία σύνδεσης με τον λογαριασμό %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Λάθος κωδικός" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Λήψη πληροφοριών λογαριασμού για %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Σφάλμα: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Η ώρα %s έχει λάθος μορφή, χρησιμοποιήστε 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Στον λογαριασμό %s δεν έχει απομείνει αρκετή κίνηση δεδομένων, θα γίνει πάλι έλεγχος σε 30 λεπτά" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Ο λογαριασμός %s έχει λήξει, θα γίνει ξανά έλεγχος σε 1 ώρα" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Σύνδεση με %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Σφάλμα κατά την εκτέλεση των πρόσθετων: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Ενεργοποιήστε τις απευθείας λήψεις στον Bitshare λογαριασμό σας" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Φτάσατε στο μέγιστο όριο λήψεων" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Παρακαλώ προσθέστε πρώτα τον λογαριαμό σας στο premium.to και επανεκκινήστε το pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Εγκατεστημένα αρχεία εντολών για το %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Το αρχείο εντολών δεν είναι εκτελέσιμο:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Σφάλμα στο %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "απομένουν %s πόντοι" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Αποτυχία αποστολής απάντησης." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Ο λογαρισμός σας CaptchaTrader δεν έχει αρκετούς πόντους" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Νέο CaptchaID από τη μεταφόρτωση: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Ο λογαριασμός σας Captcha 9kw.eu δεν έχει αρκετούς πόντους" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Παρακαλώ προσθέστε πρώτα τον λογαριαμό σας στο rehost.to και επανεκκινήστε το pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Προστέθηκε το %s από HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Η θύρα 9666 χρησιμοποιείται ήδη" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Ολοκλήρωση πακέτου: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Η λήψη ολοκληρώθηκε: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Ο λογαριασμός στο ExpertDecoders δεν έχει αρκετούς πόντους" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Τα πρόσθετα έχουν αναβαθμιστεί, παρακαλώ επανεκκινήστε το pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Τα πρόσθετα αναβαθμίστηκαν και ξαναφορτώθηκαν" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Δεν υπάρχουν αναβαθμίσεις για τα πρόσθετα" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Δεν βρέθηκε αναβάθμιση για το pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Είναι διαθέσιμη μια καινούρια έκδοση (%s) του pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Κατεβάστε απο εδώ: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Αδυναμία σύνδεσης στον κεντρικό διακομιστή για έλεγχο ενημερώσεων" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Νέα έκδοση του%(type)s | %(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Σφάλμα κατά την αναβάθμιση του %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Ασυμφωνία έκδοσης" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Δεν έχει εγκατασταθεί το %s" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Αποτυχία ενεργοποίησης του %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Ενεργοποιήθηκε" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Δεν έχουν ενεργοποιηθεί πρόσθετα για αποσυμπίεση" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Το πακέτο %s έχει δρομολογηθεί για αποσυμπίεση αργότερα" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Έλεγχος του πακέτου %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Αποσυμπίεση στο %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Δεν βρέθηκαν αρχεία για αποσυμπίεση" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "αποσυμπιέζεται" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Προστατευμένο με κωδικό" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Λάθος κωδικός" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Διαγραφή %s αρχείων" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Η αποσυμπίεση τελείωσε" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Σφάλμα στο συμπιεσμένο αρχείο" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "Ασυμφωνία CRC" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Άγνωστο Σφάλμα" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Ο ορισμός του Χρήστη και της Ομάδας απέτυχαν" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Η λίστα Crypter δεν βρέθηκε" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Η λίστα Crypter είναι κενή" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Η λήψη ολοκληρώθηκε: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Αίτηση για νέο Captcha: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Απάντηση με 'c %s κείμενο για το captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Παρακαλώ προσθέστε πρώτα έναν έγκυρο λογαριαμό στο premiumize.me και επανεκκινήστε το pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "απομένουν %d πόντοι" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Ενεργοποιήθηκε το %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Δεν φορτώθηκε κάποιος hoster" + diff --git a/locale/el/LC_MESSAGES/setup.po b/locale/el/LC_MESSAGES/setup.po new file mode 100644 index 000000000..71513138e --- /dev/null +++ b/locale/el/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Greek\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Θα θέλατε να ρυθμίσετε το pyLoad μέσω του περιβάλλοντος Web;" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Χρειάζεστε ένα πρόγραμμα περιήγησης και μια σύνδεση με αυτό το PC για αυτό το σκοπό." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "Η διεύθυνση URL θα είναι: http://ΌνομαΥπολογιστή:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Έναρξη του αρχικού περιβάλλοντος web για διαμόρφωση;" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Καλώς ήλθατε στο Βοηθό Ρύθμισης του pyLoad" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Θα γίνει έλεγχος και βασική ρύθμιση του συστήματός σας, ώστε να εκτελεστεί το pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Η τιμή στις αγκύλες [] είναι πάντα η προεπιλεγμένη τιμή," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "σε περίπτωση που δεν επιθυμείτε αλλαγή ή αμφιβάλλετε για την επιλογή σας, απλά πατήστε enter. " + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Μην ξεχνάτε: Μπορείτε πάντα να εκτελέσετε ξανά τον Βοηθό, εισάγοντας τη παράμετρο --setup ή -s, κατά την εκτέλεση του pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Αν αντιμετωπίσετε προβλήματα με το Βοηθό, πατήστε CTRL-C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "για να εγκαταλείψετε και να αποτρέψετε την αυτόματη εκτέλεσή του." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Όταν είστε έτοιμος για τον έλεγχο του συστήματος, πατήστε enter." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Χαρακτηριστικά που λείπουν: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "δεν είναι διαθέσιμο το py-crypto" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Αυτό είναι απαραίτητο αν θέλετε να αποκρυπτογραφήσετε αρχεία container." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "δεν είναι διαθέσιμο το SSL" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Αυτό απαιτείται για την πραγματοποίηση ασφαλής σύνδεσης με τον πυρήνα ή το περιβάλλον Web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Αν θέλετε να έχετε πρόσβαση μόνο τοπικά στο pyLoad, το ssl δεν είναι χρήσιμο." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "δεν υπάρχει διαθέσιμη Αναγνώριση Captcha" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Χρειάζεται μόνο από μερικούς διακομιστές διαμοιρασμού αρχείων και σαν δωρεάν χρήστης." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "δεν βρέθηκε μηχανή JavaScript" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Θα το χρειαστείτε για μερικούς συνδέσμους Click'N'Load. Εγκαταστήστε το Spidermonkey, ή το ossp-js, ή το pyv8 ή ή το rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Αν θέλετε μπορείτε να ακυρώσετε την εγκατάσταση τώρα και να διορθώσετε κάποιες εξαρτήσεις." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Συνέχιση με την εγκατάσταση;" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Θέλετε να αλλάξετε τη διαδρομή του αρχείου αποθήκευσης των ρυθμίσεων; Η τρέχουσα είναι %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Αν χρησιμοποιείτε το pyLoad σε εξυπηρετητή ή το διαμέρισμα home βρίσκεται σε εσωτερική μνήμη flash, θα ήταν καλή ιδέα να το αλλάξετε." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Αλλαγή διαδρομής αποθήκευσης αρχείου ρυθμίσεων;" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Θέλετε να ορίσετε στοιχεία σύνδεσης και να κάνετε τις βασικές ρυθμίσεις;" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Αυτό συνιστάται για την πρώτη εκτέλεση." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Δημιουργία βασικής ρύθμισης;" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Θέλετε να ρυθμίσετε το SSL;" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Ρύθμιση SSL;" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Θέλετε να ρυθμίσετε το περιβάλλον Web;" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Ρύθμιση του Web Interface;" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Η εγκατάσταση ολοκληρώθηκε με επιτυχία." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Πατήστε Enter για να εξέλθετε και να επανεκκινήσετε το pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Το περιβάλλον Web εκετελείται για εγκατάσταση." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Βασική Εγκατάσταση ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Τα ακόλουθα στοιχεία σύνδεσης είναι έγκυρα για τη Γραμμή Εντολών, το GUI και το περιβάλλον Web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Όνομα Χρήστη" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Οι εξωτερικοί πελάτες (για το GUI, τη Γραμμή εντολών ή άλλο) χρειάζονται απομακρυσμένη πρόσβαση για να λειτουργήσουν μέσω δικτύου." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Όμως, αν θέλετε να να χρησιμοποιήσετε μόνο το περιβάλλον Web, μπορείτε να το απενεργοποιήσετε για να γλυτώσετε χρήση της RAM." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Ενεργοποίηση απομακρυσμένης πρόσβασης" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Γλώσσα" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Φάκελος για λήψεις" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Μέγιστος αριθμός ταυτόχρονων λήψεων" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Χρήση Επανασύνδεσης;" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Τοποθεσία αρχείου εντολών επανασύνδεσης" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Εγκατάσταση Περιβάλλοντος Web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Ενεργοποίηση περιβάλλοντος Web;" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Διεύθυνση που θα \"ακούει\". Αν χρησιμοποιήσετε 127.0.0.1 ή localhost, το περιβάλλον Web θα είναι προσβάσιμο μόνο τοπικά." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Διεύθυνση" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Θύρα" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "Το pyLoad προσφέρει αρκετoύς διακομιστές υποστήριξης. Ακολουθεί σύντομη επεξήγηση." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Προεπιλεγμένο διακομιστής, αυτός ο διακομιστής προσφέρει SSL και είναι μια καλή εναλλακτική λύση από τον ενσωματωμένο." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Μπορεί να χρησιμοποιήθεί από τον apache και το lighttpd, απαιτείται παραμετροποίηση, η οποία δεν είναι πολύ εύκολη." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Πολύ γρήγορη εναλλακτική λύση, γραμμένη σε C, απαιτεί το πακέτο libev και γνώσεις linux." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Κατεβάστε το από εδώ: https://github.com/jonashaag/bjoern, κάντε το compile," + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "και αντιγράψτε το bjoern.so στο pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Προσοχή: Σε σπάνιες περιπτώσεις ο προεπιλεγμένος διακομιστής δεν δουλεύει. Αν παρατηρήσετε προβλήματα με το Web Interface," + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "επιστρέψτε εδώ και αλλάξτε τον με τον threaded." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Διακομιστής" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Εγκατάσταση SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Εκτελέστε αυτές τις εντολές από τον φάκελο παραμετροποίησης του pyLoad για να δημιουργήσετε πιστοποιητικά SSL:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Αν τελειώσατε και όλα πήγαν καλώς, τώρα μπορείτε να ενεργοποιήσετε το SSL." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Ενεργοποίηση SSL;" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Επιλέξτε ενέργεια" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Δημιουργία/Επεξεργασία χρήστη" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Λίστα χρηστών" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Διαγραφή χρήστη" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Τερματισμός" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Χρήστες" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Ορισμός καινούριας διαδρομής ρυθμίσεων, οι τρέχουσες ρυθμίσεις δεν θα μεταφερθούν!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Διαδρομή για το αρχείο ρυθμίσεων" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Η διαδρομή για το αρχείο ρυθμίσεων άλλαξε, η εγκατάσταση τώρα θα κλείσει, παρακαλώ κάντε επανεκκίνηση για να συνεχίστε." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Πατήστε Enter για έξοδο." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Ο ορισμός της διαδρομής ρυθμίσεων απέτυχε: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "y" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Κωδικός χρήστη:" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Ο κωδικός είναι πολύ μικρός. Χρησιμοποιήστε τουλάχιστον 4 χαρακτήρες." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Κωδικός (ξανά):" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Οι κωδικοί δεν ταιριάζουν." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "ναι" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "σωστό" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "t" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "όχι" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "λάθος" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Λανθασμένη εισαγωγή" + diff --git a/locale/el/LC_MESSAGES/webUI.po b/locale/el/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..13b7bf506 --- /dev/null +++ b/locale/el/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Greek\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "μη διαθέσιμο" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "απεριόριστο" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Διαχειριστής" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Εγκατάσταση" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Προσθήκη Λογαριασμού" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Λογαριασμοί" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Τοπικό" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Αναζήτηση" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Τύπος" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Όλα" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Ολοκληρωμένα" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Ημιτελή" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Απέτυχαν" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 πακέτο" +msgstr[1] "%d πακέτα" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "%d αρχεία" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Προσθήκη ενός λογαριασμού" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Παρακαλώ εισάγετε τα στοιχεία του λογαριασμού σας" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Επιλέξτε ένα πρόσθετο" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Παρακαλώ επιλέξτε ένα πρόσθετο το οποίο θέλετε να ρυθμίσετε" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Προσθήκη" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Κλείσιμο" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Παρακαλώ επιβεβαιώστε" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Θέλετε να διαγράψετε τα επιλεγμένα στοιχεία;" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Διαγραφή" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Ακύρωση" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Υποβολή" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "Εκτελείται..." + diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo Binary files differdeleted file mode 100755 index 1767783dd..000000000 --- a/locale/en/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/en/LC_MESSAGES/pyLoad.mo b/locale/en/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100755 index 1767783dd..000000000 --- a/locale/en/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/en/LC_MESSAGES/pyLoadCli.mo b/locale/en/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100755 index 1767783dd..000000000 --- a/locale/en/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/en/LC_MESSAGES/pyLoadGui.mo b/locale/en/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100755 index 1767783dd..000000000 --- a/locale/en/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/en/LC_MESSAGES/setup.mo b/locale/en/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100755 index 1767783dd..000000000 --- a/locale/en/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/cli.po b/locale/es/LC_MESSAGES/cli.po new file mode 100644 index 000000000..58ed5f481 --- /dev/null +++ b/locale/es/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Agregar Paquete:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Introduce un nombre para el nuevo paquete" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paquete: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analizar los enlaces que quieres añadir." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Teclea %s cuando acabes." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Enlaces añadidos: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " volver al menú principal" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Administrar Paquetes:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Administrar Enlaces:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "¿Qué quieres mover?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "¿Qué quieres borrar?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "¿Qué quieres reiniciar?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Elige lo que quieres hacer o introduce el número de paquete." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "borrar" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "mover" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "reiniciar" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - anterior" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - siguiente" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Interfaz de Línea de Órdenes" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Descargas:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Velocidad: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Tamaño: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Acabado el: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "esperando: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Estado:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "Pausado" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "ejecutando" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Velocidad total" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Ficheros en cola" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Total" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menú:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Añadir enlaces" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Administrar Cola" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Administrar Recolector" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Detener/Reanudar Servidor" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Parar servidor" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Salir" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Por favor usa esta sintaxis: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Comprobando %d enlace(s):" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "El archivo no existe." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad fue finalizado" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Muestra el estado del servidor" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Muestra las descargas en cola" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Muestra las descargas en recolector" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Agregar paquetes a la cola" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Agrega un paquete al recolector" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Borrar Archivos de la Cola/Recolector" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Borrar Paquetes de la Cola/Recolector" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Mover Paquetes de la Cola al Recolector o viceversa" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Reiniciar archivos" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Reiniciar paquetes" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Comprobar el estado en-linea, trabaja con el contenedor local" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Comprueba el estado en-linea de un contenedor" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Pausar el servidor" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "continuar descargas" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Alternar detener/reanudar" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "parar servidor" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lista de órdenes:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "No se puede escribir el archivo de configuración del usuario" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Necesitas py-openssl para conectar a este núcleo pyLoad." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Dirección: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Puerto: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Usuario: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Contraseña: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Los datos de inicio de sesión son incorrectos." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "No se puede establecer la conexión a %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Modo interactivo ignorado ya que pasaste algunas órdenes." + diff --git a/locale/es/LC_MESSAGES/core.po b/locale/es/LC_MESSAGES/core.po new file mode 100644 index 000000000..98b315436 --- /dev/null +++ b/locale/es/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Error al ejecutar %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Falló la activación de %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Complementos activados: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Complementos desactivados: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Activando Plugins..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Desactivando Plugins..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Certificados SSL no encontrados." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "El interfaz web no está disponible" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Eecutando webUI en modo de desarrollo" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "No se pudo iniciar servidor web: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "No se pudo importar el servidor web: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Este servidor no ofrece SSL, considera el usar el servidor con hilos en su lugar" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Iniciando servidor web %(name)s: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Remoto" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Descripción" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Descripción larga" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Activado" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Puerto" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Dirección" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Registro" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Tamaño en kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Carpeta" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Archivo de registro" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Cuenta" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Rotar el registro" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Permisos" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "nombre del grupo" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Cambiar Grupo y Usuario de Descargas" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Cambiar el modo de archivo de descargas" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Usuario" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Modo de Archivo para Descargas" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Cambiar grupo del proceso en ejecución" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Modo de Permiso Carpeta" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Cambiar usuario del proceso en ejecución" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "General" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Idioma" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Carpeta de Descargas" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Usar suma de comprobación" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Crear una carpeta para cada paquete" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Modo de depuración" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Mínimo Espacio Libre (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Prioridad de la CPU" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Certificado SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Clave SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Interfaz Web" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Plantilla" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Prefijo de ruta" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Servidor" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Servidor específico favorito" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Usar HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Modo de desarrollo" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Utilizar Proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Contraseña" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protocolo" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Reconectar" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Final" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Usar Reconectar" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Método" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Comenzar" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Descargar" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Máximas Descargas Paralelas" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Velocidad Límite de Descarga" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Interfaz de descarga a unirse (IP o nombre)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Omitir archivos ya existentes" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Velocidad de Descarga Máx. en kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Permitir IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Conexiones máximas para una descarga" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Reiniciar descargas fallidas al arrancar" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Tiempo de descarga" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Descarga por trozos fallida, recurro a una única conexión | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Añadido paquete %(name)s como carpeta %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Añadidos %d enlaces al paquete" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Cuenta desconocida del plugin %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Consulta" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Solicitud de captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Por favor resuelve el captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Error de backend remoto: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Iniciando %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Fallo al cargar servidor %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "Ninguno" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "sin conexión" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "en línea" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "en cola" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "Pausado" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "acabado" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "omitido" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "fallido" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "comenzando" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "esperando" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "descargando" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "temp. sin conexión" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "abortado" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "descifrando" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "procesando" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "personalizado" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "desconocido" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paquete acabado: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Usuario '%s' intenta iniciar sesión" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Señal de salida recibida" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad ya está ejecutándose con pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Cambio de grupo fallido: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Cambio de usuario fallido: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Comenzando" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Usando directorio de inicio: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Se eliminaron todos los enlaces" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Tiempo de descarga: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Espacio libre: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Activando Cuentas..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Reiniciando descargas fallidas..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad está funcionando" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "reiniciando pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad se cierra" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "apagando..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "ha ocurrido un error mientras se apagaba" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyload terminado desde el Terminal" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "El archivo de la base de datos fue eliminado por ser de una versión incompatible." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Descifrado fallido" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Desccifrados %(count)d enlaces en el paquete %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "No se descifró ningún enlace" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Obtención de información para %(name)s fallida | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Reconexión Fallida: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "¡Script de reconexión no encontrado!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Iniciando reconexión" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "¡Fallo al ejecutar script de reconexión!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Reconectado, nueva dirección IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "No hay suficiente espacio libre en el dispositivo" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Inicia descarga: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Descarga finalizada: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Plugin %s no encuentra una función." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Descarga abortada: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Descarga reiniciada: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Descarga está fuera de servicio: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "La descarga está temporalmente fuera de servicio: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Descarga fallida: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "No pude conectar al servidor o conexión reiniciada, esperando 1 minuto y reintentando." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Descarga omitida: %(name)s debido a %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Error interno del servidor" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Se ha producido un error" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Error importando %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "No se detectó ningún motor de js, por favor instale Spidermonkey, ossp-js, pyv8, nodejs o rhino" + diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 899597735..000000000 --- a/locale/es/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/plugins.po b/locale/es/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..ed9cb669e --- /dev/null +++ b/locale/es/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Descarga por trozos fallida, recurro a una única conexión | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil y tesseract no están instalados y no hay cliente conectado para descifrar captcha" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "No se obtuvo resultado para el captcha en el tiempo asignado." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Configuración de usuario y grupo fallida: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "No existe el archivo o protocolo no soportado" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Tráfico compartido (descarga directa)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Ya estás descargando desde esta dirección IP, esperando 60 segundos" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Código de autenticación inválido, la descarga se reiniciará" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: No quedan espacios disponibles" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Necesitas una cuenta premium para este archivo" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Nombre de archivo inválido" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Por favor, introduce tu cuenta de %s o desactiva este plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Error de descifrado" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "No se proporcionó archivo de clave en la URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Código de error:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Error con descargas en paralelo, esperando 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "No has iniciado sesión." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Clave de API inválida" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: No queda suficiente tráfico" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Tráfico excedido" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Autorización requerida (usuario:contraseña)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Archivo no disponible temporalmente" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: esperando entre descargas %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: esperando captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "El archivo descargado estaba vacío" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Había código HTML en el archivo (%s) descargado ... ¿error de redirección? La descarga se reiniciará." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "URL largo: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "No pude iniciar sesión con cuenta %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Contraseña Incorrecta" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Obtener información de la cuenta para %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Error: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "La hora %s tiene un formato incorrecto, use: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "La cuenta %s no tiene tráfico suficiente, se comprobará otra vez en 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "La cuenta %s ha expirado, comprobando de nuevo en 1 hora" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Iniciar sesión con %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Error ejecutanto complementos: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Activa descarga directa en tu cuenta de Bitshare" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Limite de descargas alcanzado" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Por favor, agrega primero tu cuenta rehost.to y reinicia pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Scripts instalados para %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script no ejecutable:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Error en %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s créditos restantes" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "No se pudo enviar la respuesta." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Tu cuenta de CaptchaTrader no tiene créditos suficientes" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nuevo CaptchaID desde carga: %s: %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Tu Cuenta Captcha 9kw.eu no tiene suficientes créditos" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Por favor, agrega primero tu cuenta rehost.to y reinicia pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Agregado %s desde HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Puerto 9666 ya está en uso" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paquete acabado: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Descarga finalizada: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Tu cuenta de ExpertDecoders no suficientes créditos" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Los plugins han sido actualizados, por favor reinicia pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins actualizados y cargados de nuevo" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "No hay actualizaciones de plugins disponibles" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "No hay actualizaciones de pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Nueva versión %s de pyLoad disponible ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Obténla aquí: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Imposible conectar con el servidor para actualizaciones" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Nueva versión de %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Error mientras se actualizaba %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "No coincide la versión" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "%s no instalado" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "No pude activar %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Activado" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "No se activaron plugins de extracción" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Paquete %s encolado para su posterior extracción" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Comprobar paquete %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Extraer a %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "No se encontraron ficheros que extraer" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "extrayendo" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Protegido con contraseña" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Contraseña errónea" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Borrando ficheros %s" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Extracción finalizada" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Error de Archivo" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "El CRC no coincide" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Error Desconocido" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Falló el ajuste de usuario y grupo" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "No se encontró lista de cifradores" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "La lista de cifradores está vacía" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Descarga finalizada: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Nueva Petición de Captcha: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Conteste con 'c %s texto al captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Por favor, agrega una cuenta válida de premiumize.me primero y reinicia pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d créditos restantes" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "%s activado" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "No se cargó ningún proveedor" + diff --git a/locale/es/LC_MESSAGES/pyLoad.mo b/locale/es/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 90aa70f1d..000000000 --- a/locale/es/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/pyLoadCli.mo b/locale/es/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index f969ad0d1..000000000 --- a/locale/es/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/pyLoadGui.mo b/locale/es/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index a4944783f..000000000 --- a/locale/es/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/setup.mo b/locale/es/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 87c4dd811..000000000 --- a/locale/es/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/es/LC_MESSAGES/setup.po b/locale/es/LC_MESSAGES/setup.po new file mode 100644 index 000000000..187bf573b --- /dev/null +++ b/locale/es/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "¿Te gustaría configurar pyLoad vía interfaz web?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Para ello necesita un navegador y una conexión a este PC." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "La URL sería: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "¿Arrancar el interfaz web inicial para la configuración?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Bienvenido al Asistente de Configuración de pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Revisará tu sistema y hará una configuración básica para ejecutar pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "El valor entre corchetes [] siempre es el valor por defecto," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "en caso que no quieras cambiarlo o no estés seguro de que opción elegir, solo presiona entrar." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "No lo olvides: siempre puedes volver a ejecutar este asistente con el parámetro --setup ó -s, cuando inicies pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Si tienes algún problema con este asistente presiona Ctrl-C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "para abortar y no permitirle volver a iniciarse automáticamente con pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Cuando estés listo para la revisión del sistema, presiona entrar." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Características no disponibles: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto no disponible" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Lo necesitas si quieres descifrar archivos contenedores." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL no disponible" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Esto es necesario si quieres establecer una conexión segura con el núcleo o la interfaz web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "si sólo quieres acceder a pyLoad localmente SSL no es útil." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "reconocimiento de captcha no disponible" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Solo es necesario para algunos servidores y como usuario gratuito." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "motor de JavaScript no encontrado" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Será necesario para algunos enlaces Click'N'Load. Instala Spidermonkey, ossp-js, pyv8 o rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Puedes cancelar la instalación ahora y corregir algunas dependencias si quieres." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "¿Continuar con la instalación?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "¿Quieres cambiar la ruta de configuración? Actualmente es %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Si usas pyLoad en un servidor o la partición primaria reside en una memoria flash interna, puede ser buena idea cambiarla." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "¿Cambiar la ruta de configuración?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "¿Quieres configurar los datos de acceso y la configuración básica?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Esto se recomienda en la primera ejecución." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "¿Realizar la instalación básica?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "¿Quieres configurar SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "¿Configurar SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "¿Quieres configurar la interfaz web?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "¿Configurar la interfaz web?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Instalación finalizada con éxito." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Pulse entrar para salir y reiniciar pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Interfaz web ejecutándose para instalación." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Instalación básica ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Los siguientes datos de acceso son válidos para CLI, GUI e interfaz web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Usuario" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Los clientes externos (GUI, CLI u otros) necesitan acceso remoto para funcionar a través de la red." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "No obstante, si sólo quieres utilizar interfaz web puedes deshabilitarlo para ahorrar ram." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Activar acceso remoto" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Idioma" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Carpeta de Descargas" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Máximas descargas paralelas" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "¿Usar reconectar?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Ubicación del script de reconexión" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Instalación de la Interfaz Web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "¿Activar interfaz web?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Dirección de escucha, si usas 127.0.0.1 o localhost, la interfaz web solo podrá ser accesible localmente." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Dirección" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Puerto" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "lapyLoad ofrece varios backends de servidor después de una breve explicación." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Servidor predeterminado, este servidor ofrece SSL y es una buena alternativa al integrado." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Puede usarse por apache, lighttpd, requiere que lo configures, lo cual no es una tarea fácil." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Una alternativa muy rápida escrita en C, requiere libev y conocimientos de linux." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Obtenlo de aquí: https://github.com/jonashaag/bjoern, compílalo" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "y copia bjoern.so a pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Atención: En algunos casos raros el servidor integrado no funciona, si notas problemas con la interfaz web" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "vuelve aquí y cambia el servidor integrado por el servidor con hilos." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Servidor" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Configuración de SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Ejecuta estos comandos desde la carpeta de configuración pyLoad para crear los certificados ssl:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Si has terminado y todo acabo bien, podrás activar ssl ahora." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "¿Activar SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Elige una acción" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Crear/Editar usuario" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Lista de usuarios" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Eliminar usuario" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Salir" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Usuarios" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Instalando el nuevo directorio de configuración, ¡la configuración actual no será transferida!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Directorio de configuración" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Directorio de configuración cambiado, la instalación se cerrará, por favor reinicia para continuar." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Presiona Entrar para salir." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Ajuste del directorio de configuración fallido: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "s" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Contraseña: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "La contraseña es demasiado corta. Use al menos 4 símbolos." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Contraseña (de nuevo): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Las contraseñas no coinciden." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "sí" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "verdadero" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "v" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "no" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "falso" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Entrada no válida" + diff --git a/locale/es/LC_MESSAGES/webUI.po b/locale/es/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..57c13399a --- /dev/null +++ b/locale/es/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "no disponible" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "ilimitado" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Admin" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Configuración" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Agregar cuenta" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Cuentas" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Local" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Buscar" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Tipo" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Todos" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Acabados" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Inacabados" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Fallidos" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 paquete" +msgstr[1] "%d paquetes" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 archivio" +msgstr[1] "%d archivos" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Agregue una cuenta" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Por favor, introduzca los datos de su cuenta" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Elija un plugin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Por favor, elija el plugin que desea configurar" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Añadir" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Cerrar" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Por favor confirme" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "¿Quiere borrar los elementos seleccionados?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Borrar" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Cancelar" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Enviar" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "Ejecutando..." + diff --git a/locale/fa/LC_MESSAGES/cli.po b/locale/fa/LC_MESSAGES/cli.po new file mode 100644 index 000000000..6ce1c0b1f --- /dev/null +++ b/locale/fa/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/fa/LC_MESSAGES/core.po b/locale/fa/LC_MESSAGES/core.po new file mode 100644 index 000000000..b6a7c7402 --- /dev/null +++ b/locale/fa/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/fa/LC_MESSAGES/plugins.po b/locale/fa/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..41962eaa5 --- /dev/null +++ b/locale/fa/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/fa/LC_MESSAGES/setup.po b/locale/fa/LC_MESSAGES/setup.po new file mode 100644 index 000000000..518889db4 --- /dev/null +++ b/locale/fa/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/fa/LC_MESSAGES/webUI.po b/locale/fa/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..508ed30d5 --- /dev/null +++ b/locale/fa/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/fi/LC_MESSAGES/cli.po b/locale/fi/LC_MESSAGES/cli.po new file mode 100644 index 000000000..6fb7c5219 --- /dev/null +++ b/locale/fi/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Finnish\n" +"Language: fi_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Linkkien hallinta" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "Komentorivikäyttöliittymä" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s lataa:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Nopeus:" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Koko:" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Valmistunut:" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "Tunnus:" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "odotetaan:" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Valikko:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "Lisää linkkejä" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Jonon hallinta" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Kerääjän hallinta" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/fi/LC_MESSAGES/core.po b/locale/fi/LC_MESSAGES/core.po new file mode 100644 index 000000000..49a469a45 --- /dev/null +++ b/locale/fi/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Finnish\n" +"Language: fi_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/fi/LC_MESSAGES/plugins.po b/locale/fi/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..88fe120d7 --- /dev/null +++ b/locale/fi/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Finnish\n" +"Language: fi_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/fi/LC_MESSAGES/pyLoad.mo b/locale/fi/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 2c20cfbd4..000000000 --- a/locale/fi/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/fi/LC_MESSAGES/pyLoadCli.mo b/locale/fi/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 1ae24953f..000000000 --- a/locale/fi/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/fi/LC_MESSAGES/pyLoadGui.mo b/locale/fi/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 1ae24953f..000000000 --- a/locale/fi/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/fi/LC_MESSAGES/setup.po b/locale/fi/LC_MESSAGES/setup.po new file mode 100644 index 000000000..1371318e7 --- /dev/null +++ b/locale/fi/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Finnish\n" +"Language: fi_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/fi/LC_MESSAGES/webUI.po b/locale/fi/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..0351bba34 --- /dev/null +++ b/locale/fi/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Finnish\n" +"Language: fi_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/fr/LC_MESSAGES/cli.po b/locale/fr/LC_MESSAGES/cli.po new file mode 100644 index 000000000..1af859282 --- /dev/null +++ b/locale/fr/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Ajouter un paquet:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Entrez un nom pour le nouveau paquet" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paquet : %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analyse des liens que vous voulez ajouter." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Tapez %s une fois fini." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Liens ajoutés : " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " retour au menu principal" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Gérer les paquets:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Gérer les liens:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Que voulez-vous déplacer ?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Que voulez-vous supprimer ?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Que voulez-vous redémarrer ?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Choisissez ce que vous voulez faire, ou entrez un numéro de paquet." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "supprimer" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "déplacer" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "redémarrer" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - précédent" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - suivant" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Interface en ligne de commande" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s téléchargement(s) :" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Vitesse : " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Taille : " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Fini dans : " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID : " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "en attente : " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Statut:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "en pause" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "en cours" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Vitesse totale" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Fichiers en attente" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Total" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menu :" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Ajouter des liens" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "Gérer la file d'attente" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Gérer le collecteur" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Mettre en pause/Restaurer le serveur" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Stopper le serveur" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Quitter" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Utilisez cette syntaxe : add <Nom paquet> <lien> <lien2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Vérification de %d liens :" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Le fichier n'existe pas." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad a quitté" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Affiche le statut du serveur" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "affiche les téléchargements en file d'attente" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "affiche les téléchargements dans le collecteur" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Ajouter le paquet à la file d'attente" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Ajouter le paquet au collecteur" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Supprimer les fichiers de la file d'attente/du collecteur" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Supprimer les paquets de la file d'attente/du collecteur" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Déplacer les paquets de la file d'attente au collecteur ou vice versa" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Redémarrer les fichiers" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Redémarrer les paquets" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Vérifie le statut en ligne (fonctionne avec un conteneur local)" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Vérifie le statut en ligne d'un fichier conteneur" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Mettre le serveur en pause" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "continuer les téléchargements" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Basculer pause/reprendre" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "stopper le serveur" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Liste des commandes :" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Impossible d'écrire le fichier de configuration" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Vous avez besoin de py-openssl pour vous connecter au moteur de Pyload." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adresse: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port : " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Nom d'utilisateur: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Mot de passe : " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Les identifiants sont incorrects." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Impossible d'établir la connexion à %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Mode interactif ignoré jusqu'à ce que vous entriez des commandes." + diff --git a/locale/fr/LC_MESSAGES/core.po b/locale/fr/LC_MESSAGES/core.po new file mode 100644 index 000000000..f322528f5 --- /dev/null +++ b/locale/fr/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Erreur lors de l'exécution de %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Impossible d'activer %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Addons activés : %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Extensions désactivées: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Activation des extensions..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Désactivations des plugins..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Certificats SSL non trouvés." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "L'interface web n'est pas disponible" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Lancement de l'interface web en mode développement" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Echec du lancement du serveur web : " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Echec de l'importation du serveur web : " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Ce serveur ne permet pas la connexion SSL, vous devriez envisager d'utiliser le mode “threaded” à la place" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Démarrage du serveur web %(name)s : %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "A distance" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Description" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Description longue" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Activé" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adresse" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Log" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Taille en Kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Dossier" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Fichier de log" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Compte" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Log tournant" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Permissions" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Nom du groupe" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Changement du groupe et d'utilisateurs des téléchargement" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Changement du mode de fichiers des téléchargements" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Nom utilisateur" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Mode des fichiers pour les téléchargements" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Changement du groupe des processus en cours" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Mode de permission de dossier" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Changement d'utilisateur des processus en cours" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Général" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Langue" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Dossier de téléchargement" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Utiliser un checksum" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Créer un dossier pour chaque paquet" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Mode debug" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Espace libre minimum (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Priorité processeur" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Certificat SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Clé SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Interface web" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Modèle" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Préfixe du chemin" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Serveur" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Serveur spécifique préféré" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Utiliser HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Mode développement" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Utiliser un proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Mot de passe" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protocole" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Reconnecter" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Fin" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Utiliser la reconnexion" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Méthode" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Début" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Téléchargement" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Téléchargements maximums en parallèle" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Vitesse limite de téléchargement" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Téléchargement de l'interface à lier (ip ou nom)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Ignorer les fichiers existants" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Vitesse maximale de téléchargement en kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Autoriser l'IPV6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Nombre maximum de connexions pour un téléchargement" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Redémarrage des téléchargements échoués au démarrage" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Temps de téléchargement" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Téléchargement par blocs échoué, repli vers une seule connexion | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Ajout du paquet %(name)s en tant que dossier %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Ajout de %d liens au paquet" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Compte de plugin inconnu %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Requête" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Demande de captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Veuillez résoudre le captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Erreur du backend distant: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Démarre %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Échec du chargement du backend %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "aucun" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "hors ligne" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "en ligne" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "en file d'attente" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "en pause" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "fini" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "ignoré" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "échoué" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "démarrage" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "en attente" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "téléchargement en cours" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "temp. hors ligne" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "annulé" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "décryptage" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "traitement en cours" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "personnalisé" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "inconnu" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paquet terminé : %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "L'utilisateur '%s' essaye de se connecter" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Signal d'arrêt reçu" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad est déjà lancé avec le pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Echec lors du changement de groupe: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Echec lors du changement d'utilisateur: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Démarrage" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Utilisation du dossier principal : %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Tous les liens ont été supprimés" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Temps de téléchargement : %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Espace libre : %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Activation des comptes ..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Redémarrage des téléchargements échoués..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad est opérationnel" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "redémarrage de pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad se termine" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "arrêt..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "erreur lors de l'arrêt" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "fin de pyLoad depuis le terminal" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "La base de données a été supprimée pour cause d'incompatibilité." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Le décryptage a échoué" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Décryptage de %(count)d liens dans les paquets %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Pas de liens décryptés" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Échec de la collecte des infos pour %(name)s | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "La reconnexion a échoué : %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Script de reconnexion non trouvé !" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Lancement de la reconnexion" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Echec d'exécution du script de reconnexion !" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Reconnecté, nouvelle IP : %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Il n'y a pas assez de place sur le périphérique" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Le téléchargement commence : %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Téléchargement terminé : %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Il manque une fonction au plugin %s." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Abandon du téléchargement : %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Téléchargement redémarré : %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Le téléchargement est hors-ligne : %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Le téléchargement est temporairement hors ligne: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Téléchargement échoué : %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Impossible de se connecter à l'hôte ou connexion perdue, prochain essai dans une minute." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Téléchargement ignoré : %(name)s à cause de %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Erreur du serveur interne" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Une erreur est survenue" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Erreur d'importation %(name)s : %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Pas de moteur js détécté, veuillez installer soit Spidermonkey, ossp-js, pyv/, nodesjs ou rhino" + diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index b7b2a3fb5..000000000 --- a/locale/fr/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/fr/LC_MESSAGES/plugins.po b/locale/fr/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..51f00a3d1 --- /dev/null +++ b/locale/fr/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Téléchargement par blocs échoué, repli vers une seule connexion | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil et tesseract ne sont pas installés et il n'y a pas de client connecté pour le déchiffrement des captcha" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Aucun résultat de captcha obtenu dans un temps approprié." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "La définition de l'utilisateur et du groupe a échoué :: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Le fichier n'existe pas ou le protocole n'est pas supporté" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare : partage du trafic (téléchargement direct)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Un téléchargement est déjà en cours depuis cette adresse, 60 secondes d'attente" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Code d'authentification invalide, le téléchargement va redémarrer" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom : aucun slot disponible" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Vous avez besoin d'un compte premium pour ce fichier" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Le nom du fichier est invalide" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Veuillez entrer votre compte %s ou désactivez ce plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Le déchiffrement à échoué" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Aucune clé de fichier fournie dans l'URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Code d'erreur :" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Erreur de téléchargement parallèle, attente pendant 60 secondes." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Vous n'êtes pas identifiés." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "La clé de l'API est invalide" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s : il ne reste pas assez de trafic" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Le trafic a été dépassé" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Autorisation requise (identifiant:mot de passe)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Fichier temporairement indisponible" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload : attente de %d s entre chaque téléchargements." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload : attendre le captcha pendant %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Le fichier téléchargé est vide" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Il y avait du code HTML dans le fichier téléchargé (%s)... erreur de redirection? Le téléchargement sera recommencé." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "longue_URL : %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Impossible de s'identifier avec le compte %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Mauvais mot de passe" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Obtention des informations du compte %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Erreur : %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Le format de date %s est incorrect, utilisez : 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Le compte %s n'a pas assez de trafic, re-vérification dans 30 minutes" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Le compte %s a expiré, re-vérification dans 1H" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Identifié avec %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Erreur lors de l'exécution des addons : %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Activez le téléchargement direct dans votre compte Bitshare" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Limite de téléchargement atteinte" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Veuillez ajouter votre compte premium.to en premier et redémarrez pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Scripts installés pour %s : " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script non exécutable :" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Erreur dans %(script)s : %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s crédits restants" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Impossible d'envoyer une réponse." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Votre compte CaptchaTrader n'a pas assez de crédits" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nouveau CaptchaID de l'upload: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Votre compte de captcha 9kw.eu n'a pas assez de crédits" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Veuillez d'abord ajouter votre compte rehost.to et redémarrez pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Ajout de %s à partir du HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load : port 9666 déjà en cours d'utilisation" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paquet terminé : %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Téléchargement fini : %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Votre compte ExpertDecoders n'a pas assez de crédits" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Les plugins ont été mis à jour, veuillez redémarrer pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins mis à jour et rechargés" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Aucune mise à jour pour les plugins" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Aucune mise à jour pour pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Nouvelle version %s disponible de pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Téléchargez-le ici : http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Incapable de se connecter au serveur pour les mises à jour" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Nouvelle version de %(type)s | %(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Erreur lors de la mise à jour de %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Les versions discordent" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "%s non installé" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Impossible d'activer %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Activé" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Pas de plugin d'extraction activé" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Le paquet %s est mis dans la file d'attente pour une extraction ultérieure" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Vérification du paquet %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Extraction vers %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Aucun fichier trouvé pour l'extraction" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "extraction en cours" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Mot de passe protégé" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Mot de passe incorrect" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Suppression de %s fichiers" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Extraction terminée" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Erreur d'archive" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC différents" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Erreur inconnue" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "La définition de l'utilisateur et du groupe a échoué" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Liste des crypteurs non trouvée" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "La liste des crypteurs est vide" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Téléchargement terminé : %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Nouvelle demande de captcha : %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Répondre avec ' le texte c %s sur le captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Veuillez d'abord ajouter un compte valide premiumize.me et redémarrez pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d crédits restants" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "%s activé" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Aucun hébergeur chargé" + diff --git a/locale/fr/LC_MESSAGES/pyLoad.mo b/locale/fr/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 95fd73cae..000000000 --- a/locale/fr/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/fr/LC_MESSAGES/pyLoadCli.mo b/locale/fr/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 460641599..000000000 --- a/locale/fr/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/fr/LC_MESSAGES/pyLoadGui.mo b/locale/fr/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 001412ddf..000000000 --- a/locale/fr/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/fr/LC_MESSAGES/setup.mo b/locale/fr/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 89cb21969..000000000 --- a/locale/fr/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/fr/LC_MESSAGES/setup.po b/locale/fr/LC_MESSAGES/setup.po new file mode 100644 index 000000000..dfe892eba --- /dev/null +++ b/locale/fr/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Souhaitez-vous configurer pyLoad via l'interface Web?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Vous avez besoin d'un navigateur et une connexion à ce PC pour ça." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "L'URL sera : http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Commencer l'interface Web de configuration initiale?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Bienvenue dans l'Assistant de Configuration de pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Votre système va être vérifié et subir un réglage de base afin de lancer pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "La valeur entre crochets [] est toujours la valeur par défaut," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "dans le cas où vous ne voulez pas la changer ou vous ne savez pas quoi choisir, appuyez sur entrée." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "N'oubliez pas : vous pouvez toujours relancer cet assistant avec l'option --setup ou -s, lorsque vous démarrez pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Si vous avez un problème avec cet assistant faites CTRL+C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "pour annuler et ne plus le laisser démarrer automatiquement avec pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Lorsque vous êtes prêt pour la vérification du système, appuyez sur entrée." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Fonctionnalités manquantes: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto non disponible" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Vous avez besoin de ceci si vous voulez décrypter des fichiers conteneurs." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL non disponible" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Ceci est nécessaire si vous souhaitez établir une connexion sécurisée au programme où à l'interface Web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Si vous souhaitez uniquement accéder localement à pyLoad ssl est inutile." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "aucune reconnaissance de captcha disponible" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Nécessaire uniquement pour certains hébergeurs et en tant qu'utilisateur libre." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "aucun moteur de JavaScript trouvé" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Vous en aurez besoin pour certains liens Click'N'Load. Installez Spidermonkey, ossp-js, pyv8 ou rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Vous pouvez annuler l'installation maintenant et réparer certaines dépendances si vous voulez." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Poursuivre l'installation ?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Vous voulez changer le chemin de la configuration ? Le chemin actuel est %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Si vous utilisez pyLoad sur un serveur ou votre partition home sur un périphérique interne il serait bien de le changer." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Changer le chemin de la configuration?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Vous voulez configurer les données de connexion et les paramètres de base ?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Ceci est recommandé pour la première exécution." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Faire le réglage de base ?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Vous voulez configurer ssl?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Configurer ssl?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Vous voulez configurer l'interface Web?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Configurer l'interface Web?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Installation terminée avec succès." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Appuyez sur entrée pour quitter et redémarrer pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Démarrage de l'interface web pour la configuration." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Configuration de base ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Les données de connexion suivantes sont valables pour le CLI, GUI et l'interface Web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Nom utilisateur" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Les clients externes (GUI, CLI ou autre) ont besoin d'un accès à distance pour travailler sur le réseau." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Toutefois, si vous souhaitez uniquement utiliser l'interface Web vous pouvez le désactiver pour économiser de la mémoire vive." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Activer l'accès distant" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Langue" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Dossier de téléchargement" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Téléchargements parallèles max" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Utiliser la reconnexion ?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Emplacement du script de reconnexion" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Configuration de l'interface Web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Activer l'interface Web?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Adresse d'écoute, si vous utilisez 127.0.0.1 ou localhost, l'interface Web sera accessible uniquement localement." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adresse" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad offre plusieurs serveur d'arrière plan, suivez maintenant une brève explication." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Serveur par défaut, ce serveur offre le SSL et est une bonne alternative au serveur integré." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Peut être utilisé par apache, lighttpd, mais vous oblige à les configurer, ce qui n'est pas très facile." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Alternative très rapide écrit en C, exige des connaissances en libev et linux." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Télécharger le ici : https://github.com/jonashaag/bjoern, compilez-le" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "et copiez bjoern.so dans pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Attention : dans certain cas le serveur intégré ne marche pas, si vous avez des problèmes avec l'interface web" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "revenez ici et changez le serveur de “intégré” à “threaded”." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Serveur" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Configuration de SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Exécutez ces commande depuis le répertoire de configuration de pyLoad pour générer les certificats SSL :" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Si vous avez terminé et que tout s'est bien passé, vous pouvez activer le ssl maintenant." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Activer le SSL ?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Sélectionnez une action" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Créer/modifier l'utilisateur" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Lister les utilisateurs" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Supprimer un utilisateur" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Quitter" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Utilisateurs" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Définition du nouveau chemin de configuration, la configuration actuelle ne sera pas transférée!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Chemin de configuration" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Le chemin de configuration a changé, l'installateur va maintenant se fermer, veuillez redémarrer pour continuer." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Appuyez sur entrée pour quitter." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "La définition du dossier de configuration a échoué : %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "o" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Mot de passe : " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Le mot de passe est trop court. Utilisez au moins 4 caractères." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Mot de passe (encore) : " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Les mots de passe ne correspondent pas." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "oui" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "vrai" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "v" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "non" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "faux" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Entrée non valide" + diff --git a/locale/fr/LC_MESSAGES/webUI.po b/locale/fr/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..b54dd775d --- /dev/null +++ b/locale/fr/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "non disponible" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "Illimité" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Admin" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Configurer" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Ajouter un compte" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Comptes" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Local" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Rechercher" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Type" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Tous" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Terminé" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Inachevé" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Échoué" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 paquet" +msgstr[1] "%d paquets" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 fichier" +msgstr[1] "%d fichiers" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Ajouter un compte" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Veuillez renseigner les données de votre compte" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Choisissez un plugin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Merci de choisir le plugin que vous souhaitez configurer" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Ajouter" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Fermer" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Merci de confirmer" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Voulez-vous supprimer les éléments sélectionnés?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Supprimer" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Annuler" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Valider" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "En cours..." + diff --git a/locale/gui.pot b/locale/gui.pot deleted file mode 100644 index dc74397a0..000000000 --- a/locale/gui.pot +++ /dev/null @@ -1,511 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR pyLoad Team -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: pyLoad 0.4.9\n" -"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" -"POT-Creation-Date: 2011-12-07 19:21+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: pyLoadGui.py:290 -msgid "paused" -msgstr "" - -#: pyLoadGui.py:292 -msgid "running" -msgstr "" - -#: pyLoadGui.py:332 -msgid "Unnamed" -msgstr "" - -#: pyLoadGui.py:655 -#, python-format -msgid "Finished downloading of '%s'" -msgstr "" - -#: pyLoadGui.py:657 -#, python-format -msgid "Failed downloading '%s'!" -msgstr "" - -#: pyLoadGui.py:660 -#, python-format -msgid "Added '%s' to queue" -msgstr "" - -#: pyLoadGui.py:685 -msgid "Connection lost" -msgstr "" - -#: pyLoadGui.py:685 -msgid "Lost connection to the core!" -msgstr "" - -#: pyLoadGui.py:720 -msgid "Show" -msgstr "" - -#: pyLoadGui.py:725 module/gui/MainWindow.py:133 -msgid "Exit" -msgstr "" - -#: module/gui/Connector.py:76 -msgid "bad login credentials" -msgstr "" - -#: module/gui/Connector.py:78 -msgid "no ssl support" -msgstr "" - -#: module/gui/Connector.py:80 -msgid "can't connect to host" -msgstr "" - -#: module/gui/Connector.py:94 -#, python-format -msgid "server is version %(new)s client accepts version %(current)s" -msgstr "" - -#: module/gui/Collector.py:47 -msgid "finished" -msgstr "" - -#: module/gui/Collector.py:48 -msgid "offline" -msgstr "" - -#: module/gui/Collector.py:49 -msgid "online" -msgstr "" - -#: module/gui/Collector.py:50 -msgid "queued" -msgstr "" - -#: module/gui/Collector.py:51 -msgid "skipped" -msgstr "" - -#: module/gui/Collector.py:52 -msgid "waiting" -msgstr "" - -#: module/gui/Collector.py:53 -msgid "temp. offline" -msgstr "" - -#: module/gui/Collector.py:54 -msgid "starting" -msgstr "" - -#: module/gui/Collector.py:55 -msgid "failed" -msgstr "" - -#: module/gui/Collector.py:56 -msgid "aborted" -msgstr "" - -#: module/gui/Collector.py:57 -msgid "decrypting" -msgstr "" - -#: module/gui/Collector.py:58 -msgid "custom" -msgstr "" - -#: module/gui/Collector.py:59 -msgid "downloading" -msgstr "" - -#: module/gui/Collector.py:60 -msgid "processing" -msgstr "" - -#: module/gui/Collector.py:284 module/gui/PackageDock.py:66 -#: module/gui/Queue.py:152 -msgid "Name" -msgstr "" - -#: module/gui/Collector.py:286 module/gui/Queue.py:156 -msgid "Plugin" -msgstr "" - -#: module/gui/Collector.py:288 module/gui/Queue.py:154 -msgid "Status" -msgstr "" - -#: module/gui/Collector.py:290 module/gui/Queue.py:158 -msgid "Size" -msgstr "" - -#: module/gui/Accounts.py:75 -msgid "not valid" -msgstr "" - -#: module/gui/Accounts.py:77 module/gui/Accounts.py:207 -msgid "n/a" -msgstr "" - -#: module/gui/Accounts.py:80 -msgid "%a, %d %b %Y %H:%M" -msgstr "" - -#: module/gui/Accounts.py:83 module/gui/Accounts.py:205 -msgid "unlimited" -msgstr "" - -#: module/gui/Accounts.py:138 module/gui/AccountEdit.py:38 -msgid "Type" -msgstr "" - -#: module/gui/Accounts.py:140 module/gui/AccountEdit.py:39 -msgid "Login" -msgstr "" - -#: module/gui/Accounts.py:142 -msgid "Valid until" -msgstr "" - -#: module/gui/Accounts.py:144 -msgid "Traffic left" -msgstr "" - -#: module/gui/SettingsWidget.py:74 -msgid "General" -msgstr "" - -#: module/gui/SettingsWidget.py:75 -msgid "Plugins" -msgstr "" - -#: module/gui/SettingsWidget.py:89 -msgid "Reload" -msgstr "" - -#: module/gui/SettingsWidget.py:90 module/gui/ConnectionManager.py:206 -#: module/gui/AccountEdit.py:53 -msgid "Save" -msgstr "" - -#: module/gui/SettingsWidget.py:192 -msgid "Yes" -msgstr "" - -#: module/gui/SettingsWidget.py:193 -msgid "No" -msgstr "" - -#: module/gui/ConnectionManager.py:42 module/gui/ConnectionManager.py:185 -msgid "pyLoad ConnectionManager" -msgstr "" - -#: module/gui/ConnectionManager.py:47 -msgid "New" -msgstr "" - -#: module/gui/ConnectionManager.py:48 module/gui/MainWindow.py:324 -msgid "Edit" -msgstr "" - -#: module/gui/ConnectionManager.py:49 module/gui/MainWindow.py:275 -#: module/gui/MainWindow.py:295 module/gui/MainWindow.py:323 -msgid "Remove" -msgstr "" - -#: module/gui/ConnectionManager.py:50 -msgid "Connect" -msgstr "" - -#: module/gui/ConnectionManager.py:56 -msgid "Connect:" -msgstr "" - -#: module/gui/ConnectionManager.py:73 -msgid "Use internal Core:" -msgstr "" - -#: module/gui/ConnectionManager.py:117 -#, python-format -msgid "%s (Default)" -msgstr "" - -#: module/gui/ConnectionManager.py:190 -msgid "Name:" -msgstr "" - -#: module/gui/ConnectionManager.py:191 -msgid "Host:" -msgstr "" - -#: module/gui/ConnectionManager.py:192 -msgid "Local:" -msgstr "" - -#: module/gui/ConnectionManager.py:193 -msgid "User:" -msgstr "" - -#: module/gui/ConnectionManager.py:194 -msgid "Password:" -msgstr "" - -#: module/gui/ConnectionManager.py:195 -msgid "Port:" -msgstr "" - -#: module/gui/ConnectionManager.py:207 module/gui/CaptchaDock.py:65 -msgid "Cancel" -msgstr "" - -#: module/gui/MainWindow.py:44 -msgid "pyLoad Client" -msgstr "" - -#: module/gui/MainWindow.py:92 -msgid "Packages:" -msgstr "" - -#: module/gui/MainWindow.py:96 -msgid "Files:" -msgstr "" - -#: module/gui/MainWindow.py:100 -msgid "Status:" -msgstr "" - -#: module/gui/MainWindow.py:104 -msgid "Space:" -msgstr "" - -#: module/gui/MainWindow.py:108 -msgid "Speed:" -msgstr "" - -#: module/gui/MainWindow.py:129 -msgid "File" -msgstr "" - -#: module/gui/MainWindow.py:130 -msgid "Connections" -msgstr "" - -#: module/gui/MainWindow.py:134 -msgid "Connection manager" -msgstr "" - -#: module/gui/MainWindow.py:156 -msgid "Overview" -msgstr "" - -#: module/gui/MainWindow.py:157 -msgid "Queue" -msgstr "" - -#: module/gui/MainWindow.py:158 -msgid "Collector" -msgstr "" - -#: module/gui/MainWindow.py:159 -msgid "Accounts" -msgstr "" - -#: module/gui/MainWindow.py:160 -msgid "Settings" -msgstr "" - -#: module/gui/MainWindow.py:161 -msgid "Log" -msgstr "" - -#: module/gui/MainWindow.py:190 -msgid "Hide Toolbar" -msgstr "" - -#: module/gui/MainWindow.py:194 -msgid "Toggle Pause/Resume" -msgstr "" - -#: module/gui/MainWindow.py:200 -msgid "Stop" -msgstr "" - -#: module/gui/MainWindow.py:202 module/gui/MainWindow.py:302 -#: module/gui/MainWindow.py:322 -msgid "Add" -msgstr "" - -#: module/gui/MainWindow.py:204 -msgid "Check Clipboard" -msgstr "" - -#: module/gui/MainWindow.py:211 module/gui/MainWindow.py:308 -#: module/gui/Overview.py:101 -msgid "Package" -msgstr "" - -#: module/gui/MainWindow.py:212 module/gui/MainWindow.py:309 -msgid "Container" -msgstr "" - -#: module/gui/MainWindow.py:213 -msgid "Account" -msgstr "" - -#: module/gui/MainWindow.py:214 module/gui/MainWindow.py:310 -msgid "Links" -msgstr "" - -#: module/gui/MainWindow.py:238 -msgid "Push selected packages to queue" -msgstr "" - -#: module/gui/MainWindow.py:261 -msgid "New Account" -msgstr "" - -#: module/gui/MainWindow.py:276 module/gui/MainWindow.py:298 -msgid "Restart" -msgstr "" - -#: module/gui/MainWindow.py:277 -msgid "Pull out" -msgstr "" - -#: module/gui/MainWindow.py:278 -msgid "Abort" -msgstr "" - -#: module/gui/MainWindow.py:279 module/gui/MainWindow.py:297 -msgid "Edit Name" -msgstr "" - -#: module/gui/MainWindow.py:296 -msgid "Push to queue" -msgstr "" - -#: module/gui/MainWindow.py:299 -msgid "Refresh Status" -msgstr "" - -#: module/gui/MainWindow.py:402 -#, python-format -msgid "All Container Types (%s)" -msgstr "" - -#: module/gui/MainWindow.py:403 -#, python-format -msgid "DLC (%s)" -msgstr "" - -#: module/gui/MainWindow.py:404 -#, python-format -msgid "CCF (%s)" -msgstr "" - -#: module/gui/MainWindow.py:405 -#, python-format -msgid "RSDF (%s)" -msgstr "" - -#: module/gui/MainWindow.py:406 -#, python-format -msgid "Text Files (%s)" -msgstr "" - -#: module/gui/MainWindow.py:408 -msgid "Open container" -msgstr "" - -#: module/gui/PackageDock.py:25 -msgid "New Package" -msgstr "" - -#: module/gui/PackageDock.py:68 -msgid "Password" -msgstr "" - -#: module/gui/PackageDock.py:71 -msgid "Links in this Package" -msgstr "" - -#: module/gui/PackageDock.py:77 -msgid "Create" -msgstr "" - -#: module/gui/PackageDock.py:78 -msgid "Filter URLs" -msgstr "" - -#: module/gui/CaptchaDock.py:29 -msgid "Captcha" -msgstr "" - -#: module/gui/CaptchaDock.py:64 -msgid "OK" -msgstr "" - -#: module/gui/AccountEdit.py:32 -msgid "Edit account" -msgstr "" - -#: module/gui/AccountEdit.py:40 -msgid "New password" -msgstr "" - -#: module/gui/AccountEdit.py:83 -msgid "Create account" -msgstr "" - -#: module/gui/Queue.py:160 -msgid "ETA" -msgstr "" - -#: module/gui/Queue.py:162 -msgid "Progress" -msgstr "" - -#: module/gui/Queue.py:384 -#, python-format -msgid "waiting %d seconds" -msgstr "" - -#: module/gui/Overview.py:71 module/gui/Overview.py:152 -msgid "Downloading" -msgstr "" - -#: module/gui/Overview.py:83 -msgid "Queued" -msgstr "" - -#: module/gui/Overview.py:147 -msgid "ETA: " -msgstr "" - -#: module/gui/Overview.py:149 -msgid "Parts: " -msgstr "" - -#: module/gui/Overview.py:151 -msgid "Finished" -msgstr "" - -#: module/gui/Overview.py:155 -#, python-format -msgid "Speed: %s" -msgstr "" - -#: module/gui/Overview.py:158 module/gui/Overview.py:160 -msgid "Size:" -msgstr "" diff --git a/locale/he/LC_MESSAGES/cli.po b/locale/he/LC_MESSAGES/cli.po new file mode 100644 index 000000000..89888fcc1 --- /dev/null +++ b/locale/he/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hebrew\n" +"Language: he_IL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/he/LC_MESSAGES/core.po b/locale/he/LC_MESSAGES/core.po new file mode 100644 index 000000000..5bcc5f8a8 --- /dev/null +++ b/locale/he/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hebrew\n" +"Language: he_IL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/he/LC_MESSAGES/plugins.po b/locale/he/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..18cdf35c8 --- /dev/null +++ b/locale/he/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hebrew\n" +"Language: he_IL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/he/LC_MESSAGES/setup.po b/locale/he/LC_MESSAGES/setup.po new file mode 100644 index 000000000..d939d7ca3 --- /dev/null +++ b/locale/he/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hebrew\n" +"Language: he_IL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/he/LC_MESSAGES/webUI.po b/locale/he/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..719229440 --- /dev/null +++ b/locale/he/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hebrew\n" +"Language: he_IL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/hi/LC_MESSAGES/cli.po b/locale/hi/LC_MESSAGES/cli.po new file mode 100644 index 000000000..12c1e0221 --- /dev/null +++ b/locale/hi/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hindi\n" +"Language: hi_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/hi/LC_MESSAGES/core.po b/locale/hi/LC_MESSAGES/core.po new file mode 100644 index 000000000..4d49b4127 --- /dev/null +++ b/locale/hi/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hindi\n" +"Language: hi_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/hi/LC_MESSAGES/plugins.po b/locale/hi/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..e5571e010 --- /dev/null +++ b/locale/hi/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hindi\n" +"Language: hi_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/hi/LC_MESSAGES/setup.po b/locale/hi/LC_MESSAGES/setup.po new file mode 100644 index 000000000..49cb1015e --- /dev/null +++ b/locale/hi/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hindi\n" +"Language: hi_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/hi/LC_MESSAGES/webUI.po b/locale/hi/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..c8287445d --- /dev/null +++ b/locale/hi/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hindi\n" +"Language: hi_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/hu/LC_MESSAGES/cli.po b/locale/hu/LC_MESSAGES/cli.po new file mode 100644 index 000000000..75295ee0a --- /dev/null +++ b/locale/hu/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hungarian\n" +"Language: hu_HU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Csomag hozzáadása:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Add meg az új csomag nevét" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Csomag: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Hozzáadandó linkek." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Írd %s miután végeztél." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Link felvéve: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " vissza a főmenübe" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Csomagok kezelése:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Linkek kezelése:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Mit szeretnél áthelyezni?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Mit szeretnél törölni?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Mit szeretnél újraindítani?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Válaszd ki ki mit szeretnél tenni vagy adj meg egy csomagszámot." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "törlés" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "áthelyezés" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "újraindítás" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - előző" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - következő" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Parancssoros felület" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s letöltés:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Sebesség: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Méret: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Befejezve: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "várakozás: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Állapot:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "felfüggesztve" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "fut" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "össz. sebesség" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Várólistás fájlok" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Összesen" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menü:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Link hozzáadás" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Várólista kezelése" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Gyűjtő kezelése" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Szerver szüneteltetése/folytatása" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Szerver kilövése (Kill)" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Bezárás" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Használd a következő formát: add <Csomag neve> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "%d link ellenőrzése:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "A fájl nem létezik." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad leállítva" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Szerver állapota" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Várólistás fájlok" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Gyűjtőben levő letöltések" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Csomag várólistához adása" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Csomag gyűjtőhöz adása" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Fájl törlése várólista/gyűjtő" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Csomag törlése várólista/gyűjtő" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Csomag áthelyezése a várólista és a gyűjtő között" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Fájlok újraindítása" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Csomagok újraindítása" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Online státusz ellenőrzése, csak helyi konténer esetében" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "A konténer fájl online státuszának ellenőrzése" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Szerver szüneteltetése" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "letöltések folytatása" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Szüneteltetés/folytatás" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "szerver kilövése (Kill)" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Parancsok listája:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Nem sikerült írni a felhasználói beállítások fájlját" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Ehhez a pyLoad maghoz való csatlakozáshoz szükséged van py-openssl-re." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Cím: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Felhasználó: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Jelszó: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Hibás belépési adatok." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Nem sikerült kapcsolódni ide: %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Az interaktív mód néhány parancs után figyelmen kívül hagyva." + diff --git a/locale/hu/LC_MESSAGES/core.po b/locale/hu/LC_MESSAGES/core.po new file mode 100644 index 000000000..5ef71c495 --- /dev/null +++ b/locale/hu/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hungarian\n" +"Language: hu_HU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Hiba futtatás közben %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Sikertelen aktiválás: %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Aktivált bővítmény: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Bővítmény deaktiválása: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Bővítmények aktiválása..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Bővítmények deaktiválása..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL tanúsítvány nem található." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "Beépített WebUI nem áll rendelkezésre" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "A webUI fejlesztői módban való futtatása" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "A web szervert nem sikerült elindítani: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "A web szervert nem sikerült betölteni: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Ez a szerver nem támogatja az SSL-t, kérlek használj helyette másikat" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "%(name)s web szerver indítása: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Távoli" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Leírás" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Hosszú leírás" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Aktivált" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Cím" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Napló" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Méret (kb)" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "mappa" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Log Fájl" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Számláló" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Log Forgatás" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Engedélyek" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Csoport név" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Letöltések csoportjának és felhasználójának váltása" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Letöltések fájl jogosultságának váltása" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Felhasználónév" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Fájl jogosultságok" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Futó folyamat csoportjának váltása" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Könyvtár jogosultságok" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Futó folyamat felhasználójának váltása" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Általános" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Nyelv" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Letöltések helye" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Ellenőrző összeg használata" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Minden csomaghoz könyvtár létrehozás" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Hibakereső mód" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Min. szabad hely (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "CPU prioritás" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL-tanúsítvány" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL kulcs" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Webes felület" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Sablon" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Elérési út előtag" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Szerver" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Előnyben részesített szerver" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "HTTPS használata" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Fejlesztő mód" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Proxy használata" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Jelszó" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protokoll" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Újracsatlakozás" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Vége" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Újracsatlakozás használata" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Mód" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Indít" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Letöltés" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Max. egyidejű letöltés" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Letöltési sebesség korlátozása" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "A letöltő interfész itt figyel (ip vagy név)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Létező fájlok kihagyása" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Max. letöltési sebesség (kb/s)" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "IPv6 engedélyezése" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Max. kapcsolatok száma egy letöltéshez" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Sikertelen letöltések újraindítása indulásnál" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Letöltési idő" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Darabokban letöltés sikertelen, visszatérés egyszeri kapcsolatra | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "A(z) %(name)s csomag %(folder)s könyvtárként hozzáadva" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "%d link a csomaghoz adva" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Ismeretlen hozzáférés bővítmény %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Lekérdezés" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Captcha kérés" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Old meg a captcha-t" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Távoli hiba: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Indítás: %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Backend betöltése sikertelen: %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "semmi" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "offline" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "online" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "várólistán" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "felfüggesztve" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "befejezett" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "átugorva" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "sikertelen" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "indítás" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "várakozás" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "letöltés" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "átmenetileg offline" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "megszakított" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "dekódolás" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "feldolgozás alatt" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "egyéni" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "ismeretlen" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Kész csomag: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "'%s' felhasználó próbál bejelentkezni" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Leállítási jel érkezett" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "a pyLoad már fut az alábbi pid számmal: %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Sikertelen csoport váltás: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Sikertelen felhasználó váltás: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Indítás" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Főkönyvtár használata: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Minden link törölve" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Letöltési idő: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Szabad hely: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Hozzáférések aktiválása..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Sikertelen letöltések újraindítása..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad fut" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "pyLoad újraindítása" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad kilép" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "leállítás..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "hiba leállítás közben" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad kilőve konzolról" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Nem kompatibilis verzió miatt az adatbázis törölve." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Sikertelen dekódolás" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "%(count)d link dekódolása a(z) %(name)s csomagba" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Nincs dekódolt link" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "%(name)s információinak lekérése sikertelen | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Újracsatlakozás sikertelen: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Újracsatlakozó script nem található!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Újracsatlakozás indítása" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Az újracsatlakozó scriptet nem sikerült futtatni!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Újracsatlakozva, új IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Nincs elég hely az eszközön" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Letöltés elindítva: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Letöltés befejezve: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "A %s bővítményből hiányzik egy funkció." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Letöltés megszakítva: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Letöltés újraindítva: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "A letöltés offline: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "A letöltés átmenetileg offline: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "A letöltés sikertelen: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Nem sikerült csatlakozni a címhez, újrapróbálás 1 perc múlva." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Letöltés átugorva: %(name)s - %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Internal Server Error" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Hiba történt." + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Hiba a(z) %(name)s betöltése közben: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Nem észlelhető js motor, kérlek telepítsd valamelyiket: Spidermonkey, ossp-js, pyv8, nodejs, rhino" + diff --git a/locale/hu/LC_MESSAGES/plugins.po b/locale/hu/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..5848e7395 --- /dev/null +++ b/locale/hu/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hungarian\n" +"Language: hu_HU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Darabokban letöltés sikertelen, visszatérés egyszeri kapcsolatra | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Captcha megfejtéshez nincs kliens csatlakozva és nincs telepítve Pil vagy tesseract" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Nem érkezett captcha eredmény a megadott időn belül." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Felhasználó és csoport beállítás sikertelen: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Nem létező fájl, vagy nem támogatott protokoll" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Adatforgalom megosztás (közvetlen letöltés)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Már van folyamatban letöltés erről az IP-ről, várakozás: 60mp" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Hibás Auth kód, a letöltés újraindítása" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Nincs ingyenes hely" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Ehhez a fájlhoz prémium hozzáférés szükséges" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Hibás fájlnév" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Add meg a(z) %s hozzáférés adatait, vagy deaktiváld ezt a beépülőt" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Sikertelen dekódolás" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Nincs fájl kulcs az URL-ben" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Hibakód:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Többszálas letöltés hiba, várakozás: 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Nem vagy bejelentkezve." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Érvénytelen API kulcs" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Nincs elég adatforgalom hátra" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Adatforgalom túllépve" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Azonosítás szükséges (felhasználónév:jelszó)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Állomány átmenetileg nem elérhető" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: letöltések közti várakozás %d mp." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: captcha-ra várakozás %d mp." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "A letöltött fájl üres" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "HTML kód van a letöltött fájlban (%s). Lehet, hogy átirányítási hiba, a letöltés újra lesz indítva." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "hosszú_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Nem sikerült bejelentkezni: %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Hibás jelszó" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Hozzáférés információinak letöltése: %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Hiba: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "A %s időformátum hibás, használd így: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "%s hozzáférésen nincs elég adatforgalom, újra próbálás: 30 perc" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "%s hozzáférés lejárt, újra próbálás: 1 óra" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Bejelentkezés mint %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Hiba a bővítmény futtatása közben: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Közvetlen letöltés aktiválása a Bitshare hozzáférésen" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "A letöltési korlát elérve" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Először adj meg premium.to hozzáférést majd indítsd újra a -ot." + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Scriptek telepítve - %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script nem futtatható:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Hiba: %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s kredit maradt" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Nem sikerült választ küldeni." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "A CaptchaTrader hozzáférésen nincs elég credit" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Új CaptchaID a feltöltésből: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "A Captcha 9kw.eu hozzáférésen nincs elég credit" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Először adj meg rehost.to hozzáférést majd indítsd újra a pyLoad-ot." + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "%s HotFolder-ből hozzáadva" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: A 9666 port használatban van" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Kész csomag: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Letöltés befejezve: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Az ExpertDecoders hozzáférésen nincs elég credit" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Bővítmények frissítve, indítsd újra a pyLoad-ot ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Bővítmények frissítve és betöltve" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Nincs bővítmény frissítés" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Nincs új pyLoad verzió" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Új pyLoad verzió elérhető: %s ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Letöltés: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Nem sikerült a szerverhez csatlakozni a frissítésekért" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Új verzió: %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Hiba frissítés közben %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "verzió ütközés" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Nincs %s telepítve" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Nem sikerült aktiválni: %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Aktivált" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Nincs kicsomagoló bővítmény aktiválva" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "%s csomag a várólistán későbbi kitömörítésre" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Csomag ellenőrzése: %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Kicsomagolás: %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Nincs kitömöríthető fájl" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "kicsomagolás" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Jelszóval védett" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Hibás jelszó" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "%s fájl törlése" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Kicsomagolás kész" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Tömörített állomány hiba" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC hiba" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Ismeretlen hiba" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Felhasználó és csoport beállítás sikertelen" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Crypter lista nem található" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Crypter lista üres" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Letöltés befejezve: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Új captcha kérés: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Válaszolj ezzel: 'c %s szöveg a captcha-n'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Először adj meg premiumize.me hozzáférést majd indítsd újra a pyLoad-ot." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d kredit maradt" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Aktivált: %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Nincs szolgáltató betöltve" + diff --git a/locale/hu/LC_MESSAGES/setup.po b/locale/hu/LC_MESSAGES/setup.po new file mode 100644 index 000000000..a6f7fb8a5 --- /dev/null +++ b/locale/hu/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hungarian\n" +"Language: hu_HU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Szeretnéd a pyLoad-ot webes felületen beállítani?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Szükséged lesz egy böngészőre és egy kapcsolatra ehhez a számítógéphez." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "A cím: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Webes felület indítása a beállításokhoz?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Üdvözöllek a pyLoad beállítási segédben." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Ellenőrizzük a rendszert és létrehozunk egy alap beállítást a pyLoad számára." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "A [] közötti érték minden esetben az alapértelmezett érték," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "ha nem akarod megváltoztatni, vagy nem vagy biztos benne mit válassz, csak nyomj entert." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Ne feledd: a segédhez visszatérhetsz ha a pyLoadCore-t a --setup vagy -s paraméterekkel indítod." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Ha bármi probléma lépne fel a segéd futása közben nyomj STRG-C kombinációt," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "a megszakításhoz, és hogy ne induljon el újra automatikusan." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Ha készen állsz a rendszer ellenőrzésre, nyomj Entert." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Hiányzó szolgáltatások: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto nem elérhető" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Erre szükséged van a konténer fájlok dekódolásához." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL nem elérhető" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Erre szükséged van, ha biztonságos kapcsolatot szeretnél a mag vagy webes felület eléréséhez." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Ha csak helyben akarod elérni a pyLoad-ot, az ssl nem szükséges." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "nincs elérhető Captcha felismerő" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Csak néhány szolgáltatóhoz kell és ingyenes felhasználóként." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "nincs JavaScript motor" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Néhány Click'N'Load linknek szüksége van erre. Telepítsd a Spidermonkey, ossp-js, pyv8 vagy rhino csomagokat" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Most megszakíthatod a telepítést, és kijavíthatod az esetleges hibákat." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Folytatod a beállítást?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Megváltoztatod a konfigurációs fájlok helyét? A jelenlegi: %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Ha a pyLoad-ot a belső tárolóban futtatja a fő partíción, ajánlott megváltoztatni." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Megváltoztatod a konfigurációs fájlok helyét?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Be akarod állítani a bejelentkezési adatokat és az általános beállításokat?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Ajánlott az első futtatásnál." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Létre akarod hozni az általános beállításokat?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Be akarod állítani az ssl-t?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Beállítod az ssl-t?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Beállítod a webes kezelőfelületet?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Beállítod a webes kezelőfelületet?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "A beállítások sikeresen elvégezve." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Nyomj entert a kilépéshez majd indítsd újra a pyLoad-ot" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Webes felület indítása a beállításokhoz." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Általános beállítások ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Ezek a belépési adatok a következőkhöz alkalmasak: CLI, GUI, web felület." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Felhasználónév" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Külső kliensek (GUI, CLI, ...) számára szükséges távoli elérés a hálózaton keresztüli működéshez." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Ha csak a webes felületet használod, akkor kikapcsolhatod, hogy memóriát takaríts meg." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Távoli hozzáférés engedélyezése" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Nyelv" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Letöltési könyvtár" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Max. egyidejű letöltés" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Újracsatlakozás használata?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Újracsatlakozó script helye" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Webes felület Beállítása ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Webes felület engedélyezése?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Figyelt cím, ha csak helyben akarod elérni akkor használd az 127.0.0.1 vagy localhost nevet." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Cím" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "A pyLoad számos backend szerver lehetőséget nyújt, ezekről következik egy rövid ismertető." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Alap beállítás, ez a szerver támogatja az SSL-t és jó alternatíva lehet a beépítettel szemben." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Használható több web-szerverrel (apache, lighttpd) egyéni beállítást igényel, ami nem egyszerű." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Nagyon gyors C-ben írt alternatíva, libev és linux ismeretek szükségesek hozzá." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Letölthető innen: https://github.com/jonashaag/bjoern, le kell fordítani" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "és a bjoern.so fájlt a pyload/lib könyvtárba másolni" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Figyelem: Néhány kivételes esetben a beépített szerver nem működik megfelelően. Ha hibát észlel a webes felületen" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "térjen vissza ide, és válasszon másik szervert." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Szerver" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL beállítás ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Futtasd ezeket a parancsokat a konfigurációs könyvtárban, az ssl tanúsítványok elkészítéséhez:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Ha kész vagy, és minden rendben, akkor aktiválhatod az ssl-t." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "SSL engedélyezése?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Válassz műveletet" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Felhasználó szerkesztés/felvétel" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Felhasználók listázása" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Felhasználó törlése" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Kilépés" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Felhasználók" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "A konfigurációs fájlok új helye, a jelenlegi konfiguráció nem kerül áthelyezésre." + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Konfiguráció helye" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "A konfigurációs fájlok helye megváltozott." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Nyomj Entert a kilépéshez." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "A beállítások eléri útja hibás: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "i" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Jelszó: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Rövid jelszó. Legalább 4 karaktert használj." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Jelszó (ismét): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "A jelszavak nem egyeznek." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "igen" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "igaz" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "i" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "nem" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "hamis" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "h" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Hibás érték" + diff --git a/locale/hu/LC_MESSAGES/webUI.po b/locale/hu/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..16336ff19 --- /dev/null +++ b/locale/hu/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Hungarian\n" +"Language: hu_HU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "nem elérhető" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "végtelen" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Admin" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Beállítás" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Fiók hozzáadása" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Hozzáférések" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Helyi" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Keresés" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Típus" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Összes" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Befejezett" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Befejezetlen" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Sikertelen" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 csomag" +msgstr[1] "%d csomag" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 fájl" +msgstr[1] "%d fájl" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Fiók hozzáadása" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Add meg a fiók adatait" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Beépülő választása" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Válaszd ki a beállítani kívánt beépülőt" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Hozzáad" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Bezárás" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Kérlek erősítsd meg" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Biztos törölni akarod a kiválasztott elemeket?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Törlés" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Mégse" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Küldés" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "fut..." + diff --git a/locale/it/LC_MESSAGES/cli.po b/locale/it/LC_MESSAGES/cli.po new file mode 100644 index 000000000..5b10c6001 --- /dev/null +++ b/locale/it/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Aggiungi Pacchetto:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Inserisci un nome per il nuovo pacchetto" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Pacchetto: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analizza i link che desideri aggiungere." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Digita %s quanto hai fatto." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Link aggiunti: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " torna al menu principale" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Gestisci Pacchetti:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Gestisci Link:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Che cosa vuoi spostare?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Che cosa vuoi cancellare?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Che cosa vuoi riavviare?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Scegli cosa vuoi fare oppure digita il numero del pacchetto." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "cancella" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "sposta" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "riavvia" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - precendente" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - sucessivo" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Interfaccia a riga di comando" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s File Scaricati:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Velocità: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Dimensione: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Terminato in: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "in attesa: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Stato:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "in pausa" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "in esecuzione" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Velocità totale" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "File in coda" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Totale" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menu:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Aggiungi Link" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "Gestisci Coda" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Gestisci Libreria" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Pausa/Riattiva Server" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Termina Server" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Esci" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Si prega di utilizzare questa sintassi: add <Nome pacchetto> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Controllo %d link:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Il file non esiste." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad è stato chiuso" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Mostra lo stato del server" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Mostra i download in coda" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Mostra i download nella libreria" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Aggiunge pacchetti alla coda" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Aggiungi pacchetto alla libreria" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Elimina file dalla Coda/Collezione" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Elimina pacchetti dalla Coda/Libreria" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Sposta Pacchetti dalla Coda alla Libreria o vice versa" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Riavvia file" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Riavvia pacchetti" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Controlla stato online, funziona con contenitori locali" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Controlla stato online di un file contenitore" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Mette in pausa il server" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "continua i download" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Pulsante pausa/riprendi" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr " termina server" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lista dei comandi:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Impossibile scrivere il file di configurazione utente" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Hai bisogno di py-openssl per connetterti a questo pyLoad core." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Indirizzo:" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Porta: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Nome utente: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Password:" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "I dati di login sono errati." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Impossibile stabilire una connessione a %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Modalità interattiva ignorata dato che hai passato alcuni comandi." + diff --git a/locale/it/LC_MESSAGES/core.po b/locale/it/LC_MESSAGES/core.po new file mode 100644 index 000000000..c113a7b12 --- /dev/null +++ b/locale/it/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Errore nell'esecuzione di %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Attivazione di %(name)s fallita" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Addon attivati: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Addon disattivati: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Attivazione Plugin..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Disattivazione Plugin..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Certificati SSL non trovati." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "WebUI è non disponibile" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Esecuzione della WebUI in modalità sviluppo" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Avvio del webserver fallito: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Importazione del webserver fallita: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Questo server non supporta SSL, si consiglia l'uso della versione threaded" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Avvio del webserver %(name)s: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Remoto" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Descrizione" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Descrizione estesa" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Attivato" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Porta" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Indirizzo" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Log" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Dimensione in kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Cartella" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "File di log" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Conteggio" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Rotazione Log" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Permessi" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Nome del Gruppo" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Cambia gruppo e utente per i download" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Cambia modalità di download file" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Nome utente" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Modalità per il download" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Cambia il gruppo del processo in esecuzione" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Modalità per i permessi delle cartelle" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Cambia l'utente del processo in esecuzione" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Generale" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Lingua" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Cartella Download" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Usa Checksum" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Crea una cartella per ogni pacchetto" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Modalità Debug" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Spazio Libero Minimo (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Priorità CPU" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Certificato SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Chiave SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Interfaccia Web" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Modello" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Prefisso Percorso" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Server" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Favorisci server specifico" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Usa HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Modalità sviluppo" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Usa Proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Password" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protocollo" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Riconnessione" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Fine" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Usa Riconnessione" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Metodo" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Avvia" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Download" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Numero massimo di download paralleli" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Limita la velocità di download" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Interfaccia di download da associare (ip o nome)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Salta i file già esistenti" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Massima velocità di download in kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Consenti IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Numero massimo di connessioni per singolo download" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Riavvia i download falliti all'avvio" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Tempo di Download" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Download accellerato fallito, ripiego su singola connessione | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Aggiunto il pacchetto %(name)s come cartella %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Aggiunti %d link al pacchetto" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Plugin account sconosciuto %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Ricerca" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Richiesta captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Per favore risolvi il captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Errore backend remoto: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Avvio %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Impossibile avviare backend %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "nessuno" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "offiline" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "online" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "in coda" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "in pausa" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "finito" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "saltato" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "fallito" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "avviando" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "in attesa" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "scaricando" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "temp. offline" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "annullato" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "decifrando" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "elaborando" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "personalizzato" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "sconosciuto" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Pacchetto completato: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Utente '%s' tenta di accedere" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Ricevuto segnale di uscita" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad già in esecuzione con pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Impossibile cambiare il gruppo: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Impossibile cambiare utente: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Avvio" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Uso la cartella home: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Tutti i link rimossi" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Tempo di scaricamento: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Spazio libero: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Attivazione Account..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Riavvio download falliti..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad è attivo e funzionante" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "riavvio pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "esco da pyLoad" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "chiusura..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "errore durante lo spegnimento" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad chiuso da terminale" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Database cancellato a causa della versione incompatibile." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Decifratura non riuscita" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Decifrato %(count)d link nel pacchetto %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Nessun link decriptato" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Recupero informazioni per %(name)s fallito | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Riconnessione fallita: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Script per la riconnessione non trovato!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Nuovo tentativo di connessione" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Fallita l'esecuzione dello script di riconnessione!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Riconnesso, nuovo IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Spazio su disco insufficiente" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Avvio Download: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Download terminato: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Il plugin %s ha fallito una funzione." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Download annullato: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Download riavviato: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Il download è offline: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Il download è temporaneamente offline: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Download fallito: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Impossibile collegarsi all'host o connessione resettata, aspetto 1 minuto e riprovo." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Download saltato: %(name)s a causa di %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Errore Interno del Server" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Si è verificato un errore" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Errore importando %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Nessuno motore js rilevato, per favore installa uno tra Spidermonkey, ossp-js, pyv8, nodejs o rhino" + diff --git a/locale/it/LC_MESSAGES/django.mo b/locale/it/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 57858c8a6..000000000 --- a/locale/it/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/it/LC_MESSAGES/plugins.po b/locale/it/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..d8e3974e8 --- /dev/null +++ b/locale/it/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Download accellerato fallito, ripiego su singola connessione | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil e tesseract non sono installati e nessun Client è connesso per decifrare i captcha" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Nessun risultato captcha ottenuto in tempo." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Impostazione di Utente e Gruppo non riuscita: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "File non esistente o protocollo non supportato" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Traffic Share (download diretto)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Stai già scaricando da questo indirizzo IP, aspetta 60 secondi" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Auth Code non valido, il download sarà riavviato" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Nessuno slot gratuito libero" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Hai bisogno di un account Premium per questo file" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Nome di file riportato non valido" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Per favore Inserisci il tuo account %s o disattiva questo plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Decifratura non riuscita" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Nessuna chiave fornita nell'URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Codice di errore:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Errore durante il download parallelo, attendo 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Non connesso." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "Chiave API invalida" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Traffico rimanente non sufficiente" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Traffico superato" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Richiesta autorizzazione (nomeutente:password)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "File temporaneamente non disponibile" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: attendo %d s tra i download." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: attendo %d s per il captcha." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Il file scaricato era vuoto" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "C'era del codice HTML nel file scaricato (%s)... errore di reindirizzamento? Il download sarà riavviato." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "url_esteso: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Impossibile effettuare il login con l'account %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Password errata" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Ottieni informazioni per l'account %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Errore: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Il tuo orario %s ha un formato sbagliato, usa: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "L'account %s non ha abbastanza traffico, controllo di nuovo tra 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "L'account %s è scaduto; prossimo controllo fra 1 ora" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Login come %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Errore durante l'esecuzione degli addon: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Attiva il download diretto sul tuo account Bitshare" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Limite di download raggiunto" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Inserisci prima il tuo account premium.to e poi riavvia pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Script installati per %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script non eseguibile:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Errore in %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s crediti rimasti" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Non posso inviare la risposta." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Il tuo account CaptchaTrader non ha crediti sufficienti" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nuovo CaptchaID dall'upload: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Il tuo account Captcha 9kw.eu non ha abbastanza crediti" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Inserisci prima il tuo account rehost.to e poi riavvia pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Aggiunto %s da HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Porta 9666 già in uso" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Pacchetto completato: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Download finito: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Il tuo account ExpertDecoders ha non abbastanza crediti" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** I plugin sono stati aggiornati, riavvia pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins aggiornati e riavviati" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Nessun aggiornamento per i plugin disponibile" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Nessun aggiornamento per pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Nuova versione di pyLoad %s disponibile ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Scaricala da qui: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Impossibile collegarsi al server per gli aggiornamenti" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Nuova versione di %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Errore durante l'aggiornamento di %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Versione non corrispondente" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "%s non installato" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Impossibile attivare %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Attivato" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Nessun plugin per l'estrazione attivato" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Il pacchetto %s è stato messo in coda per l'estrazione successiva" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Verifico pacchetto %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Estraggo in %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Nessun file trovato da estrarre" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "estrazione" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Protetto da password" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Password errata" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Cancello %s file" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Estrazione completata" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Archivio danneggiato" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC non corrispondente" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Errore Sconosciuto" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Configurazione Utente e Gruppo fallita" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Elenco Crypter non trovato" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Elenco Crypter vuoto" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Download finito: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Nuova richiesta Captcha: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Rispondi usando 'c %s text on the captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Aggiungi prima un account premiumize.me valido e riavviare pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d crediti rimasti" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Attivato %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Nessun Hoster caricato" + diff --git a/locale/it/LC_MESSAGES/pyLoad.mo b/locale/it/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 07b1c01c5..000000000 --- a/locale/it/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/it/LC_MESSAGES/pyLoadCli.mo b/locale/it/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 158fd317a..000000000 --- a/locale/it/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/it/LC_MESSAGES/pyLoadGui.mo b/locale/it/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index cd1cbdf73..000000000 --- a/locale/it/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/it/LC_MESSAGES/setup.mo b/locale/it/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index e13b99bc9..000000000 --- a/locale/it/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/it/LC_MESSAGES/setup.po b/locale/it/LC_MESSAGES/setup.po new file mode 100644 index 000000000..d5b06ec81 --- /dev/null +++ b/locale/it/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Vuoi configurare pyLoad attraverso l'interfaccia Web?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Hai bisogno di un Browser e un collegamento a questo PC per farlo." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "L'Url sarà: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Avvia l'interfaccia web iniziale per la configurazione?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Benvenuto nell'Assistente di Configurazione di pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Il setup controllerà il tuo sistema e genererà una configurazione di base per avviare pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Il valore tra le parentesi quadre [] è sempre il valore di default," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "nel caso in cui non vuoi cambiare impostazione o non sei sicuro su cosa scegliere, premi semplicemente Invio." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Ricorda: Puoi sempre rieseguire questo assistente con i parametri --setup o -s, quando avvii pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Se hai qualche problema con questo assistente premi CTRL+C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "per annullare e non farlo partire più automaticamente con pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Quando sei pronto per la verifica del sistema premi Invio." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Funzioni mancanti: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto non disponibile" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Hai bisogno di questo se vuoi decifrare i file contenitori." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL non disponibile" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Questo è necessario se vuoi stabilire una connessione sicura con il core o l'interfaccia web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Se vuoi accedere a pyLoad soltanto in locale, SSL non è necessario." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "Riconoscimento captcha non disponibile" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Necessario solo per alcuni hoster e come freeuser." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "nessun motore JavaScript trovato" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Avrai bisogno di questo per alcuni link Click'N'Load. Installa Spidermonkey, ossp-js, pyv8 o rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Se vuoi puoi annullare il setup ora e correggere alcune dipendenze." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Continuare con il setup?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Vuoi cambiare il percorso per la configurazione? Quello attuale è %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Se si utilizza pyLoad su un server o la partizione principale si trova su una memoria flash interna potrebbe essere una buona idea cambiarlo." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Cambiare il percorso della configurazione?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Si desidera configurare i dati di accesso e le impostazioni di base?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "E' consigliato per il primo avvio." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Creare configurazione di base?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Vuoi configurare l'ssl?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Configurare ssl?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Vuoi configurare l'interfaccia web?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Configurare l'interfaccia web?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Configurazione terminata con successo." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Premi Invio per uscire e riavviare pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Interfaccia Web in esecuzione per l'installazione." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Configurazione Base ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "I seguenti dati di accesso sono validi per CLI, GUI e interfaccia web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Nome utente" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "I client esterni (GUI, CLI o altri) richiedono accesso remoto al sistema per funzionare nella rete." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Comunque, se vuoi utilizzare solo l'interfaccia web puoi disattivarlo per ridurre il consumo di memoria ram." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Attiva l'accesso remoto" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Lingua" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Cartella Download" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Numero massimo di download paralleli" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Usare la riconnessione?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Posizione dello script di riconnessione" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Configurazione dell'interfaccia web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Attivare l'interfaccia web?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Indirizzo di ascolto, se usi 127.0.0.1 o localhost, l'interfaccia web sarà accessibile soltanto localmente." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Indirizzo" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Porta" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad offre diversi tipi di supporto a server, segue una breve spiegazione." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Server predefinito, questo server offre SSL ed è una buona alternativa al server nativo." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Può essere utilizzato con apache o lighttpd, ma ne richiede la configurazione, che non è una procedura semplice." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Alternativa molto veloce, scritta in C, richiede libev e conoscenza di linux." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Scaricalo da qui: https://github.com/jonashaag/bjoern, compilalo" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "e copia bjoern.so in pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Attenzione: In alcuni rari casi il server integrato non funzione, se noti problemi con l'interfaccia web" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "torna qui e sostituisci il server integrato con quello threaded." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Server" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Configurazione SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Esegui questi comandi dalla cartella di configurazione di pyLoad per creare i certificati SSL:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Se hai finito e tutto è andato bene, puoi attivare SSL ora." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Attivare SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Seleziona azione" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Crea/Modifica utente" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Lista utenti" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Rimuovi utente" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Esci" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Utenti" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Imposta nuovo percorso per la configurazione. La configurazione attuale NON sarà trasferita!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Percorso della configurazione" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Il percorso della configurazione è cambiato, Il setup ora si chiuderà, riavvialo per andare avanti." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Premi Invio per uscire." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Impostazione del percorso di configurazione non riuscita: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "s" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Password:" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Password troppo corta. Usa almeno 4 caratteri." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Password (di nuovo): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Le password non corrispondono." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "sì" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "vero" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "v" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "no" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "falso" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Inserimento non valido" + diff --git a/locale/it/LC_MESSAGES/webUI.po b/locale/it/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..5d62c684a --- /dev/null +++ b/locale/it/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "non disponibile" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "illimitato" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Amministratore" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Configurazione" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Aggiungi Account" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Account" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Locale" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Cerca" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Tipo" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Tutto" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Completati" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Non completati" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Falliti" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 pacchetto" +msgstr[1] "%d pacchetti" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 file" +msgstr[1] "%d file" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Aggiungi un account" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Inserisci le informazioni del tuo account" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Scegli un plugin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Scegli il plugin che vuoi configurare" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Aggiungi" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Chiudi" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Conferma" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Vuoi eliminare gli elementi selezionati?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Elimina" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Annulla" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Invia" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "In esecuzione..." + diff --git a/locale/ja/LC_MESSAGES/cli.po b/locale/ja/LC_MESSAGES/cli.po new file mode 100644 index 000000000..2e3d448fd --- /dev/null +++ b/locale/ja/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-07 09:03-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Japanese\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "パッケージを追加:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "新しいパッケージの名前を入力" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "パッケージ: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "追加したいリンクを解析します" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "終わったら %s を入力してください。" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "リンクが追加されました: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " メイン メニューに戻る" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "パッケージを管理:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "リンクを管理:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "何を移動しますか?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "何を削除しますか?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "何を再起動しますか?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "何をするかを選択するかパッケージ番号を入力してください。" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "削除" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "移動" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "再起動" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - 前" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - 次" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " コマンド ライン インタフェース" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s ダウンロード:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " 速度: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " サイズ: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " 完了: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "待機中: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "ステータス:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "一時停止" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "実行しています。" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "合計速度" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "キュー内のファイル" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "合計" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "メニュー:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " リンクを追加" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " キューを管理" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " コレクターを管理" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " サーバーを一時停止/再開" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " サーバーを終了させる" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " 終了" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "この構文を使用してください: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "%d 個のリンクをチェック中:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "ファイルが存在しません。" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad が終了しました" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "印刷サーバーの状態" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "キュー内のダウンロードを印刷" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "コレクター内のダウンロードを印刷" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "パッケージをキューに追加" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "パッケージをコレクターに追加" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "キュー/コレクターからファイルを削除" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "キュー/コレクターからパッケージを削除" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "キューからコレクター (またはその逆) にパッケージを移動" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "ファイルを再起動" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "パッケージを再起動" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "オンラインの状態やローカル コンテナの仕事をチェック" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "コンテナ ファイルのオンライン状態をチェック" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "サーバーを一時停止" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "ダウンロードを継続" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "一時停止/再開を切替え" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "サーバーを終了させる" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "コマンドの一覧:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "ユーザー設定ファイルに書き込むことができませんでした" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "この pyLoad コアに接続するには py-openssl が必要です。" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "アドレス: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "ポート: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "ユーザー名: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "パスワード: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "ログイン データが間違っています。" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "%(addr)s:%(port)s への接続を確立できませんでした。" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "対話モードはあなたが入力したいくつかのコマンドを無視しました。" + diff --git a/locale/ja/LC_MESSAGES/core.po b/locale/ja/LC_MESSAGES/core.po new file mode 100644 index 000000000..27d3efad6 --- /dev/null +++ b/locale/ja/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-07 09:03-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Japanese\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "%S を実行するとエラー" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "%(name)sをアクティブ に失敗しました" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "アドオンの起動: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "アドオンの無効: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "プラグインをアクティベート中..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "プラグインを無効にする." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL 証明書が見つかりません。" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "WebUI 構築がないです。" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "WebUI 開発モードで実行されています。" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "ウェブサーバーの開始に失敗しました:" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "ウェブサーバのインポートに失敗しました:" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "このサーバーで SSL はありません、代わりにスレッドを使用してください" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "(名) %s のウェブサーバを起動する: %d (ホスト) s: % (ポート)" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "リモート" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "説明" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "長い説明" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "アクティブ化" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "ポート" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "アドレス" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "ログ" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "サイズ kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "フォルダー" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "ファイルログ" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "カウント" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "ログを回転させる" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "アクセス許可" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "グループ名" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "ダウンロードのユーザーとグループの変更" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "ダウンロードのファイルモードを変更" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "ユーザー名" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Filemode ダウンロード" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "実行中のプロセスのグループの変更" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "フォルダーのアクセス許可モード" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "実行中のプロセスのユーザーの変更" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "標準" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "言語" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "フォルダーをダウンロードします。" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "チェックサムを使用してください。" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "各パッケージのフォルダーを作成します。" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "デバッグ モード" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "分の空き領域 (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "CPU 優先順位" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL証明書" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL キー" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "ウェブインタフェース" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "テンプレート" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "パスプレフィックス" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "サーバー" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "特定のサーバーの好意" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "HTTPS を使用します。" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "開発モード" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "プロキシ" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "プロキシを使用します。" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "パスワード" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "プロトコル" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "再接続" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "終了" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "再接続を使用します。" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "メソッド" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "開始" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "ダウンロード" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "最大並列ダウンロード" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "ダウンロード速度の制限" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "名前 (ip) にバインドするインターフェイスをダウンロードします。" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "既存のファイルをスキップします。" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Kb/秒での最大ダウンロード速度" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "IPv6 を許可します。" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "1 つのダウンロードの最大接続数" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "起動に失敗したダウンロードを再起動します。" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "ダウンロードにかかる時間" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "ダウンロード チャンクへの移行に失敗しました 1 つの接続 |。%s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "追加パッケージ%(name)sをフォルダー %(folder)s として" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "%dリンクをパッケージに追加しました" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "不明なアカウント プラグイン %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "クエリ" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "キャプチャ要求" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Captcha を解決してください。" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "リモート バックエンド エラー: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "開始 %(name)s:%(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr " バックエンド %(name)sの読み込みに失敗した |%(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "なし" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "オフライン" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "オンライン" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "キューに入っています。" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "一時停止" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "完了" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "スキップ" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "失敗しました" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "開始中" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "待機中" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "ダウンロード中" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "一時的にオフライン" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "中止しました" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "復号化" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "処理中" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "カスタム" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "不明" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "パッケージが完了しました: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "ユーザー '%s' にログインしようとしています。" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "終了のシグナルを受信" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad はプロセス ID %s で既に実行中" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "グループの変更に失敗しました: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "ユーザーの変更に失敗しました: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "開始中" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "ホームディレクトリとして %s を使用中" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "すべてのリンクが削除されました" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "ダウンロードにかかる時間: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "空き領域: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "アカウントをアクティベート中..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "失敗したダウンロードを再起動する." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad が実行中" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "pyLoad を再起動中" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad が終了します" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "シャットダウン中..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "シャットダウン中にエラー" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "ターミナルから pyLoad を終了しました" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "データベースが互換性のないバージョンのため削除されました。" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "復号化に失敗しました" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "パッケージ%(name)sにリンク%(count)d復号化しました" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "復号化リンクがありません。" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "%(name)sの失敗の情報取得する |%(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "再接続に失敗しました: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "再接続スクリプトが見つかりません!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "再接続を開始中" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "失敗した実行スクリプト再接続 !" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "再接続、新しい IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "デバイスに空き領域不足" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "ダウンロード開始: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "ダウンロード完了: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "プラグイン %s には関数がありません。" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "ダウンロードを中止: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "ダウンロードを再開: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "ダウンロードがオフライン: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "ダウンロードが一時的にオフライン: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "ダウンロードに失敗しました: %(name)s|%(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "ホストまたは接続のリセットに接続できなかったため、1 分後接続に再試行してください。" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "ダウンロードをスキップしました:%(plugin)sにより %(name)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "内部サーバー エラー" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "エラーが発生しました" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "%(name)s:%(msg)sのインポート エラー" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Js エンジンが検出されると、インストールしてくださいない Spidermonkey、js ossp、pyv8、nodejs またはサイ" + diff --git a/locale/ja/LC_MESSAGES/plugins.po b/locale/ja/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..c20e97f3b --- /dev/null +++ b/locale/ja/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-08-07 09:03-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Japanese\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "ダウンロード チャンクへの移行に失敗しました 1 つの接続 |。%s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil とテッセラクト インストールされていないクライアント接続 captcha 解読" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "適切な時期に取得したキャプチャ結果はありません。" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "ユーザーとグループの設定に失敗しました: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "存在しないファイルまたはサポートされていないプロトコル" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "急流: トラフィックを共有 (ダウンロードによる直販)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "既にこの ip アドレスからダウンロードする、60 秒を待っています。" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "無効な認証コード、ダウンロードが再起動" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: 空きスロット" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "このファイルのプレミアム アカウントを必要があります。" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "無効なファイル名を報告" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "%S アカウントを入力するか、このプラグインを無効にしてください。" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "復号化に失敗しました" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "URL で提供されるファイル キーがありません。" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "エラー コード:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "並列ダウンロード エラー、60秒を待っています。" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "ログインしていません。" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "API キーが無効です。" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s:十分なトラフィックがない" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "トラフィックを超えています" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr " (username:password)承認が必要" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "ファイルが一時的に利用できません。" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: ダウンロード %d 秒間待機しています。" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: キャプチャ %d 秒を待っています。" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "ダウンロードしたファイルが空だった" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "ダウンロードしたファイル (%s). の HTML コードがあったエラーをリダイレクトですか?ダウンロードが再開されます。" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "アカウント%(user)s ではログインできませんでした |%(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "間違ったパスワード" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "%sのアカウント情報を取得します。" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "エラー: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "%s時間、形式が間違っていますを使用して: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "アカウント %s が十分なトラフィックがない、30 分でもう一度チェック" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "アカウント %s は有効期限が切れて、1 h でもう一度チェック" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "%sでログイン" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "アドオンを実行中のエラー: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Bitshare アカウントで直接のダウンロードを有効にします。" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "ダウンロード制限に達しました" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "まず、premium.to アカウントを追加し、pyLoad を再起動してください。" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "%s のインストール スクリプト" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "スクリプトがない実行可能ファイル:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "%(script)s のエラー:%(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s のクレジットを左" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "応答を送信できませんでした。" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "あなたの CaptchaTrader アカウントが十分なクレジット" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "アップロードから新しい CaptchaID: %s: %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "あなたのキャプチャ 9kw.eu アカウントが十分なクレジット" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "まず、rehost.to アカウントを追加し、pyLoad を再起動してください。" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "HotFolder から%s追加 " + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: 使用中のポート 9666" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "パッケージが完了しました: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "ダウンロード完了: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "あなたの ExpertDecoders アカウントが十分なクレジット" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "* * * プラグインが更新されて、pyLoad を再起動してください * * *" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "プラグインの更新し、再読み込み" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "プラグインの更新はありません。" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "PyLoad の更新" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "* * * 新しい pyLoad バージョン %s が利用 * * *" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "* * * それを得るここで: http://pyload.org/download * * *" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "更新サーバーに接続することはできません。" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "新しいバージョンの%(type)s|%(name)s%(version) .2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "%sを更新するときのエラー" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "バージョンの不一致" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "%s がインストールされました" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "%S をアクティブにできませんでした。" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "アクティブ化" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "アクティブ化のプラグインがない" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "パッケージ %s は、抽出後のキュー" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "パッケージ %s をチェック" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "%S を抽出します。" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "ファイルを抽出するが見つかりません" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "展開中" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "パスワードで保護されています" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "正しくないパスワード" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "%s 個のファイルを削除中" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "展開が完了しました" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "アーカイブ エラー" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC が不一致" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "不明なエラー" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "ユーザー設定およびグループに失敗しました" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Crypter リストが見つかりません" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Crypter リストは空です。" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "ダウンロード完了: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "新しいキャプチャ要求: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "'C %s テキストのキャプチャを' と答え" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "最初の premiumize.me の有効なアカウントを追加して、pyLoad を再起動してください。" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d のクレジットを残った" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "アクティブ化 %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "ホスティング サービスが読み込まれていません" + diff --git a/locale/ja/LC_MESSAGES/setup.po b/locale/ja/LC_MESSAGES/setup.po new file mode 100644 index 000000000..0c85dfc40 --- /dev/null +++ b/locale/ja/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-08-07 09:03-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Japanese\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "ウェブインタ フェースを介して pyLoad を構成する希望ですか?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "それのため、ブラウザーとこの PC に接続必要があります。" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "Url になる: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "構成の初期のウェブインタ フェースを開始ですか?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "pyLoad 設定アシスタントにようこそ。" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "それはあなたのシステムをチェックし、基本的なセットアップ pyLoad を実行するために。" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "角かっこ内の値は常に、既定値は" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "それを変更したくない、何を選択する確認が場合ヒットだけを入力します。" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "忘れてはいけない: pyLoadCore を起動すると常にセットアップまたは-s パラメーターでこのアシスタントを再実行することができます。" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "このアシスタントで何か問題がある場合は CTRL + C をヒットします。" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "中止しないでください彼はもはや自動的に pyLoadCore を開始させてください。" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "システム チェックの準備が整ったら、ヒットを入力します。" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "不足している機能:" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr " py-crypto 利用できません" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "コンテナー ファイルの暗号化を解除したい場合この必要があります。" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL を利用できません" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "これはコアまたはウェブインタ フェースへのセキュリティで保護された接続を確立する場合に必要です。" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "PyLoad ssl へのローカル アクセスする場合は使用されません。" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr " Captcha の認識ができません" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Freeuser といくつかのホスティングのためにのみ必要です。" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "JavaScript エンジンが見つかりません" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "必要がありますこれいくつか click 'n の' リンクを読み込みます。サイ pyv8 や ossp js Spidermonkey をインストールします。" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "今、セットアップを中止し、たい場合、いくつかの依存関係を修正できます。" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "セットアップを続けますか。" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "設定パスを変更しますか。現在は %s です。" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "PyLoad を使用して、サーバー上または home パーティションの内部のフラッシュに住んでいる場合良いアイデアそれを変更することがあります。" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "設定パスの変更ですか?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "ログイン データと基本設定を構成しますか。" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "最初の実行をお勧めしますです。" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "基本的なセットアップを行うか?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Ssl を構成しますか。" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Ssl を構成しますか。" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "ウェブインタ フェースを構成しますか。" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "ウェブインタ フェースを構成しますか。" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "セットアップは正常に完了しました。" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "PyLoad を再起動を終了を入力ヒット" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "ウェブインタ フェースのセットアップを実行しています。" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## 基本的なセットアップ ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "次の logindata は CLI と GUI のウェブインタ フェースに有効です。" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "ユーザー名" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "外部クライアント (GUI、CLI または他) リモート アクセス、ネットワーク上で動作する必要があります。" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "ただし、のみ、ウェブインタ フェースを使用する場合は ram を保存して無効にできます。" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "リモート アクセスを有効にします。" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "言語" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "フォルダーをダウンロードします。" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "最大並列ダウンロード" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "再接続を使用します。" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "スクリプトの場所を接続し直します" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## ウェブインタフェースのセットアップ ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "ウェブインタフェースをアクティベートしますか?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "リスン アドレス、127.0.0.1 または localhost、ウェブインタ フェースを使用する場合は、ローカルでのみアクセス可能。" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "アドレス" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "ポート" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad 今、簡単な説明、次のいくつかのサーバー バックエンドを提供しています。" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "既定のサーバーこのサーバー SSL を提供しています、組み込みには良い選択肢です。" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Apache、lighttpd を使用することができます、あまりにも簡単な仕事ではない、それらを構成する必要があります。" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "C で書かれた非常に高速な方法では、libev および linux の知識が必要です。" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "ここからそれを得る: https://github.com/jonashaag/bjoern、それをコンパイル" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "pyload/lib に bjoern.so をコピー" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "注意: いくつかのまれなケースで、組み込みのサーバーが動作しない、ウェブインタ フェースで問題が発生した場合" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "ここに戻ってくるし、ここでスレッドに組み込みサーバーを変更します。" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "サーバー" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL セットアップ ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Ssl 証明書を pyLoad config フォルダーからこれらのコマンドを実行します。" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "完了したら、すべてがうまく行った場合 ssl を今すぐアクティブ化することができます。" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "SSL を有効にするか?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "動作を選択" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - ユーザを作成/編集" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - ユーザ一覧を表示" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - ユーザを削除" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - 終了" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "ユーザー" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "新しい configpath を設定、現在の設定は転送されません !" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "構成パス" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "構成パスが変更されると、セットアップは今近くに行くを再起動してください。" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "終了するには Enter を押してください。" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "設定パスの設定に失敗しました: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "y" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "パスワード: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "パスワードが短すぎます。4 文字以上にしてください。" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "パスワード (再入力): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "パスワードが一致しませんでした。" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "はい" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "true" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "t" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "いいえ" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "false" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "無効な入力" + diff --git a/locale/ja/LC_MESSAGES/webUI.po b/locale/ja/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..f0813985f --- /dev/null +++ b/locale/ja/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Japanese\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "無制限" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "アカウント" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "追加" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "閉じる" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "削除" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "キャンセル" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "送信" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/ko/LC_MESSAGES/cli.po b/locale/ko/LC_MESSAGES/cli.po new file mode 100644 index 000000000..e78edd2d4 --- /dev/null +++ b/locale/ko/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/ko/LC_MESSAGES/core.po b/locale/ko/LC_MESSAGES/core.po new file mode 100644 index 000000000..0f6aff9ac --- /dev/null +++ b/locale/ko/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/ko/LC_MESSAGES/plugins.po b/locale/ko/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..32064ba96 --- /dev/null +++ b/locale/ko/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/ko/LC_MESSAGES/setup.po b/locale/ko/LC_MESSAGES/setup.po new file mode 100644 index 000000000..a56deced8 --- /dev/null +++ b/locale/ko/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/ko/LC_MESSAGES/webUI.po b/locale/ko/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..d8ae6bd5d --- /dev/null +++ b/locale/ko/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/ms/LC_MESSAGES/cli.po b/locale/ms/LC_MESSAGES/cli.po new file mode 100644 index 000000000..c06a52042 --- /dev/null +++ b/locale/ms/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Malay\n" +"Language: ms_MY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/ms/LC_MESSAGES/core.po b/locale/ms/LC_MESSAGES/core.po new file mode 100644 index 000000000..27506eaf7 --- /dev/null +++ b/locale/ms/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Malay\n" +"Language: ms_MY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/ms/LC_MESSAGES/plugins.po b/locale/ms/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..b0750e29a --- /dev/null +++ b/locale/ms/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Malay\n" +"Language: ms_MY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/ms/LC_MESSAGES/setup.po b/locale/ms/LC_MESSAGES/setup.po new file mode 100644 index 000000000..681b445f4 --- /dev/null +++ b/locale/ms/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Malay\n" +"Language: ms_MY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/ms/LC_MESSAGES/webUI.po b/locale/ms/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..4829b73a6 --- /dev/null +++ b/locale/ms/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Malay\n" +"Language: ms_MY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/nl/LC_MESSAGES/cli.po b/locale/nl/LC_MESSAGES/cli.po new file mode 100644 index 000000000..f0848d337 --- /dev/null +++ b/locale/nl/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Pakket toevoegen:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Naam invullen voor nieuw pakket" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Pakket: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Parse de links die je wilt toevoegen." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Typ %s als het klaar is." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Links toegevoegd: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " terug naar hoofdmenu" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Pakketten beheren:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Links beheren:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Wat wil je verplaatsen?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Wat wil je verwijderen?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Wat wil je herstarten?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "verwijderen" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "verplaatsen" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "herstarten" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - vorige" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - volgende" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Downloads:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Snelheid: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Grootte: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Compleet in: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "wachtend: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Status:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "totale Snelheid" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Bestanden in wachtrij" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Totaal" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menu:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Links toevoegen" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "Wachtrij beheren" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Verzamelaar beheren" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " (De)Pauzeren Server" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Server afsluiten" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Afsluiten" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Gebruik deze opbouw: add <Package name> <link> <link2> enz" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Controleren van %d links:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Bestand bestaat niet." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad was afgesloten" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Server status weergeven" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Downloads in wachtrij weergeven" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Downloads in verzamelaar weergeven" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Pakketten toevoegen aan wachtrij" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Pakket toevoegen aan verzamelaar" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Bestanden verwijderen uit Wachtrij/Verzamelaar" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Pakketten verwijderen uit Wachtrij/Verzamelaar" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Verplaats pakketten van Wachtrij naar Verzamelaar of omgekeerd" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Bestanden herstarten" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Pakketten herstarten" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Controleer online status, werkt met lokale container" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Controleert de online status van een container bestand" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Server pauzeren" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "downloads hervatten" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Pauzeren/Starten" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "server afsluiten" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lijst met commands:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Kan configuratiebestand voor gebruiker niet aanmaken" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Om verbinding te maken met deze pyLoad Core is py-openssl nodig." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adres: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Poort: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Gebruikersnaam: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Wachtwoord: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Login gegevens verkeerd." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Kan geen verbinding maken met %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Interactieve modus wordt genegeerd, omdat je commando's hebt ingevult." + diff --git a/locale/nl/LC_MESSAGES/core.po b/locale/nl/LC_MESSAGES/core.po new file mode 100644 index 000000000..d47e7462a --- /dev/null +++ b/locale/nl/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Activeren mislukt van %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Plugins aan het activeren..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL certificaten niet gevonden." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Deze server heeft geen SSL, overweeg de threaded server te gebruiken" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Geactiveerd" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Poort" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adres" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Map" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Gebruikersnaam" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Algemeen" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Taal" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Server" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Wachtwoord" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Download chunks gefaald, terugvallen op één connectie | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr " Fout in achtergrond op afstand: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Opstarten %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Laden mislukt van backend %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "in wachtrij" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "compleet" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "overgeslagen" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "mislukt" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "startend" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "wachtend" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "downloaden" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "tijdelijk offline" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "afgebroken" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "decrypten" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "verwerken" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "persoonlijk" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "onbekend" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Pakket compleet: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Quit signaal ontvangen" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad is al actief met pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Groep wisselen mislukt: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Gebruiker wisselen mislukt: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "starten" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Home map gebruiken: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Alle links verwijderd" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Beschikbare ruimte: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Accounts aan het activeren..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad is al draaiende" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "pyLoad aan het herstarten" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad aan het afsluiten" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "aan het afsluiten..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "fout tijdens afsluiten" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Informatie verzamelen voor %(name)s mislukt | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Opnieuw verbinden mislukt: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Opnieuw verbinden script niet gevonden!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Opnieuw aan het verbinden" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Uitvoeren van opnieuw verbinden script mislukt!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Opnieuw verbonden, nieuw IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Niet genoeg schijfruimte vrij op schijf" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Download gestart: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Download compleet: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Plugin %s mist een functie." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Download afgebroken: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Download herstart: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Download is tijdelijk offline: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Download mislukt: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Kan geen verbinding maken met host, wacht 1 minuut en probeer opnieuw." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Download overgeslagen: %(name)s vanwege %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Fout bij importeren %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/nl/LC_MESSAGES/django.mo b/locale/nl/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index bea13751c..000000000 --- a/locale/nl/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/nl/LC_MESSAGES/plugins.po b/locale/nl/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..4256df98e --- /dev/null +++ b/locale/nl/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Download chunks gefaald, terugvallen op één connectie | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Captcha ontcijfering niet mogelijk : A. Client is niet verbonden & B. Pil en Tesseract module niet gevonden" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Geen captcha resultaat gevonden in de toegestaande tijd." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Instellingen van gebruiker en groep kunnen niet geladen worden : %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Dit bestand of protocol wordt niet ondersteund" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Bandbreedte Delen (Direct Downloaden)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Er wordt al een bestand gedownload vanaf dit ip adres, na 60 seconden volgende poging" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Ongeldige Auth Code, download wordt automatisch herstart" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Geen beschikbaarde downloadslots" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "U heeft een premium account nodig voor deze bestand" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Bestandsnaam geeft ongeldigheidsmelding" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Voer hier uw %s gegevens in of deactiveer deze plugin" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Ontcijferen van codering mislukt" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Geen bestandssleutel meegeleverd door URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Foutmeldingscode:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Gelijktijdige download mislukt, wacht 60 seconden voor volgende poging." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Niet ingelogd." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "API sleutel ongeldig" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Overdracht limiet bijna bereikt , niet voldoende credits" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Dataverkeer overschreden" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Autorisatie vereist (gebruikersnaam:wachtwoord)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Bestand tijdelijk niet beschikbaar" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: wachten tussen downloads %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: Wachten tot captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Downloadlink heeft geen bestand gekoppeld" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "Er is een html reactie gevonden in het aangevraagde bestand om te downloaden(%s), is het een redirect verzoek? Er wordt nu geprobeerd om bestand opnieuw te downloaden." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Kan niet inloggen onder gebruikersnaam %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Verkeerd wachtwoord opgegeven" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Informatie over account van %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "foutmelding : %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Uw tijd %s heeft een verkeerde indeling, gebruik het volgende formaat : 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Account %s heeft niet genoeg credits, wij proberen het opnieuw in 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Account %s is verlopen, opnieuw te controleren in 1h" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Login met %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Fout bij het uitvoeren van addons: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Activeer Direct Downloaden in je Bitshare Account" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Downloadlimiet bereikt" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Gelieve eerst uw premium account toe te voegen en daarna pyLoad te herstarten" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Geïnstalleerde scripts voor %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Script niet uitvoerbaar:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Fout in %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s credits over" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Kan het antwoord niet verzenden." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Uw CaptchaTrader Account heeft niet genoeg credits" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nieuwe CaptchaID van upload: %s: %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Uw Captcha 9kw.eu Account heeft niet genoeg credits" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Gelieve eerst uw premium account toe te voegen en daarna pyLoad te herstarten" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Toegevoegde %s van HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Poort 9666 al in gebruik" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Pakket compleet: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Download compleet: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Uw ExpertDecoders Account heeft niet genoeg credits" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Plugins zijn bijgewerkt, pyLoad opnieuw starten aub***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Plugins bijgewerkt en opnieuw geladen" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Geen plugin updates beschikbaar" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Geen Updates voor pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Nieuwe pyLoad Versie %s beschikbaar ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Download hier: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Niet in staat te verbinden met server voor updates" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Nieuwe versie van %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Fout tijdens updaten van %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Versie mismatch" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Niet %s geinstalleerd" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Kan %s niet activeren" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Geactiveerd" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Geen uitpak plug-ins geactiveerd" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Pakket %s in wachtrij voor later uitpakken" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Controleer pakket %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Uitpakken naar %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Geen bestanden gevonden om uit te pakken" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "uitpakken" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Beveiligd met een wachtwoord" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Verkeerd wachtwoord" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "%s bestanden verwijderen" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Uitpakken voltooid" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Archief fout" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC foutief" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Onbekende fout" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Instellen van gebruikers en de groep is mislukt" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Crypter lijst niet gevonden" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Crypter lijst is leeg" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Download compleet: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Nieuw Captcha verzoek: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Beantwoord met 'c%s text on the captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Graag een valide permiumize.me account toevoegen en daarna pyLoad herstarten." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "uw resterende aantal credits : %d" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Het volgende is geactiveerd : %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Geen Hoster geladen" + diff --git a/locale/nl/LC_MESSAGES/pyLoad.mo b/locale/nl/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 7c52dee71..000000000 --- a/locale/nl/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/nl/LC_MESSAGES/pyLoadCli.mo b/locale/nl/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index c414cfea4..000000000 --- a/locale/nl/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/nl/LC_MESSAGES/pyLoadGui.mo b/locale/nl/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 8bbc9385f..000000000 --- a/locale/nl/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/nl/LC_MESSAGES/setup.mo b/locale/nl/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 8d8fd0d4f..000000000 --- a/locale/nl/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/nl/LC_MESSAGES/setup.po b/locale/nl/LC_MESSAGES/setup.po new file mode 100644 index 000000000..b115a2766 --- /dev/null +++ b/locale/nl/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Welkom bij de pyLoad configuratie assistent." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Deze zal uw systeem controleren en de basissetup uitvoeren." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "De waarden tussen de haakjes [] zijn de standaardwaarden," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "mocht het zo zijn dat u deze niet wil veranderen of indien u niet zeker bent, druk op enter." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Let op: u kunt altijd deze setup herstarten met de --setup of -s parameter als u pyLoadCore start." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "om af te sluiten en de assistent niet meer automatisch te starten." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Als u klaar bent voor de systeemcontrole, druk op enter." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "geen py-crypto beschikbaar" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "U heeft dat nodig om container bestanden te openen." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "geen SSL beschikbaar" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Dit is nodig om een beveiligde verbinding in te stellen naar de core of de webinterface." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "geen captcha herkenning beschikbaar" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Wordt alleen gebruikt bij enkele hosters als freeuser." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "geen JavaScript engine gevonden" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "U heeft dit nodig voor bepaalde Click'N'Load links. Instaleer Spidermoney, ossp-js, pyv8 of rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Doorgaan met setup?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Wilt u de configuratiemap aanpassen? Huidige is %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Verander configmap?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Wilt u login gegevens en basisinstellingen configureren?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Dit is aan te raden bij een eerste start." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Basissetup creëren?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Wilt u SSL configureren?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "SSL configureren?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Wilt u de webinterface configureren?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Webinterface configureren?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Setup is succesvol voltooid." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Druk op enter om af te sluiten en start pyLoad opnieuw" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Basisinstellingen ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "De volgende logingegevens zijn geldig voor CLI, GUI en webinterface." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Gebruikersnaam" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Externe cliënten (GUI, CLI of andere) hebben externe toegang nodig om te werken over het netwerk." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Als je enkel de webinterface wil gebruiken mag je het uitschakelen om RAM te besparen." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Externe toegang inschakelen" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Taal" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Maximale parallele downloads" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Gebruik reconnect?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Reconnect script pad" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Webinterface setup ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Activeer webinterface?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Listen adres, als u 127.0.0.1 of localhost gebruikt is de webinterface alleen lokaal beschikbaar." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adres" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Poort" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad beschikt over een aantal server backends, nu volgt een korte uitleg." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Kan gebruikt worden door apache, lighttpd, vereist configuratie wat niet makkelijk te doen is." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Download het hier: https://github.com/jonashaag/bjoern, compile het" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Opgelet: In sommige gevallen werkt de builtin server niet, als je problemen hebt met de webinterface." + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "kom terug en verander de builtin server naar de threaded server." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Server" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL setup ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Voer de volgende commando's uit vanuit pyLoad configuratiemap om ssl certificaten te maken:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Als de commando's succesvol uitgevoerd zijn kunt u SSL nu activeren." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "SSL activeren?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Aktie selecteren" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Aanmaken/wijzigen gebruiker" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Lijst met gebruikers" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Gebruiker verwijderen" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Afsluiten" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Gebruikers" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Druk op enter om af te sluiten." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Instellen van configuratiepad mislukt: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "j" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Wachtwoord: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Wachtwoord (nogmaals): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Wachtwoord kwam niet overeen." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "ja" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "waar" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "w" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "nee" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "niet waar" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "nw" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Ongeldige invoer" + diff --git a/locale/nl/LC_MESSAGES/webUI.po b/locale/nl/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..dce7b984d --- /dev/null +++ b/locale/nl/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Toevoegen" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Sluiten" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Verwijderen" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Annuleren" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/no/LC_MESSAGES/cli.po b/locale/no/LC_MESSAGES/cli.po new file mode 100644 index 000000000..8ce132724 --- /dev/null +++ b/locale/no/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Norwegian\n" +"Language: no_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/no/LC_MESSAGES/core.po b/locale/no/LC_MESSAGES/core.po new file mode 100644 index 000000000..1e9c35568 --- /dev/null +++ b/locale/no/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Norwegian\n" +"Language: no_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/no/LC_MESSAGES/plugins.po b/locale/no/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..836c2e222 --- /dev/null +++ b/locale/no/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Norwegian\n" +"Language: no_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/no/LC_MESSAGES/setup.po b/locale/no/LC_MESSAGES/setup.po new file mode 100644 index 000000000..86f80a0f6 --- /dev/null +++ b/locale/no/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Norwegian\n" +"Language: no_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/no/LC_MESSAGES/webUI.po b/locale/no/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..a5fdddb5c --- /dev/null +++ b/locale/no/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Norwegian\n" +"Language: no_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/pa/LC_MESSAGES/cli.po b/locale/pa/LC_MESSAGES/cli.po new file mode 100644 index 000000000..4583194c1 --- /dev/null +++ b/locale/pa/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Punjabi\n" +"Language: pa_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/pa/LC_MESSAGES/core.po b/locale/pa/LC_MESSAGES/core.po new file mode 100644 index 000000000..eaee7189d --- /dev/null +++ b/locale/pa/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Punjabi\n" +"Language: pa_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/pa/LC_MESSAGES/plugins.po b/locale/pa/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..a1e26f5b5 --- /dev/null +++ b/locale/pa/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Punjabi\n" +"Language: pa_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/pa/LC_MESSAGES/setup.po b/locale/pa/LC_MESSAGES/setup.po new file mode 100644 index 000000000..2a4b6890e --- /dev/null +++ b/locale/pa/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Punjabi\n" +"Language: pa_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/pa/LC_MESSAGES/webUI.po b/locale/pa/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..b63503415 --- /dev/null +++ b/locale/pa/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Punjabi\n" +"Language: pa_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/pl/LC_MESSAGES/cli.po b/locale/pl/LC_MESSAGES/cli.po new file mode 100644 index 000000000..bf68c90c4 --- /dev/null +++ b/locale/pl/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Polish\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Dodaj pakiet:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Wprowadź nazwę dla nowego pakietu" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Pakiet: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Analizuj linki które chcesz dodać." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Napisz %s gdy ukończone." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Dodanych linków:" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "powrót do głównego menu" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Zarządzali paczkami:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Zarządzaj linkami:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Czy chcesz przenieść?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Czy na pewno chcesz usunąć?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Czy chcesz zrestartować?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Dokonaj wyboru lub podaj numer pakietu." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "skasuj" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "przenieś" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "Restart" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "- poprzedni" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - następny" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "Interfejs Linii Komend" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Pobrań:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Prędkość:" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Rozmiar:" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Zakończono w:" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "ID:" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "Oczekiwanie:" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Status:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "Wstrzymane" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "Aktywne" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Prędkość pobierania" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Pliki w kolejce" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Ogółem" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menu:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "Dodaj linki" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "zarządzaj kolejką" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Zarządzanie poczekalnią linków" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Zatrzymaj/Wznów serwer" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "Wyłącz serwer" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "Wyjście" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Używamy składni: add <Package name> <link> <link2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Sprawdzanie linków %d:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Plik nie istnieje." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad został wyłączony" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Wyświetla status serwera" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Wyświetla pobierane pliki z kolejki" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Wyświetla pobierane pliki z poczekalni" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Dodaje pakiet do kolejki" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Dodaje paczkę do poczekalni" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Usuń Pliki z Kolejki lub Poczekalni" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Usuń Paczki z Kolejki/Poczekalni" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Przenoszenie paczek z kolejki do poczekalni i odwrotnie" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Zrestartuj pliki" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Zrestartuj paczki" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Sprawdź status online, współpracuje z lokalnym kontenerem" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Sprawdź stan online pliku kontenera" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Czasowo wstrzymaj serwer" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "kontynuuj pobieranie" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Przełącz na wstrzymanie/pracę" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "wyłącz serwer" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lista poleceń:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Nie można zapisać pliku konfiguracyjnego" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Do połączenia z pyLoad Core wymagana jest instalacja py-openssl." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adres:" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port:" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Użytkownik:" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Hasło:" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Błędne dane logowania." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Nie można połączyć z %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Tryb interaktywny wyłączono po wprowadzonych komendach." + diff --git a/locale/pl/LC_MESSAGES/core.po b/locale/pl/LC_MESSAGES/core.po new file mode 100644 index 000000000..45a22c7eb --- /dev/null +++ b/locale/pl/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Polish\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Błąd podczas wykonywania %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Nie powiodła się aktywacja %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Aktywne dodatki: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Nieaktywne dodatki: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Włączanie wtyczek ..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Wyłączanie dodatków..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "Nie znaleziono certyfikatów SSL." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "WebUI nie jest dostępny" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Uruchamianie webUI w trybie deweloperskim" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Niepowodzenie przy uruchamianiu webserwera: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Niepowodzenie przy imporcie webserwera: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Ten serwer nie wspiera SSL, należy rozważyć użycie serwera threaded" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Uruchamianie webserwera %(name)s: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Zdalny" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Opis" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Opis szczegółowy" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Aktywowany" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adres" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Dziennik zdarzeń" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Rozmiar w kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Folder" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Plik dziennika zdarzeń" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Licznik" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Rotowanie dziennika zdarzeń" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Uprawnienia" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Nazwa grupy" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Zmiana grupy i użytkownika katalogu do zapisywania plików" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Zmiana trybu uprawnień pobieranych plików" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Nazwa użytkownika" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Uprawnienia pobieranych plików w katalogu" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Zmiana grupy uruchomionych procesów" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Tryb uprawnień folderu" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Zmiana użytkownika uruchomionego procesu" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Ogólne" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Język" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Folder na pobierane pliki" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Sprawdzanie sumy kontrolnej" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Twórz folder dla każdego pobrania" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Tryb debugowania" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Minimalna ilość wolnego miejsca (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Priorytet CPU" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "Certyfikat SSL" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "Klucz SSL" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Interfejs WWW" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Szablon" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Prefiks ścieżki" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Serwer" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Specyficzny folder serwera" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "Używaj HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Tryb deweloperski" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Używaj Proxy" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Hasło" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protokół" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Połącz ponownie" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Zakończ" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Używaj funkcji ponownego łączenia" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Metoda" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Start" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Pobieranie" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Maksymalna liczba równoległych pobrań" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Limit prędkości pobierania" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Powiązanie z interfejsem pobierań (IP lub nazwa)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Pomiń istniejące pliki" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Maksymalna prędkość pobierań w kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "Zezwalaj na IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Maksymalna liczba połączeń dla jednego pobierania" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Uruchom ponowne nieudane pobrania na starcie" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Czas pobierania" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Pobranie fragmentów nie powiodło się, powrót do pojedynczego połączenia | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Dodano pakiet %(name)s jako folder %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Dodano linki %d do pakietu" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Nieznana wtyczka konta %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Zapytanie" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Prośba o podanie captcha" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Proszę wpisać captcha." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Błąd zdalnego zaplecza: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Uruchamiam %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Bład ładowania backendu %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "brak" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "offline" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "online" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "zakolejkowane" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "Wstrzymane" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "zakończono" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "pominięte" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "niepowodzenie" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "rozpoczynam" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "oczekuję" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "pobieranie" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "tymczasowo offline" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "anulowno" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "rozkodowuję" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "przetwarzanie" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "własny" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "nieznany" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paczka ukończona: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Użytkownik '%s' próbuje się zalogować" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Otrzymano sygnał zakończenia" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad jest już uruchomiony - proces %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Niepowodzenie przy zmianie grupy: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Niepowodzenie przy zmianie użytkownika: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Rozpoczynam" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Używam katalogu domowego: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Wszystkie linki zostały usunięte" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Czas pobierania: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Wolne miejsce: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Aktywacja kont ..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Ponowne uruchamianie błędnych pobrań..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad jest uruchomiony i działa" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "restartuję pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad kończy działanie" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "wyłączanie..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "błąd przy wyłączaniu" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "zamknięcie pyLoad z terminala" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Baza danych została skasowana z powodu niekompatybilnej wersji." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Błąd odszyfrowania linków" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Rozszyfrowanie linków %(count)d do pakietu %(name)s" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Nie odszyfrowano linków" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Pobieranie informacji o %(name)s nie powiodło się | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Nieudane ponowne łączenie: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Nie znaleziono skryptu ponownego łączenia!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Uruchamiam ponowne łączenie" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Niepowodzenie przy uruchamianiu skryptu ponownego łączenia!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Ponownie połączony, nowe IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Zbyt mało miejsca na dysku" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Pobieranie rozpocznie się: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Pobieranie zakończono: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Wtyczka %s nie zawiera funkcji." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Pobieranie przerwane: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Pobieram ponownie: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Pobieranie jest wyłączone: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Pobieranie jest tymczasowo niedostępne:% s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Pobieranie nie powiodło się: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Brak połączenia z hostem lub połączenie zostało zresetowane, zaczekaj 1 minutę i spróbuj ponownie." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Pominięto pobieranie: %(name)s z powodu %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Wewnętrzny błąd serwera" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Wystąpił błąd" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Błąd przy imporcie %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Brak silnika js, należy zainstlować Spidermonkey, ossp-js, pyv8, nodejs lub lrhino" + diff --git a/locale/pl/LC_MESSAGES/django.mo b/locale/pl/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index fae662552..000000000 --- a/locale/pl/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/pl/LC_MESSAGES/plugins.po b/locale/pl/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..9d9268fd8 --- /dev/null +++ b/locale/pl/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Polish\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Pobranie fragmentów nie powiodło się, powrót do pojedynczego połączenia | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Nie zainstalowano modułów pil i tesseract oraz brak połączenia z serwisem dekodującym captcha" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Nie podano kodu captcha w odpowiednim czasie." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Ustawienia użytkowników i grup nie powiodło się: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Nie istniejący plik lub nieobsługiwany protokół" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Współdzielenie ruchu (bezpośrednie pobieranie)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Trwa pobieranie spod tego adresu IP. Odczekaj 60 sekund" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Błędny kod autoryzacji. Pobieranie zostanie wznowione" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidshareCom: Brak wolnych slotów" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Ten plik wymaga konta premium" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Zgłoszono nieprawidłową nazwę pliku" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Proszę wejść na konto %s lub wyłączyć wtyczkę" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Odszyfrowywanie nie powiodło się" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "Nie umieszczono plik klucza w adresie URL" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Kod błędu:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Błąd równoległego pobierania, odczekaj 60s." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Nie zalogowany." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "niepoprawny klucz API" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: Pozostało zbyt mało transferu" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Przekroczono transfer" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Wymagana autoryzacja (użytkownik:hasło)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Plik czasowo niedostępny" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: Oczekiwanie pomiędzy pobraniami %d s." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: czekam na captcha %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Pobrany plik był pusty" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "W pobranym pliku (%s) był kod HTML... przekierowanie błędu? Pobieranie zostanie uruchomione ponownie." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Nie można zalogować się na koncie %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Hasło nieprawidłowe" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Pobir=erz informacje o koncie dla %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Błąd: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Format czasu %s jest nieprawidłowy, użyj: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Konto % s generuje zbyt mały ruch, sprawdź ponownie za 30min" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Konto %s wygasło, sprawdź ponownie za 1godz" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Zaloguj się z %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Błąd podczas pracy dodatków: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Uaktywnij pobieranie bezpośrednie w ustawieniach swojego konta Bitshare" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Osiągnięto limit transferu" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Najpierw dodaj swoje konto rehost.to i zrestartuj pyLoad" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Zainstalowane skrypty dla %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Niewykonywalny skrypt:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Błąd w %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "Pozostało %s punktów" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Nie może wysłać odpowiedzi." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Posiadasz zbyt małą ilość punktów na koncie CaptchaTrader" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Nowe CaptchaID z uploadu: %s: %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Twoje konto captcha 9kw.eu nie ma wystarczająco kredytów" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Najpierw dodaj swoje konto rehost.to i zrestartuj pyLoad" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Dodano %s z HotFolder" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Wtyczka Click'N'Load: Port 9666 jest zajęty" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paczka ukończona: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Pobieranie zakończono: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "Twoje konto ExpertDecoders nie ma wystarczająco kredytów" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Wtyczki zostały zaktualizowane, proszę zrestartować pyLoad ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Wtyczki zaktualizowane i przeładowane" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Brak dostępnych aktualizacji wtyczek" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Brak aktualizacji dla pyLoad" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Dostępna nowa wersja %s ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Pobierz stąd: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Brak połączenia z serwerem aktualizacji" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Nowa wersja %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Podczas aktualizacji wystąpił bląd %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Niezgodność wersji" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Nie zainstalowano %s" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Nie można aktywować %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Aktywowany" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Brak aktywnych wtyczek do rozpakowywania plików" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Pakiet %s zakolejkowany do rozpakowania" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Sprawdzanie paczki %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Wypakowano %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Nie znaleziono plików do rozpakowania" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "wypakowuję" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Zabezpieczone hałsem" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Nieprawidłowe hasło" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Usuwanie %s plików" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Wypakowanie zakończone" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Błąd archiwum" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "Nieprawidłowa suma kontrolna CRC" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Wystąpił nieznany błąd" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Ustawienie użytkowników i grup nie powiodło się: %s" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Nie odnaleziono listy Crypter" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Lista Crypter jest pusta" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Pobieranie zakończono: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Nowe żądanie captcha: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "W odpowiedzi użyj 'c %s tekst z captcha'" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Dodaj najpierw ważne konto premiumize.me i zrestartuj pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "Pozostało %s punktów" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Aktywowano %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Nie załadowano Hostera" + diff --git a/locale/pl/LC_MESSAGES/pyLoad.mo b/locale/pl/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 31ea1e162..000000000 --- a/locale/pl/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/pl/LC_MESSAGES/pyLoadCli.mo b/locale/pl/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 565f26e3a..000000000 --- a/locale/pl/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/pl/LC_MESSAGES/pyLoadGui.mo b/locale/pl/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index bbfcb176e..000000000 --- a/locale/pl/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/pl/LC_MESSAGES/setup.mo b/locale/pl/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index c8303602c..000000000 --- a/locale/pl/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/pl/LC_MESSAGES/setup.po b/locale/pl/LC_MESSAGES/setup.po new file mode 100644 index 000000000..0cd5fd848 --- /dev/null +++ b/locale/pl/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Polish\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Czy chcesz skonfigurować pyLoad do współpracy z interfejsem WWW?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Potrzebujesz do tego przeglądarki i połączenia z komputerem." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "Adres URL musi być w postaci: http://hostname:8000 /" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Rozpocząć konfigurację?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Witamy w asystencie konfiguracji pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Dokona sprawdzenia systemu i ustawi podstawowe parametry potrzebne do uruchomienia pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Wartość w nawiasach kwadratowych [] jest zawsze domyślną," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "jeśli nie chcesz zmieniać wartości lub nie masz pewności co wybrać, naciśnij enter." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Nie zapomnij: kiedy uruchamiasz pyLoadCore zawsze możesz ponownie wybrać asystenta dodając parametr --setup lub -s." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Jeśli masz jakiekolwiek problemy z asystentem wciśnij Ctlr-C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "aby go zatrzymać i nie pozwolić mu automatycznie uruchomić się z pyLoadCore. " + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Naciśnij enter jak będziesz gotowy na sprawdzenie systemu." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Niedostępne funkcje: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto jest niedostępny" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Potrzebujesz go jeśli chcesz rozszyfrowywać pliki kontenerowe." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL niedostępny" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Jest to potrzebne jeśli chcesz nawiązywać szyfrowane połączenia z Core lub interfejsem Web." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Jeśli chcesz mieć dostęp tylko lokalny - SSL nie jest użyteczne." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "brak rozpoznawania captcha" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Potrzebne tylko do niektórych serwisów dla kont darmowych." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "nie znaleziono silnika JavaScript" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Do dodania kilku linków jednocześnie będziesz potrzebował Click'N'Load. Zainstaluj SpiderMonkey, ossp-js, pyv8 lub rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Możesz przerwać instalację i naprawić niektóre zależności." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Kontynuować instalację?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Czy chcesz zmienić ścieżkę do plików konfiguracji? Obecnie to %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Jeśli używasz pyLoad'a na serwerze lub partycji home która znajduje się na pamięci flash - dobrym pomysłem może być zmiana tego parametru." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Zmienić ścieżkę dla plików konfiguracji?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Czy chcesz dokonać konfiguracji logowania i ustawień podstawowych?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Jest to wymagane przy pierwszym uruchomieniu." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Stworzyć podstawowe ustawienia?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Czy chcesz ustawić SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Ustawić SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Czy chcesz ustawić interfejs Web?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Ustawić interfejs Web?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Instalacja zakończona pomyślnie." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Naciśnij enter aby wyjść i ponownie uruchom pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Została uruchomiona instalacja." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Podstawowe Ustawienia ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Następujące parametry logowania są odpowiednie dla CLI, GUI i interfejsu Web." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Nazwa użytkownika" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Klienty zewnętrzne (GUI, CLI lub inne) potrzebują zdalnego połączenia do działania przez sieć." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Jeżeli chcesz używać tylko interfejsu web możesz dezaktywować to aby zaoszczędzić pamięć." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Włącz zdalny dostęp" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Język" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Folder na pobierane pliki" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Maksymalna liczba jednoczesnych pobrań" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Używać ponownego łączenia?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Lokalizacja skryptu do ponownego łączenia" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Ustawienia interfejsu Web ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Aktywować interfejs Web?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Adres do nasłuchu, jeśli użyjesz 127.0.0.1 lub localhost, interfejs Web będzie dostępny jedynie lokalnie." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adres" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad oferuje kilka typów serwerów backends, a teraz po krótce wyjaśniam." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Domyślny serwer, ten serwer oferuje SSL i jest dobrą alternatywą dla wbudowanego." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Może być używany przez apache, lighttpd, wymaga od Ciebie ich konfiguracji, która nie jest zbyt łatwym zadaniem." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Bardzo szybka alternatywa, napisany w C, wymaga libev i znajomości Linuxa." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Pobierz go stąd: https://github.com/jonashaag/bjoern, i skompiluj go" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "i skopiować bjoern.so do pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Uwaga: W pewnych, rzadkich przypadkach serwer builtin nie działa, jeśli występują problemy z interfejsem WWW" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "wróć tu i zmień serwer builtin na threaded" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Serwer" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Ustawienia SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "W celu wygenerowania certyfikatów ssl uruchom następujące komendy z katalogu pyload:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Jeśli wszystko pomyślnie się zakończyło, możesz aktywować SSL." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Aktywować SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Wybierz działanie" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1- Utwórz/Edutuj użytkownika" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2- Pokaż użytkowników" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Usuń użytkownika" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4- Wyjście" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Użytkownicy" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Ustawiam nową ścieżkę do plików konfiguracji, obecna konfiguracja nie zostanie tam przeniesiona!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Ścieżka konfiguracji" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Ścieżka do plików konfiguracji została zmieniona, Instalator zostanie teraz zamknięty, uruchom go ponownie, aby przejść dalej." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Naciśnij Enter aby zakończyć." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Ustawienie ścieżki do plików konfiguracji nie powiodło się: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "t" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "n" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Hasło:" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Hasło za krótkie. Użyj przynajmniej 4 znaków." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Hasło (ponownie):" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Hasła do siebie nie pasują." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "tak" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "prawda" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "t" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "nie" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "fałsz" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "f" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Błędne dane" + diff --git a/locale/pl/LC_MESSAGES/webUI.po b/locale/pl/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..e38891d40 --- /dev/null +++ b/locale/pl/LC_MESSAGES/webUI.po @@ -0,0 +1,133 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Polish\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "niedostępny" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "Bez limitu" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Administrator" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Ustawienia" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Dodaj konto" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Konta" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Lokalny" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Szukaj" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Typ" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Wszystkie" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Zakończono" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Niedokończono" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Niepowodzenie" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 pakiet" +msgstr[1] "%d pakiety(ów)" +msgstr[2] "pakiety: %d" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 plik" +msgstr[1] "plików: %d" +msgstr[2] "plików: %d" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Dodaj konto" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Wprowadź dane swojego konta" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Wybierz wtyczkę" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Wybierz wtyczkę, którą chcesz skonfigurować" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Dodaj" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Zamknij" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Proszę potwierdzić" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Czy chcesz usunąć wybrane elementy?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Usuń" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Anuluj" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Wyślij" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "W trakcie działania..." + diff --git a/locale/plugins.pot b/locale/plugins.pot new file mode 100644 index 000000000..8ef8c07d9 --- /dev/null +++ b/locale/plugins.pot @@ -0,0 +1,431 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR pyLoad Team +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: pyload 0.4.9.9-dev\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-10-13 18:16+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: pyload/plugins/network/CurlDownload.py:245 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:327 +msgid "" +"Pil and tesseract not installed and no Client connected for captcha " +"decrypting" +msgstr "" + +#: pyload/plugins/Base.py:331 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:260 pyload/plugins/Hoster.py:297 +#: pyload/plugins/hoster/ARD.py:79 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:170 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/Crypter.py:228 +#, python-format +msgid "Could not remove file '%s'" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:102 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:129 +#: pyload/plugins/hoster/RapidshareCom.py:197 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:133 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:202 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:205 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:207 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:45 +#: pyload/plugins/hoster/Premium4Me.py:29 +#: pyload/plugins/hoster/MultiDebridCom.py:44 +#: pyload/plugins/hoster/FastixRu.py:38 pyload/plugins/hoster/ZeveraCom.py:25 +#: pyload/plugins/hoster/UnrestrictLi.py:57 +#: pyload/plugins/hoster/SimplydebridCom.py:28 +#: pyload/plugins/hoster/AlldebridCom.py:41 +#: pyload/plugins/hoster/RehostTo.py:26 pyload/plugins/hoster/ReloadCc.py:24 +#: pyload/plugins/hoster/DebridItaliaCom.py:43 +#: pyload/plugins/hoster/PremiumizeMe.py:26 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:56 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:106 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:118 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:99 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:215 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:159 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:162 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:64 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:145 +#: pyload/plugins/hoster/NetloadIn.py:169 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:182 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:213 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:251 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:99 +#, python-format +msgid "" +"There was HTML Code in the Downloaded File (%s)...redirect error? The " +"Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:77 +#: pyload/plugins/hoster/XHamsterCom.py:86 +#: pyload/plugins/hoster/XHamsterCom.py:89 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:140 pyload/plugins/Account.py:146 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:141 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:202 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:211 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:263 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:286 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:294 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:311 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:37 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:130 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:68 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:78 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:69 +#: pyload/plugins/addons/Captcha9kw.py:58 +#: pyload/plugins/addons/ExpertDecoders.py:50 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:117 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:135 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:92 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:128 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:32 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:75 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:83 +#: pyload/plugins/addons/IRCInterface.py:74 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:91 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:95 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:74 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:76 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:79 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:96 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:93 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:95 +#: pyload/plugins/addons/ExtractArchive.py:100 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:105 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:107 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:119 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:142 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:198 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:205 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:216 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:237 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:245 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:252 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:258 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:264 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:316 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:37 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:51 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:96 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" diff --git a/locale/pt/LC_MESSAGES/cli.po b/locale/pt/LC_MESSAGES/cli.po new file mode 100644 index 000000000..bf7c48044 --- /dev/null +++ b/locale/pt/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Portuguese, Brazilian\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Interface da Linha de Comandos" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Velocidade: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Tamanho: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Terminado em: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "a aguardar: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Adicionar Links" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Gerir Fila" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Gerir Coletor" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Parar/Retomar servidor" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Terminar Servidor" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Sair" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Colocar servidor em Pausa" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "continuar downloads" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "terminar servidor" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lista de comandos:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/pt/LC_MESSAGES/core.po b/locale/pt/LC_MESSAGES/core.po new file mode 100644 index 000000000..43af8ab2b --- /dev/null +++ b/locale/pt/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Portuguese, Brazilian\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/pt/LC_MESSAGES/plugins.po b/locale/pt/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..a4ccdf5da --- /dev/null +++ b/locale/pt/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Portuguese, Brazilian\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/pt/LC_MESSAGES/setup.po b/locale/pt/LC_MESSAGES/setup.po new file mode 100644 index 000000000..d016aaa01 --- /dev/null +++ b/locale/pt/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Portuguese, Brazilian\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/pt/LC_MESSAGES/webUI.po b/locale/pt/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..dca60e458 --- /dev/null +++ b/locale/pt/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Portuguese, Brazilian\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/pt_BR/LC_MESSAGES/django.mo b/locale/pt_BR/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 2fdf540b0..000000000 --- a/locale/pt_BR/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/pt_BR/LC_MESSAGES/pyLoad.mo b/locale/pt_BR/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 2e8a1e5b3..000000000 --- a/locale/pt_BR/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo b/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 2ca834196..000000000 --- a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo b/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 28fe26252..000000000 --- a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/pt_BR/LC_MESSAGES/setup.mo b/locale/pt_BR/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index c64d0f23c..000000000 --- a/locale/pt_BR/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/ro/LC_MESSAGES/cli.po b/locale/ro/LC_MESSAGES/cli.po new file mode 100644 index 000000000..08478832f --- /dev/null +++ b/locale/ro/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Romanian\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/ro/LC_MESSAGES/core.po b/locale/ro/LC_MESSAGES/core.po new file mode 100644 index 000000000..e47d1c0e3 --- /dev/null +++ b/locale/ro/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Romanian\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/ro/LC_MESSAGES/plugins.po b/locale/ro/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..7ab9a55b9 --- /dev/null +++ b/locale/ro/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Romanian\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/ro/LC_MESSAGES/pyLoad.mo b/locale/ro/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 2c20cfbd4..000000000 --- a/locale/ro/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/ro/LC_MESSAGES/pyLoadCli.mo b/locale/ro/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 1ae24953f..000000000 --- a/locale/ro/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/ro/LC_MESSAGES/pyLoadGui.mo b/locale/ro/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 1ae24953f..000000000 --- a/locale/ro/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/ro/LC_MESSAGES/setup.po b/locale/ro/LC_MESSAGES/setup.po new file mode 100644 index 000000000..b165a12ee --- /dev/null +++ b/locale/ro/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Romanian\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/ro/LC_MESSAGES/webUI.po b/locale/ro/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..f579c139e --- /dev/null +++ b/locale/ro/LC_MESSAGES/webUI.po @@ -0,0 +1,133 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Romanian\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/ru/LC_MESSAGES/cli.po b/locale/ru/LC_MESSAGES/cli.po new file mode 100644 index 000000000..1ce4880c3 --- /dev/null +++ b/locale/ru/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Russian\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Прибавить пакет:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Введите название пакета" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Контейнеры: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Анализ ссылок для добавления." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Введите %s когда будете готовы." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Добавлены ссылки: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "назад в главное меню" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Координировать пакеты:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Управление ссылками" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Что Вы хотите переместить?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Что Вы хотите удалить?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Что Вы хотите перестартовать?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "удалять" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "перемещать" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "перезапуск" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "- предыдущий" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "- следующий" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "Интерфейс командной строки" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s загрузок:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Скорость: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Размер: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Закончено в: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "ожидание: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Меню:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "Добавить ссылки" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "Kоординировать очередь" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Координировать коллектор ссылок" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Запустить/Приостановить сервер" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "Остановить сервер" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "Выход" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Используйте шаблон: add <Имя контейнера> <ссылка1> <ссылка2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Проверка %d ссылки:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Файл не найден." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "Работа pyLoad была завершена" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Показать состояние сервера" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Показать закачки в очереди" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Показать закачки в списке" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Добавить контейнер в очередь" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Добавляет пакет к коллектору ссылок" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Удалить файлы из очереди/списка" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Удалить контейнер из очереди/списка" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Передвигает пакет из очереди в коллектор и наоборот" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Перезапустить файлы" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Перезапустить пакеты" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Проверка статуса связи, действует с местными контейнерами" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Приостановить сервер" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "продолжить закачки" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Переключить пауза/продолжить" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "остановить сервер" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Список команд: " + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Записать файл конфигурации пользователя не удалось" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Для соединения с ядром pyLoad необходим пакет py-openssl." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Адрес: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Порт: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Имя пользователя: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Пароль: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Неправильные учётные данные." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Невозможно подключиться к %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "В связи с тем, что были введены ряд команд, интерактивный режим был прерван." + diff --git a/locale/ru/LC_MESSAGES/core.po b/locale/ru/LC_MESSAGES/core.po new file mode 100644 index 000000000..71c7a2cd0 --- /dev/null +++ b/locale/ru/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Russian\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Ошибка активации %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Активация плагина..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL сертификаты не найдены." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Активирован(a/о)" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Порт" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Адрес" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Папка" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Пользователь" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Основные" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Язык" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Сервер" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Пароль" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Загрузка параллельных потоков данных завершена с ошибками, возврат к однопоточной загрузке | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Ошибка удаленного сервиса: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Запуск %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Ошибка при загрузки %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "оф-лайн" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "он-лайн" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "в очереди" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "закончено" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "пропущено" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "завершено с ошибкой" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "запускается" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "ожидание" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "загружается" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "временно недоступно" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "прервано" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "раскодирование" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "обрабатывается" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "пользовательский" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "неизвестно" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Пакет завершен: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Получен сигнал для завершения работы" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad уже запущен, pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Не удалось изменить группу: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Не удалось изменить пользователя: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Запускается" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Основная папка: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Все ссылки удалены" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Свободное место: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Активация аккаунта..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "перезапуск pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "завершение работы pyLoad" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "завершение работы..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "ошибка при завершении работы" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Получение информации по %(name)s завершилось ошибкой | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Переподключение не удалось: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Скрипт переподключения не найден!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Начинаем переподключение" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Ошибка при выполнении скрипта переподключения" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Переподключено, новый IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Не хватает места на устройстве." + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Загрузка начата: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Загрузка завершена: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "В плагине %s нет функции." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Загрузка прервана: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Ошибка загрузки: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Загрузка недоступна: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Загрузка временно недоступна: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Ошибка загрузки: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Невозможно соединиться с провайдером или срыв соединения, следующая попытка через одну минуту." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Загрузка пропущена: %(name)s @ %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Ошибка импорта %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/ru/LC_MESSAGES/django.mo b/locale/ru/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 0a1394f21..000000000 --- a/locale/ru/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/ru/LC_MESSAGES/plugins.po b/locale/ru/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..95d090260 --- /dev/null +++ b/locale/ru/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Russian\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Загрузка параллельных потоков данных завершена с ошибками, возврат к однопоточной загрузке | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Пакет завершен: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Активирован(a/о)" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/ru/LC_MESSAGES/pyLoad.mo b/locale/ru/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 156a1bc44..000000000 --- a/locale/ru/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/ru/LC_MESSAGES/pyLoadCli.mo b/locale/ru/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index adbb5e141..000000000 --- a/locale/ru/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/ru/LC_MESSAGES/pyLoadGui.mo b/locale/ru/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 11d4328c0..000000000 --- a/locale/ru/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/ru/LC_MESSAGES/setup.mo b/locale/ru/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index fb1201628..000000000 --- a/locale/ru/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/ru/LC_MESSAGES/setup.po b/locale/ru/LC_MESSAGES/setup.po new file mode 100644 index 000000000..5cc69579c --- /dev/null +++ b/locale/ru/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:56-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Russian\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Вас приветствует помощник по настройке pyLoad" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Будет проверена система и внесены первоначальные настройки для запуска pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "В квадратных скобках [] указываются значения по умолчанию," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "если Вы не хотите менять эти значения или не уверены в своём выборе, просто нажмите ENTER." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Не забудьте: Вы всегда можете снова запустить помощника по настройкам, набрав pyLoadCore с ключом --setup или -s." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "для прекращения установки. pyLoadCore больше не будет запускаться автоматически." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Для старта проверки системы, нажмите ENTER." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "недоступен py-crypto" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Он нужен для раскодирования файлов." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "нет SSL" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Он нужен для защиты соединения с ядром или web-интерфейсом." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "Расшифровка Captcha недоступна" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Нужна на некоторых файл-хостингах." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "Нет поддержки JavaScript" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Ето понадобиться для некоторых ссылок Click'N'Load. Инсталировайте Spidermonkey, ossp-js, pyv8 или rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Продолжить настройку?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Нужно ли менять путь к папке настроек? Текущий путь %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Изменить путь?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Хотите изменить учётные данные и другие базовые настройки?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Настоятельно рекомендуется при первом запуске." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Изменить основные настройки?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Хотите настроить SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Настроить SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Хотите настроить WEB-интерфейс?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Настроить WEB-интерфейс?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Установка успешно завершена." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Нажмите ENTER и запустите pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Основные настройки ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Эти учётные данные подходят к CLI, GUI и WEB-интерфейсу." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Пользователь" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Внешним клиентам (GUI, CLI и другие) понадобиться дистанционный доступ для работы через сеть." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Если Вы хотите пользоваться только соединением через веб интерфейс, отключите его, чтобы уменьшить потребление оперативной памяти." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Включить дистанционный доступ" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Язык" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Макс.число одновременных закачек" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Использовать переподключение интернета?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Путь к скрипту переподключения" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Установки WEB-интерфейса ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Включить WEB-интерфейс?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "ip-адрес WEB-интерфейса. Если указать 127.0.0.1 или localhost, то WEB-интерфейс будет доступен только локально." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Адрес" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Порт" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "В pyLoad несколько вложенных серверов, ниже следуют описания в коротком виде." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Можно пользоваться при apache, lighttpd, требует настройки, которая не простая работа." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Сервер" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Настройки SSL ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Для генерации SSL сертификатов выполните следующие команды, находясь в папке конфигурации pyLoad:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Если всё прошло успешно, сейчас можно будет включить поддержку SSL." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Включить SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Выберите действие" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Добавить/Изменить пользователя" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Показать список пользователей" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Удалить пользователя" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Выход" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Пользователи" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Нажмите клавишу Enter для выхода." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Не удалось установить путь к настройкам: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "да" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "нет" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Пароль: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Пароль (Повторить)" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Пароли не совпадают." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Некорректный ввод" + diff --git a/locale/ru/LC_MESSAGES/webUI.po b/locale/ru/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..3525f7ca8 --- /dev/null +++ b/locale/ru/LC_MESSAGES/webUI.po @@ -0,0 +1,133 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Russian\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "безграничный" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Аккаунты" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Добавить" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Закрыть" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Удалить" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Отменить" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Отправить" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/setup.pot b/locale/setup.pot index cf4dd8cfc..c9ace7bd5 100644 --- a/locale/setup.pot +++ b/locale/setup.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: pyLoad 0.4.9\n" +"Project-Id-Version: pyload 0.4.9.9-dev\n" "Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" -"POT-Creation-Date: 2011-12-07 19:21+0100\n" +"POT-Creation-Date: 2013-10-13 18:16+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -17,479 +17,305 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: module/setup.py:51 -msgid "y" -msgstr "" - -#: module/setup.py:53 -msgid "n" -msgstr "" - -#: module/setup.py:72 +#: pyload/setup/Setup.py:118 msgid "Welcome to the pyLoad Configuration Assistent." msgstr "" -#: module/setup.py:73 +#: pyload/setup/Setup.py:119 msgid "" "It will check your system and make a basic setup in order to run pyLoad." msgstr "" -#: module/setup.py:75 +#: pyload/setup/Setup.py:121 msgid "The value in brackets [] always is the default value," msgstr "" -#: module/setup.py:76 +#: pyload/setup/Setup.py:122 msgid "" "in case you don't want to change it or you are unsure what to choose, just " "hit enter." msgstr "" -#: module/setup.py:77 +#: pyload/setup/Setup.py:124 msgid "" "Don't forget: You can always rerun this assistent with --setup or -s " "parameter, when you start pyLoadCore." msgstr "" -#: module/setup.py:78 -msgid "If you have any problems with this assistent hit STRG-C," +#: pyload/setup/Setup.py:125 +msgid "If you have any problems with this assistent hit CTRL+C," msgstr "" -#: module/setup.py:79 +#: pyload/setup/Setup.py:126 msgid "to abort and don't let him start with pyLoadCore automatically anymore." msgstr "" -#: module/setup.py:81 +#: pyload/setup/Setup.py:128 msgid "When you are ready for system check, hit enter." msgstr "" -#: module/setup.py:88 -msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad." -msgstr "" - -#: module/setup.py:89 -msgid "Please correct this and re-run pyLoad." -msgstr "" - -#: module/setup.py:90 -msgid "Setup will now close." -msgstr "" - -#: module/setup.py:94 -msgid "System check finished, hit enter to see your status report." -msgstr "" - -#: module/setup.py:96 -msgid "## Status ##" -msgstr "" - -#: module/setup.py:101 -msgid "container decrypting" -msgstr "" - -#: module/setup.py:102 -msgid "ssl connection" -msgstr "" - -#: module/setup.py:103 -msgid "automatic captcha decryption" -msgstr "" - -#: module/setup.py:104 -msgid "GUI" -msgstr "" - -#: module/setup.py:105 -msgid "Webinterface" -msgstr "" - -#: module/setup.py:106 -msgid "extended Click'N'Load" -msgstr "" - -#: module/setup.py:113 -msgid "Features available:" -msgstr "" - -#: module/setup.py:117 -msgid "Featues missing: " -msgstr "" - -#: module/setup.py:121 -msgid "no py-crypto available" -msgstr "" - -#: module/setup.py:122 -msgid "You need this if you want to decrypt container files." -msgstr "" - -#: module/setup.py:126 -msgid "no SSL available" -msgstr "" - -#: module/setup.py:127 -msgid "" -"This is needed if you want to establish a secure connection to core or " -"webinterface." -msgstr "" - -#: module/setup.py:128 -msgid "If you only want to access locally to pyLoad ssl is not usefull." -msgstr "" - -#: module/setup.py:132 -msgid "no Captcha Recognition available" -msgstr "" - -#: module/setup.py:133 -msgid "Only needed for some hosters and as freeuser." -msgstr "" - -#: module/setup.py:137 -msgid "Gui not available" -msgstr "" - -#: module/setup.py:138 -msgid "The Graphical User Interface." -msgstr "" - -#: module/setup.py:142 -msgid "no JavaScript engine found" -msgstr "" - -#: module/setup.py:143 -msgid "" -"You will need this for some Click'N'Load links. Install Spidermonkey, ossp-" -"js, pyv8 or rhino" -msgstr "" - -#: module/setup.py:145 -msgid "You can abort the setup now and fix some dependicies if you want." -msgstr "" - -#: module/setup.py:147 +#: pyload/setup/Setup.py:134 msgid "Continue with setup?" msgstr "" -#: module/setup.py:153 +#: pyload/setup/Setup.py:140 #, python-format msgid "Do you want to change the config path? Current is %s" msgstr "" -#: module/setup.py:154 +#: pyload/setup/Setup.py:142 msgid "" -"If you use pyLoad on a server or the home partition lives on an iternal " +"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." msgstr "" -#: module/setup.py:155 +#: pyload/setup/Setup.py:143 msgid "Change config path?" msgstr "" -#: module/setup.py:162 +#: pyload/setup/Setup.py:149 msgid "Do you want to configure login data and basic settings?" msgstr "" -#: module/setup.py:163 +#: pyload/setup/Setup.py:150 msgid "This is recommend for first run." msgstr "" -#: module/setup.py:164 +#: pyload/setup/Setup.py:151 msgid "Make basic setup?" msgstr "" -#: module/setup.py:171 +#: pyload/setup/Setup.py:158 msgid "Do you want to configure ssl?" msgstr "" -#: module/setup.py:172 +#: pyload/setup/Setup.py:159 msgid "Configure ssl?" msgstr "" -#: module/setup.py:178 +#: pyload/setup/Setup.py:164 msgid "Do you want to configure webinterface?" msgstr "" -#: module/setup.py:179 +#: pyload/setup/Setup.py:165 msgid "Configure webinterface?" msgstr "" -#: module/setup.py:184 +#: pyload/setup/Setup.py:170 msgid "Setup finished successfully." msgstr "" -#: module/setup.py:185 +#: pyload/setup/Setup.py:171 msgid "Hit enter to exit and restart pyLoad" msgstr "" -#: module/setup.py:191 -msgid "## System Check ##" -msgstr "" - -#: module/setup.py:196 -msgid "Your python version is to new, Please use Python 2.6/2.7" -msgstr "" - -#: module/setup.py:199 -msgid "Your python version is to old, Please use at least Python 2.5" -msgstr "" - -#: module/setup.py:202 -msgid "Python Version: OK" -msgstr "" - -#: module/setup.py:249 -#, python-format -msgid "Your installed jinja2 version %s seems too old." -msgstr "" - -#: module/setup.py:250 -msgid "You can safely continue but if the webinterface is not working," -msgstr "" - -#: module/setup.py:251 -msgid "" -"please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary." -msgstr "" - -#: module/setup.py:268 -msgid "JS engine" -msgstr "" - -#: module/setup.py:274 +#: pyload/setup/Setup.py:178 msgid "## Basic Setup ##" msgstr "" -#: module/setup.py:277 +#: pyload/setup/Setup.py:181 msgid "The following logindata is valid for CLI, GUI and webinterface." msgstr "" -#: module/setup.py:282 module/setup.py:371 module/setup.py:387 +#: pyload/setup/Setup.py:187 pyload/setup/Setup.py:265 +#: pyload/setup/Setup.py:279 msgid "Username" msgstr "" -#: module/setup.py:288 -msgid "" -"External clients (GUI, CLI or other) need remote access to work over the " -"network." -msgstr "" - -#: module/setup.py:289 -msgid "" -"However, if you only want to use the webinterface you may disable it to save " -"ram." -msgstr "" - -#: module/setup.py:290 -msgid "Enable remote access" -msgstr "" - -#: module/setup.py:295 +#: pyload/setup/Setup.py:194 msgid "Language" msgstr "" -#: module/setup.py:298 -msgid "Downloadfolder" +#: pyload/setup/Setup.py:196 +msgid "Download folder" msgstr "" -#: module/setup.py:299 +#: pyload/setup/Setup.py:197 msgid "Max parallel downloads" msgstr "" -#: module/setup.py:303 +#: pyload/setup/Setup.py:199 msgid "Use Reconnect?" msgstr "" -#: module/setup.py:306 +#: pyload/setup/Setup.py:202 msgid "Reconnect script location" msgstr "" -#: module/setup.py:311 +#: pyload/setup/Setup.py:207 msgid "## Webinterface Setup ##" msgstr "" -#: module/setup.py:314 +#: pyload/setup/Setup.py:210 msgid "Activate webinterface?" msgstr "" -#: module/setup.py:316 +#: pyload/setup/Setup.py:212 msgid "" "Listen address, if you use 127.0.0.1 or localhost, the webinterface will " "only accessible locally." msgstr "" -#: module/setup.py:317 +#: pyload/setup/Setup.py:213 msgid "Address" msgstr "" -#: module/setup.py:318 +#: pyload/setup/Setup.py:214 msgid "Port" msgstr "" -#: module/setup.py:320 +#: pyload/setup/Setup.py:216 msgid "" "pyLoad offers several server backends, now following a short explanation." msgstr "" -#: module/setup.py:321 -msgid "Default server, best choice if you dont know which one to choose." -msgstr "" - -#: module/setup.py:322 -msgid "This server offers SSL and is a good alternative to builtin." +#: pyload/setup/Setup.py:217 +msgid "" +"Default server, this server offers SSL and is a good alternative to builtin." msgstr "" -#: module/setup.py:323 +#: pyload/setup/Setup.py:219 msgid "" "Can be used by apache, lighttpd, requires you to configure them, which is " "not too easy job." msgstr "" -#: module/setup.py:324 -msgid "Very fast alternative written in C, requires libev and linux knowlegde." +#: pyload/setup/Setup.py:220 +msgid "Very fast alternative written in C, requires libev and linux knowledge." msgstr "" -#: module/setup.py:325 +#: pyload/setup/Setup.py:221 msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" msgstr "" -#: module/setup.py:326 -msgid "and copy bjoern.so to module/lib" +#: pyload/setup/Setup.py:222 +msgid "and copy bjoern.so to pyload/lib" msgstr "" -#: module/setup.py:329 +#: pyload/setup/Setup.py:226 msgid "" "Attention: In some rare cases the builtin server is not working, if you " "notice problems with the webinterface" msgstr "" -#: module/setup.py:330 +#: pyload/setup/Setup.py:227 msgid "come back here and change the builtin server to the threaded one here." msgstr "" -#: module/setup.py:333 +#: pyload/setup/Setup.py:229 msgid "Server" msgstr "" -#: module/setup.py:337 +#: pyload/setup/Setup.py:234 msgid "## SSL Setup ##" msgstr "" -#: module/setup.py:339 +#: pyload/setup/Setup.py:236 msgid "" "Execute these commands from pyLoad config folder to make ssl certificates:" msgstr "" -#: module/setup.py:345 +#: pyload/setup/Setup.py:242 msgid "If you're done and everything went fine, you can activate ssl now." msgstr "" -#: module/setup.py:347 +#: pyload/setup/Setup.py:243 msgid "Activate SSL?" msgstr "" -#: module/setup.py:361 +#: pyload/setup/Setup.py:255 msgid "Select action" msgstr "" -#: module/setup.py:362 +#: pyload/setup/Setup.py:256 msgid "1 - Create/Edit user" msgstr "" -#: module/setup.py:363 +#: pyload/setup/Setup.py:257 msgid "2 - List users" msgstr "" -#: module/setup.py:364 +#: pyload/setup/Setup.py:258 msgid "3 - Remove user" msgstr "" -#: module/setup.py:365 +#: pyload/setup/Setup.py:259 msgid "4 - Quit" msgstr "" -#: module/setup.py:377 +#: pyload/setup/Setup.py:270 msgid "Users" msgstr "" -#: module/setup.py:406 -msgid "Setting new configpath, current configuration will not be transfered!" +#: pyload/setup/Setup.py:318 +msgid "Setting new configpath, current configuration will not be transferred!" msgstr "" -#: module/setup.py:407 -msgid "Configpath" +#: pyload/setup/Setup.py:319 +msgid "Config path" msgstr "" -#: module/setup.py:415 -msgid "Configpath changed, setup will now close, please restart to go on." +#: pyload/setup/Setup.py:327 +msgid "Config path changed, setup will now close, please restart to go on." msgstr "" -#: module/setup.py:416 +#: pyload/setup/Setup.py:328 msgid "Press Enter to exit." msgstr "" -#: module/setup.py:420 +#: pyload/setup/Setup.py:332 #, python-format msgid "Setting config path failed: %s" msgstr "" -#: module/setup.py:425 -#, python-format -msgid "%s: OK" -msgstr "" - -#: module/setup.py:427 -#, python-format -msgid "%s: missing" -msgstr "" - -#: module/setup.py:456 -msgid "[y]/n" +#: pyload/setup/Setup.py:343 +msgid "y" msgstr "" -#: module/setup.py:458 -msgid "y/[n]" +#: pyload/setup/Setup.py:345 +msgid "n" msgstr "" -#: module/setup.py:470 +#: pyload/setup/Setup.py:369 msgid "Password: " msgstr "" -#: module/setup.py:475 -msgid "Password to short. Use at least 4 symbols." +#: pyload/setup/Setup.py:373 +msgid "Password too short. Use at least 4 symbols." msgstr "" -#: module/setup.py:481 +#: pyload/setup/Setup.py:376 msgid "Password (again): " msgstr "" -#: module/setup.py:488 +#: pyload/setup/Setup.py:382 msgid "Passwords did not match." msgstr "" -#: module/setup.py:499 +#: pyload/setup/Setup.py:393 msgid "yes" msgstr "" -#: module/setup.py:499 +#: pyload/setup/Setup.py:393 msgid "true" msgstr "" -#: module/setup.py:499 +#: pyload/setup/Setup.py:393 msgid "t" msgstr "" -#: module/setup.py:502 +#: pyload/setup/Setup.py:396 msgid "no" msgstr "" -#: module/setup.py:502 +#: pyload/setup/Setup.py:396 msgid "false" msgstr "" -#: module/setup.py:502 +#: pyload/setup/Setup.py:396 msgid "f" msgstr "" -#: module/setup.py:505 module/setup.py:516 +#: pyload/setup/Setup.py:399 pyload/setup/Setup.py:409 msgid "Invalid Input" msgstr "" diff --git a/locale/sq/LC_MESSAGES/cli.po b/locale/sq/LC_MESSAGES/cli.po new file mode 100644 index 000000000..84e7ae1df --- /dev/null +++ b/locale/sq/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Albanian\n" +"Language: sq_AL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/sq/LC_MESSAGES/core.po b/locale/sq/LC_MESSAGES/core.po new file mode 100644 index 000000000..a0b939603 --- /dev/null +++ b/locale/sq/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Albanian\n" +"Language: sq_AL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/sq/LC_MESSAGES/plugins.po b/locale/sq/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..14819ee8b --- /dev/null +++ b/locale/sq/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Albanian\n" +"Language: sq_AL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/sq/LC_MESSAGES/setup.po b/locale/sq/LC_MESSAGES/setup.po new file mode 100644 index 000000000..7901588e8 --- /dev/null +++ b/locale/sq/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Albanian\n" +"Language: sq_AL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/sq/LC_MESSAGES/webUI.po b/locale/sq/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..6a68f8736 --- /dev/null +++ b/locale/sq/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Albanian\n" +"Language: sq_AL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/sr/LC_MESSAGES/cli.po b/locale/sr/LC_MESSAGES/cli.po new file mode 100644 index 000000000..4436fb454 --- /dev/null +++ b/locale/sr/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Serbian (Cyrillic)\n" +"Language: sr_SP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Додајте пакет:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Унесите име новог пакета" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Пакет: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Рашчланите везе које желите да додате." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Унесите %s када завршите." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Додато веза: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " назад на главни мени" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Управљајте пакетима:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Управљајте везама:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Шта желите да преместите?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Шта желите да обришете?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Шта желите да поново покренете?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Изаберите жељену радњу или унесите број пакета." + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "обриши" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "премести" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "поново покрени" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " – претходно" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " – следеће" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Интерфејс командне линије" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s преузимања:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Брзина: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Величина: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Завршено: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr " ID: " + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "на чекању: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Статус:" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "паузирано" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "покренуто" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "Укупна брзина" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Датотеке у списку" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Укупно" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Мени:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Додај везе" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Управљај списком" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr " Управљај сакупљачем" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr " Паузирај/настави сервер" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Окончај сервер" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Изађи" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Користите ову синтаксу: add <име пакета> <веза> <друга веза>…" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Провера %d веза:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Датотека не постоји." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad је обустављен" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Штампа статус сервера" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Штампа преузимања у списку" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Штампа преузимања у сакупљачу" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Додаје пакет у списак" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Додаје пакет у сакупљач" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Брише датотеке из списка/сакупљача" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Брише пакете из списка/сакупљача" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Премешта пакете из списка у сакупљач и обрнуто" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Поново покрени датотеке" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Поново покрени пакете" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Проверава доступност (ради са локалним контејнером)" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Проверава доступност датотеке контејнера" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Паузирај сервер" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "настави преузимања" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Паузирај/настави" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "окончај сервер" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Списак команди:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Не могу да пишем на датотеку са подешавањима" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Потребан вам је py-openssl да бисте се повезали на сервер pyLoad-а." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Адреса: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Порт: " + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Корисничко име: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Лозинка: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Подаци за пријаву су погрешни." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Не могу да успоставим везу са адресом %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Занемарен је интерактивни режим јер сте задали неке команде." + diff --git a/locale/sr/LC_MESSAGES/core.po b/locale/sr/LC_MESSAGES/core.po new file mode 100644 index 000000000..ab516327a --- /dev/null +++ b/locale/sr/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Serbian (Cyrillic)\n" +"Language: sr_SP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "Грешка при извршавању %s" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Не могу да активирам %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Активирани прикључци: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Деактивирани прикључци: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Активирам прикључке…" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Деактивирам прикључке…" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL сертификати нису пронађени." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "Веб интерфејс није доступан" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "Покретање веб интерфејса у развојном режиму" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Не могу да покренем веб сервер: " + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Не могу да увезем веб сервер: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Овај сервер не подржава SSL. Уместо њега користите угнеждени." + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Покрећем веб сервер %(name)s: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Удаљени приступ" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Опис" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Дужи опис" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Активирано" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Порт" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Адреса" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Евиденција" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Величина у килобајтима" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Фасцикла" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Датотека евиденције" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Број" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Ротирање евиденције" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "Дозволе" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Име групе" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Промените групу и кориснике преузимања" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "Промените режим преузимања" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Корисничко име" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Режим преузимања" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Промените групу покренутих процеса" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Дозволе за фасцикле" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Промени корисника покренутог процеса" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Опште" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Језик" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Фасцикла за преузимања" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Контролни збир" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "Креирај фасциклу за сваки пакет" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Отклањање грешака" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "Минимално слободног простора (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "Приоритет процесора" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL сертификат" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL кључ" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Веб интерфејс" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Шаблон" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Префикс путање" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Сервер" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Жељени сервер" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP адреса" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "HTTPS" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Развојни режим" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Посреднички сервер" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Посреднички сервер" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Лозинка" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Протокол" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Поновно повезивање" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Заврши" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Повежи се поново" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Метод" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Покрени" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Преузми" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Максималан број истовремених преузимања" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Ограничи брзину преузимања" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Интерфејс преузимања (IP адреса или име)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Прескочи постојеће датотеке" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Максимална брзина преузимања (kB/с)" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "IPv6" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Максималан број веза за једно преузимање" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Поново покрени неуспела преузимања при покретању програма" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Време преузимања" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Одломци преузимања нису успели. Враћање на једну везу | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Додат је пакет %(name)s као фасцикла %(folder)s" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Додато је %d веза у пакет" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Непознат прикључак за налог %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Упит" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Захтев потврдног кода" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Разрешите потврдни кôд." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Грешка удаљеног фида: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Покрећем %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Не могу да учитам позадинску компоненту %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "ништа" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "ван мреже" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "на мрежи" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "на чекању" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "паузирано" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "завршено" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "прескочено" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "неуспело" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "започињем" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "чекам" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "преузимам" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "привремено ван мреже" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "прекинуто" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "дешифрујем" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "обрађујем" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "прилагођено" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "непознато" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Пакет је завршен: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Корисник „%s“ покушава да се пријави." + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Примљен је сигнал за излазак" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad већ ради са бројем %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Не могу да променим групу: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Не могу да променим корисника: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Покрећем" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Основна фасцикла: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Све везе су уклоњене" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Време преузимања: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Слободно простора: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Активирам налоге…" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Поново покрећем неуспела преузимања…" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad је спреман за коришћење" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "поновно покретање pyLoad-а" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad се затвара" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "искључујем…" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "грешка при искључивању" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "pyLoad је окончан из терминала" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "База података је обрисана због некомпатибилне верзије." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Дешифровање није успело" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Дешифровано је %(count)d веза у пакет „%(name)s“" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Ниједна веза није дешифрована" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Не могу да преузмем податке за %(name)s | %(err)s" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Неуспешно поновно повезивање: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Скрипт за поновно повезивање није пронађен." + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Покрећем поновно повезивање" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Не могу да извршим скрипт за поновно повезивање." + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Поново је повезано; нова IP адреса: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Нема довољно простора на уређају" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Преузимање је започето: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Преузимање је завршено: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Прикључку %s недостаје функција." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Преузимање је прекинуто: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Преузимање је поново покренуто: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Преузимање је ван мреже: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Преузимање је тренутно ван мреже: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Преузимање није успело: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Не могу да се повежем са хостом или је веза прекинута. Поновно повезивање за 1 минут." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Преузимање је прескочено: %(name)s због %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "Унутрашња грешка сервера" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Дошло је до грешке" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Грешка увоза %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Механизам JavaScript-а није откривен. Инсталирајте SpiderMonkey, OSSP js, pyv8, Node.js или Rhino." + diff --git a/locale/sr/LC_MESSAGES/django.mo b/locale/sr/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 2f03721f1..000000000 --- a/locale/sr/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/sr/LC_MESSAGES/plugins.po b/locale/sr/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..fa9c6de39 --- /dev/null +++ b/locale/sr/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Serbian (Cyrillic)\n" +"Language: sr_SP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Одломци преузимања нису успели. Враћање на једну везу | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Pil и Tesseract нису инсталирани, нити је повезан клијент ради дешифровања потврдних кодова." + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Није обезбеђен резултат потврдног кода у одговарајуће време." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Постављање корисника и групе није успело: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Непостојећа датотека или неподржани протокол" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: дељење саобраћаја (директно преузимање)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Већ преузимате са ове IP адресе. Сачекајте 60 секунди." + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Неисправан кôд за проверу идентитета. Преузимање ће поново бити покренуто." + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "Rapidshare.com: нема слободних слотова" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Потребан вам је премијум налог да бисте преузели ову датотеку" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Пријављено име датотеке није исправно" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Унесите налог на сервису %s или деактивирајте овај прикључак." + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Дешифровање није успело" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "У URL адреси није наведен кључ датотеке" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Кôд грешке:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Грешка у истовременом преузимању. Сачекајте 60 секунди." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "Нисте пријављени." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "API кључ је неисправан" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: немате довољно саобраћаја" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Прекорачили сте саобраћај" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Потребна је провера идентитета (корисничко име и лозинка)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Датотека тренутно није доступна" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: чекање између преузимања %d с." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: чекање потврдног кода %d с." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "Преузета датотека је празна" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "У преузетој датотеци се налази HTML кôд (%s). Можда је грешка у преусмеравању. Преузимање ће поново бити покренуто." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "Дужа URL адреса: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Не могу да се пријавим са налогом %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Неисправна лозинка" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "Преузмите податке о налогу за %s" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Грешка: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Време %s је у погрешном формату. Користите: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Налог %s нема довољно саобраћаја. Проверавам поново за 30 минута." + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Налог %s је истекао. Проверавам поново за сат времена." + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "Пријавите се са %s" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Грешка при извршавању прикључака: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Активирајте директно преузимање у налогу Bitshare-а" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "Достигнуто је ограничење преузимања" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Прво додајте налог на сервису premium.to па поново покрените pyLoad." + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "Инсталирани скрипти за %s: " + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Скрипта не може да се изврши:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Грешка у %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "Преостало је %s кредита" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "Не могу да пошаљем одговор." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "У вашем налогу на сервису CaptchaTrader нема довољно кредита." + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Нови CaptchaID од отпремања: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "У вашем налогу на сервису 9kw.eu нема довољно кредита." + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Прво додајте налог на сервису rehost.to па поново покрените pyLoad." + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "Додато %s из HotFolder-а" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: порт 9666 већ је у употреби" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Пакет је завршен: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Преузимање је завршено: %(name)s @ %(plugin)s" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "У вашем налогу на сервису ExpertDecoders нема довољно кредита." + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Прикључци су ажурирани. Поново покрените pyLoad. ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Прикључци су ажурирани и поново учитани" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Нема ажурирања прикључака" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "Нема нове верзије pyLoad-а" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Доступна је нова верзија, pyLoad %s ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Преузмите је одавде: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Не могу да се повежем са сервером за ажурирања" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Нова верзија %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "Грешка при ажурирању %s" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Верзије се не поклапају" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Није инсталиран %s" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "Не могу да активирам %s" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Активирано" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Нису активирани прикључци за распакивање" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "Пакет %s је стављен у ред за распакивање" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "Провери пакет %s" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Распакуј у %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Нема датотека за распакивање" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "распакујем" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Заштићено лозинком" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Неисправна лозинка" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "Бришем %s датотека" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Распакивање је завршено" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Грешка у архиви" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC се не поклапа" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Непозната грешка" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Постављање корисника и групе није успело" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Списак шифратора није пронађен" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Списак шифратора је празан" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Преузимање је завршено: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Нови захтев потврдног кода: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "Одговорите са „c %s текст на потврдном коду“" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Додајте исправан налог сервиса premiumize.me па поново покрените pyLoad." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "Преостало је %d кредита" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Активирано %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Ниједан хостер није учитан" + diff --git a/locale/sr/LC_MESSAGES/pyLoad.mo b/locale/sr/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index e66d60ec6..000000000 --- a/locale/sr/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/sr/LC_MESSAGES/pyLoadCli.mo b/locale/sr/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 6d4d21e9d..000000000 --- a/locale/sr/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/sr/LC_MESSAGES/pyLoadGui.mo b/locale/sr/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 5ece99c01..000000000 --- a/locale/sr/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/sr/LC_MESSAGES/setup.mo b/locale/sr/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 3ce6ecf6c..000000000 --- a/locale/sr/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/sr/LC_MESSAGES/setup.po b/locale/sr/LC_MESSAGES/setup.po new file mode 100644 index 000000000..3f2f7525b --- /dev/null +++ b/locale/sr/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Serbian (Cyrillic)\n" +"Language: sr_SP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "Желите ли да подесите pyLoad преко веб интерфејса?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "За то вам је потребан веб прегледач и веза с овим рачунаром." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "URL адреса: http://hostname:8000/" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Започети веб интерфејс ради подешавања?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Добро дошли код помоћника за подешавање pyLoad-а." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Он ће проверити систем и наместити основне поставке тако да можете да покренете pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Вредност у угластим заградама је подразумевана." + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "ако не желите да промените или незнате шта да изаберете, стисните 'ентер'." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Знајте: можете поново да покренете овај асистент са параметром --setup или -s када покренет pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Ако имате проблема с овим помоћником, притисните Ctrl+C," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "за поништавање и немојте да га покренете аутоматски са pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Када сте спремни да проверите систем, стисните 'ентер'." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Могућности које недостају: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "py-crypto није доступан" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "То Вам треба ако желите да дешифрујете контејнере." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL није доступан" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Потребно ако желите да урадите сигурносну везу на језгро или вебинтерфејс." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "Ако само желите да приступите pyLoad-у локално, SSL вам није од користи." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "није доступно налажење провере" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Потребно за неке хостере и као бесплатан корисник." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "механизам JavaScript-а није пронађен" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "То вам треба за неке Click'N'Load везе. Инсталирајте SpiderMonkey, OSSP js, pyv8 или Rhino." + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Можете одмах да прекинете подешавање и исправите зависне елементе, ако желите." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Наставити са подешавањем?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Желите ли да промените путању за подешавање? Тренутна путања је %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Ако користите pyLoad на серверу или на унутрашњем флешу, размислите да је промените." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Променити путању за подешавање?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Желите ли да наместите податке за пријаву и основне поставке?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Ово је препоручено на прво покретање." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Уради осносно подешавање?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Желите ли да подесите SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Подесити SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Да се конфигурише веб интерфејс?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Подесити веб интерфејс?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Подешавање је успешно завршено." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Притисните Enter да изађете и поново покренете pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Веб интерфејс је покренут ради подешавања." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Основно подешавање ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Следећи подаци за пријаву важе за CLI, GUI и веб интерфејс." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Корисничко име" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Спољним клијентима (GUI, CLI и др.) потребан је удаљени приступ како би радили преко мреже." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Међутим, ако желите да користите само веб интерфејс, можете га онемогућити како бисте сачували радну меморију." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Омогући удаљени приступ" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Језик" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Фасцикла за преузимање" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Максималан број истовремених преузимања" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Користити поновно повезивање?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Локација скрипта за поновно повезивање" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Подешавање веб интерфејса ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Активирати веб интерфејс?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Адреса за пријем. Ако користите 127.0.0.1 или localhost, веб интерфејс ће бити доступан само локално." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Адреса" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Порт" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad пружа неколико системске подршке сервера, ево кратко објашњење." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Подразумевани сервер пружа SSL и добра је алтернатива уграђеном серверу." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Може да га користи apache, lighttpd, потребно је да их подесите, што није најлакше." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "Веома брза алтернатива писана у језику C (захтева познавање libev-а и Linux-а)" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Узети га овде: https://github.com/jonashaag/bjoern компилујте га" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "и копирајте bjoern.so у pyload/lib" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Пажња: у неким случајевима уграђен сервер не ради, ако имате проблема са веб интерфејсом" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "вратите се овде и промените уграђен сервер са навојеним." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Сервер" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## Подешавање SSL-а ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Покрените ове команде из pyLoad фасцикле подешавања да би урадили ССЛ цертификате:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Ако сте завршили ис све је у реду, можете сада да активирате ССЛ." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Активирати SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Изаберите радњу" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 – креирајте/уредите корисника" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 – излистајте кориснике" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 – уклоните корисника" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 – изађите" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Корисници" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Постављање нове путање за подешавање; тренутна конфигурација неће бити пренета." + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Путања за подешавање" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Путања за подешавање је промењена. Крените поново из почетка." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Притисните Enter да изађете." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Не могу да поставим путању за подешавање: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "д" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "н" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Лозинка: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Лозинка је прекратка. Унесите бар четири симбола." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Потврда лозинке: " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Лозинке се не поклапају." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "да" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "тачно" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "т" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "не" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "нетачно" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "н" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Неисправан унос" + diff --git a/locale/sr/LC_MESSAGES/webUI.po b/locale/sr/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..8c21a763d --- /dev/null +++ b/locale/sr/LC_MESSAGES/webUI.po @@ -0,0 +1,135 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Serbian (Cyrillic)\n" +"Language: sr_SP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "није доступно" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "неограничено" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Администратор" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Подешавање" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Додај налог" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Налози" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Локално" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Претражи" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Тип" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Све" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Завршено" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Незавршено" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Неуспело" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "1 пакет" +msgstr[1] "%d пакета" +msgstr[2] "%d пакета" +msgstr[3] "%d пакета" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "1 датотека" +msgstr[1] "%d датотеке" +msgstr[2] "%d датотека" +msgstr[3] "%d датотека" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Додавање налога" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Унесите податке налога." + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Избор прикључка" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Изаберите прикључак који желите да подесите." + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Додај" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Затвори" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Потврда" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Желите ли да обришете изабране ставке?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Обриши" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Откажи" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Сачувај" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "Покрећем…" + diff --git a/locale/sv/LC_MESSAGES/cli.po b/locale/sv/LC_MESSAGES/cli.po new file mode 100644 index 000000000..c2534a130 --- /dev/null +++ b/locale/sv/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1) ;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Lägg till paket:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Ange ett namn för nya paketet" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paket: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Klistra in länkarna som du vill lägga till." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Skriv %s när du är färdig." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Länkar lades till: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr " tillbaka till huvudmenyn" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Hantera paket:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Hantera länkar:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Vad vill du flytta?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Vad vill du ta bort?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Vad vill du starta om?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "ta bort" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "flytta" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "starta om" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - föregående" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr " - nästa" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Kommandoradsgränssnitt" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s hämtningar:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Hastighet: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Storlek: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Färdig om: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "väntar: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Meny:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Lägg till länkar" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr " Hantera kö" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Hantera länksamlaren" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "Pausa/starta servern" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr " Döda server" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr " Avsluta" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Använd denna syntax: add <paketnamn> <länk> <länk2> ..." + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Kontrollerar %d länkar:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Filen finns inte." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad avbröts" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Visar serverstatus" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Visar nedladdningar i kön" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Visar nedladdningar i länksamlaren" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Lägger till paketet i kön" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Lägger till paketet i länksamlaren" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Radera filer från kön/länksamlaren" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Radera paket från kön/länksamlaren" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Flytta paket från kön till länksamlaren och vice versa" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Starta om filer" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Starta om paket" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Undersök onlinestatus, fungerar med lokal container" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Kontrollerar onlinestatusen för en containerfil" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Pausa servern" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "återuppta hämtningar" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Pausa/fortsätt" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "döda server" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Lista över kommandon:" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Kunde inte skriva användarens inställningsfil" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "Du behöver py-openssl för att ansluta till denna pyLoad core." + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adress: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Användarnamn: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Lösenord: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Inloggningsdata är felaktigt." + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "Kunde inte etablera anslutning till %(addr)s:%(port)s." + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Interaktivt läge ignorerat då vissa kommandon skickades." + diff --git a/locale/sv/LC_MESSAGES/core.po b/locale/sv/LC_MESSAGES/core.po new file mode 100644 index 000000000..1f77113c0 --- /dev/null +++ b/locale/sv/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1) ;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Misslyckades aktivera %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Aktiverar tilläggsprogram..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL-certifikat hittades inte." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Den här servern använder inte SSL, överväg att använda den trådade istället " + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Aktiverat" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Fel i fjärradministrationsmodulen: %s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Startar %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Inläsningen av administrationsmodulen %(name)s misslyckades | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "frånkopplad" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "ansluten" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "kölagd" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "färdig" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "hoppades över" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "misslyckades" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "startar" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "väntar" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "hämtar" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "temp. frånkopplad" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "avbruten" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "dekrypterar" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "behandlar" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "anpassad" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "okänd" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paketet klart: %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Tog emot avslutningssignal" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad körs redan med pid %s" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Misslyckades att ändra gruppen %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Kunde inte ändra användaren: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Startar" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Använder hemkatalog: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Alla länkar togs bort" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Ledigt utrymme: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Aktiverar konton..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad är igång" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "startar om pyLoad" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyLoad avslutas" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "stänger ner..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "fel vid nedstängning" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Återanslutningen misslyckades: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Skript för att återansluta hittades inte!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Startar reconnect" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Exekveringen av återanslutningsskriptet misslyckades!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Återansluten, nytt IP-nummer: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Otillräckligt utrymme på enheten" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Hämtningen startar: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Hämtningen klar: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Plugin %s saknar en funktion." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Hämtningen avbröts: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Hämtning startades om: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Hämtningen är offline: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Hämtningen är tillfälligt offline: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Hämtning misslyckades: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Hämtning hoppades över: %(name)s pga %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Fel vid import av %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/sv/LC_MESSAGES/django.mo b/locale/sv/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index c85b2c173..000000000 --- a/locale/sv/LC_MESSAGES/django.mo +++ /dev/null diff --git a/locale/sv/LC_MESSAGES/plugins.po b/locale/sv/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..5381c4799 --- /dev/null +++ b/locale/sv/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1) ;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paketet klart: %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Aktiverat" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/sv/LC_MESSAGES/pyLoad.mo b/locale/sv/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 29d93d585..000000000 --- a/locale/sv/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/sv/LC_MESSAGES/pyLoadCli.mo b/locale/sv/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index f0c2c04bd..000000000 --- a/locale/sv/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/sv/LC_MESSAGES/pyLoadGui.mo b/locale/sv/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 4231a1bbf..000000000 --- a/locale/sv/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/sv/LC_MESSAGES/setup.mo b/locale/sv/LC_MESSAGES/setup.mo Binary files differdeleted file mode 100644 index 2cf2efd70..000000000 --- a/locale/sv/LC_MESSAGES/setup.mo +++ /dev/null diff --git a/locale/sv/LC_MESSAGES/setup.po b/locale/sv/LC_MESSAGES/setup.po new file mode 100644 index 000000000..583fb8a75 --- /dev/null +++ b/locale/sv/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1) ;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "Välkommen till konfigurationsguiden för pyLoad." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Den kommer att kontrollera ditt system och göra en grundkonfiguration för att kunna köra pyLoad." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Värden inom hakparanteser [] är alltid standardvärdet," + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "tryck bara Enter i de fall du inte vill ändra det eller om du är osäker vad du ska välja." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Glöm inte: Du kan alltid köra denna guide igen med --setup eller parametern -s, när du startar pyLoadCore." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "för att avbryta och låt bli att starta automatiskt med pyLoadCore." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Tryck på Enter när du är färdig för att kontrollera systemet." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "ingen py-crypto tillgänglig" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Du behöver denna om du vill dekryptera container-filer." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "ingen SSL tillgänglig" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Denna krävs om du vill etablera en säker anslutning till core eller webbgränssnittet." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "ingen Captcha Recognition tillgänglig" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Behövs endast för vissa \"hosters\" och som \"freeuser\"." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "ingen JavaScript-motor hittades" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Du behöver detta för några Click'N'Load-länkar. Installera Spidermonkey, ossp-js, pyv8 eller rhino" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Fortsätt med konfigurationen?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Vill du ändra konfigurationssökvägen? Aktuell sökväg är %s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Ändra konfigurationssökväg?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Vill du konfigurera inloggningsdata och grundinställningar?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Detta rekommenderas för första gången." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Skapa grundkonfiguration?" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Vill du konfigurera SSL?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Konfigurera SSL?" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Vill du konfigurera webbgränssnittet?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Konfigurera webbgränssnittet?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Konfigurationen färdigställdes." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Tryck Enter för att avsluta och starta om pyLoad" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Grundkonfiguration ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Följande inloggningsdata är giltigt för CLI, GUI och webbgränssnitt." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Externa klienter (GUI, CLI osv) behöver fjärråtkomst för att fungera över nätverket." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Men om du bara vill använda webbgränssnittet kan du avaktivera den för att spara RAM." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Aktivera fjärråtkomst" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Max samtidiga hämtningar" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Använd återanslutning (Reconnect)?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Plats för Reconnect-skript" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Konfiguration av webbgränssnitt ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Aktivera webbgränssnitt?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Lyssningsadress, om du använder 127.0.0.1 eller localhost, kommer webbgränssnittet endast vara åtkomligt lokalt." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad erbjuder flera server backends. Här följer en kort förklaring." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Kan användas med apache, lighttpd. Kräver att du konfigurera dem, vilket inte är så svårt." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Hämta det här: https://github.com/jonashaag/bjoern, kompilera det" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Viktigt: I vissa ovanliga fall kanske den inbyggda servern inte fungerar. Om du upplever problem med webbgränssnittet" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "så kom tillbaka hit och ändra den inbyggda servern till den trådade servern." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL-konfiguration ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Kör dessa kommandon från konfigurationsmappen för pyLoad för att skapa SSL-certifikaten:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Du kan aktivera SSL nu om du är färdig och allting gick bra." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "Aktivera SSL?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Välj åtgärd" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Skapa/Redigera användare" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Lista användare" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Ta bort användare" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Avsluta" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Användare" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Tryck Enter för att avsluta." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Inställning av konfigurationssökväg misslyckades: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "j" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Lösenord: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Lösenord (igen): " + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Lösenorden stämmer inte överens." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "ja" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "sant" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "s" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "nej" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "falskt" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Ogiltig inmatning" + diff --git a/locale/sv/LC_MESSAGES/webUI.po b/locale/sv/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..b69894f98 --- /dev/null +++ b/locale/sv/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1) ;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/te/LC_MESSAGES/cli.po b/locale/te/LC_MESSAGES/cli.po new file mode 100644 index 000000000..8411f6bc3 --- /dev/null +++ b/locale/te/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Telugu\n" +"Language: te_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/te/LC_MESSAGES/core.po b/locale/te/LC_MESSAGES/core.po new file mode 100644 index 000000000..c4ebc203e --- /dev/null +++ b/locale/te/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Telugu\n" +"Language: te_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/te/LC_MESSAGES/plugins.po b/locale/te/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..0023f7f1b --- /dev/null +++ b/locale/te/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Telugu\n" +"Language: te_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/te/LC_MESSAGES/setup.po b/locale/te/LC_MESSAGES/setup.po new file mode 100644 index 000000000..9908e4b6a --- /dev/null +++ b/locale/te/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Telugu\n" +"Language: te_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/te/LC_MESSAGES/webUI.po b/locale/te/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..7ac76e913 --- /dev/null +++ b/locale/te/LC_MESSAGES/webUI.po @@ -0,0 +1,131 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Telugu\n" +"Language: te_IN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/tr/LC_MESSAGES/cli.po b/locale/tr/LC_MESSAGES/cli.po new file mode 100644 index 000000000..a72bc3aff --- /dev/null +++ b/locale/tr/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "Paket ekle:" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "Yeni paketin adını girin" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Paket: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Eklemek isteniz linkleri çözümle" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Tip %s tamamlandı" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Eklenen linkler: " + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "Menüye geri dön" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "Paketleri Düzenle:" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Linkleri Düzenle:" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "Neleri taşımak istersiniz?" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "Neleri silmek istersiniz?" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "Neleri yenilemek istersiniz?" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "Ne yapmak istediğinizi seçin veya paket numarası girin" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "sil" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "taşı" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "yeniden başlat " + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr " - önceki" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "-Sonraki" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr " Komut satır arayüzü" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s İndirmeler:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr " Hız: " + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr " Büyüklük: " + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr " Bitiş zamanı: " + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "ID:" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "bekleniyor: " + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "Durum" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "Duraklatıldı" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "çalışıyor" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "toplam hız" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "Sıradaki Dosyalar" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "Toplam" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Menü:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr " Linkleri Ekle" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "Kuyruğu Düzenle" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "Kolektörü Düzenle " + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "(Un)Sunucuyu Durdur" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "Sunucuyu Devredışı bırak" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "Çıkış" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "Lütfen doğru eşleştirme metnini kullanın:Ekle <Paket adı> <link> <linkl2>" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "%d Linkler Kontrol ediliyor" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Dosya bulunamadı" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "pyLoad durdu" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "Sunucu durumunu yazdır" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "Kuyruktakileri yazdır" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "Kolektördekileri yazdır" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "Kuyruğa paket ekle" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "Kolektöre paket ekle" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "Dosyaları Kuyruktan/Kolektör'den sil" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "Paketleri Kuyruktan/Kolektör'den sil" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "Paketleri Kuyruktan , Kolektöre taşı veya tam tersini yap" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Dosyaları yenile" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "Paketleri yenile" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "Yerel konteynerla çalıştıgını ve online olup olmadığını kontrol et" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "Konteyner dosyalarının çemrimiçi oldugunu kontrol et" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "Sunucuyu durdur" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "indirmelere devam et" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "Seçilenleri durdur/devam ettir" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "sunucuyu durdur yap" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "Komut listesi" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "Kullanıcı ayar dosyası yazılamadı" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "pyLoad çekirdeğine bağlanmak için py-opensll ye baglanmanız lazım" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "Adres: " + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Port:" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Kullanıcı adı: " + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Şifre: " + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "Giriş bilgileri yanlış" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "%(addr)s:%(port)s. ile bağlantı kurulamadı" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "Bazı komutları onayladınızdan beri , Çevrimdışı modu engellendi" + diff --git a/locale/tr/LC_MESSAGES/core.po b/locale/tr/LC_MESSAGES/core.po new file mode 100644 index 000000000..a426616e6 --- /dev/null +++ b/locale/tr/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "%s yürütürken hata" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "Aktive etme başarısız %(name)s" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "Aktif eklentiler: %s" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "Pasif eklentiler: %s" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "Eklentiler etkinleştiriliyor ..." + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "Eklentiler devredışı bırakılıyor..." + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "SSL sertifikaları bulunamadı." + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "WebUI kullanılamaz" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "webUI geliştirici modunda çalışıyor" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "Web sunucusu başlatma başarısız oldu:" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "Web sunucusundan alma başarısız oldu: " + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "Bu sunucu hiçbir SSL sunmuyor, kullanarak yerine dişli düşünün lütfen" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "Başlıyor %(name)s websunucusu: %(host)s:%(port)d" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "Uzaktan" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "Açıklama" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "Ayrıntılı açıklama" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "Etkinleştirildi" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Port" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Adres" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "Günlük" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "Boyut kb" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Klasör" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "Dosya günlük" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "Sayı" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "Günlük döndürme" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "İzinler" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "Grupadı" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "Grup ve kullanıcı yüklemeleri değiştirme" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "yüklemelerin dosya modu değiştirme" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Kullanıcı Adı" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "Yükleme Dosya Modu" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "Çalışan işlemlerin grubunu değiştirme" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "Dosya İzin modu" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "Çalışan işlemlerin kullanıcısını değiştirme" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Genel" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Dil" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "Yükleme Klasörü" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "Doğrulama kullan" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "her paket için dosya yarat" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "Hata ayıklama modu" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "En az boş alan (MB)" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "CPU önceliği" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "SSL" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "SSL Sertifikası" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "SSL anahtarı" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "Webarayüzü" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "Şablon" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "Yol öneki" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Sunucu" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "Belirli sunucu" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "IP" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "HTTPS kullan" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "Geliştirme modu" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "Proxy" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "Proxy kullan" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Şifre" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "Protokol" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "Yeniden bağlanma" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "Son" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "Bağlantı sıfırlama" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "Yöntem" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "Başlat" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "Yükleme" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "Max indirme sayısı" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "Yükleme hızını sınırla" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "Yükleme arayüzü (ip veya isim)" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "Zaten varolan dosyaları atla" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "Max indirme hızı kb/s" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "IPv6 izin ver" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "Bir yükleme için max bağlantı sayısı" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "Başarısız olan yüklemeleri başlangıçta yeniden başlat" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "Yükleme süresi" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Karşıdan yükleme başarısız, tekli bağlantıya dönün | %s" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "Paket %(name)s eklendi %(folder)s klasör olarak" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "Pakete %d linkleri eklendi" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "Bilinmeyen hesap eklenti %s" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "Sorgu" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "Captcha isteği" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "Lütfen captcha çözünüz." + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "Uzak uç hatası: % s" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "Başlıyor %(name)s: %(addr)s:%(port)s" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "Arka uç yüklenirken hata %(name)s | %(error)s" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "hiçbiri" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "Çevrimdışı" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "Çevrimiçi" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "sıraya alındı" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "Duraklatıldı" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "bitti" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "atlandı" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "başarısız oldu" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "başlatılıyor" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "bekliyor" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "indiriliyor" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "geçici çevrimdışı" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "iptal edildi" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "çözülüyor" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "işlem devam ediyor" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "Özel" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "bilinmeyen" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "Paket tamamlandı. %s" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "Kullanıcı oturum açmak için '%s' dener" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "Çıkış sinyali alındı" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "pyLoad zaten %s pid ile çalışıyor" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "Grup değiştirme başarısız: %s" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "Kullanıcı değiştirme başarısız: %s" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "Başlıyor" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "Ev dizinini kullan: %s" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "Tüm bağlantılar kaldırıldı" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "Yükleme süresi: %s" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "Boş alan: %s" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "Hesap etkinleştiriliyor ..." + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "Başarısız olan yüklemeler yeniden başlatılıyor..." + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "pyLoad başlatıldı ve çalışıyor" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "pyLoad yeniden başlatılıyor" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "pyload çıkış" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "kapatılıyor ..." + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "kapatma sırasında hata oluştu" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "terminalden kapatılan pyLoad" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "Veritabanı uygunsuz sürüm yüzünden silindi." + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "Çözümleme başarısız oldu" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "Çözülmüş %(count)d linkleri %(name)s paketlerine" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "Hiç bağlantı çözümlenmedi" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "Bilgisi %(name)s alınırken hata | %(err)s oluştu" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "Tekrar bağlanma başarısız: %s" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "Tekrar bağlanma komutu bulunamadı!" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "Tekrar bağlanma başlatılıyor" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "Yeniden Bağlantı komut dosyası yürütmesi başarısız!" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "Yeniden Bağlandı, yeni IP: %s" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "Aygıtta yeterli alan yok" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "Yükleme başlar: %s" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "Yükleme bitti: %s" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "Eklenti %s işlevi eksik." + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "Yükleme iptal edildi: %s" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "Yükleme yeniden başlatıldı: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "Yükleme çevrimdışı: %s" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "Yükleme geçici olarak çevrımdışı: %s" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "Yükleme başarısız: %(name)s | %(msg)s" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "Sunucuya bağlanılamadı veya bağlantı sıfırlandı, 1 dakikalık erteleme bekleniyor." + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "Yükleme atlandı: %(name)s bunun yüzünden %(plugin)s" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "İç Sunucu Hatası" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "Bir hata meydana geldi" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "Alınırken hata %(name)s: %(msg)s" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "Hiçbir js engine tespit edilemedi, lütfen Spidermonkey, ossp-js, pyv8, nodejs veya rhino birini yükleyin" + diff --git a/locale/tr/LC_MESSAGES/plugins.po b/locale/tr/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..8aee2c1a9 --- /dev/null +++ b/locale/tr/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "Karşıdan yükleme başarısız, tekli bağlantıya dönün | %s" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "Captcha şifresini çözmek için pil ve tesseract yüklenmemiş ve hiçbir istemci bağlı değil" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "Uygun zamanda elde edilen captcha sonuç yok." + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "Kullanıcı ve grup kurma başarısız oldu: %s" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "Mevcut olmayan dosya veya desteklenmeyen bir iletişim kuralı" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "Rapidshare: Trafik Paylaşılan (direk yükleme)" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "Zaten bu IP adresinden yükleme yapılmakta, 60 saniye bekleniyor" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "Geçersiz kimlik doğrulama kodu, yükleme yeniden başlatılacak" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "RapidShareCom: Tüm ücretsiz bağlantılar doldu" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "Bu dosya için premium üyelik hesabı gerekir" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "Dosya adı geçersiz bildirdi" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "Lütfen %s hesabınıza girin veya bu eklentiyi devre dışı bırakın" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "Çözümleme başarısız oldu" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "URL'de sağlanan hiçbir dosya anahtarı yok" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "Hata kodu:" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "Paralel yükleme hatası, 60 saniye bekleniyor." + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "giriş yapılmadı." + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "API anahtarı geçersiz" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "%s: yeterli trafik kalmadı" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "Trafik aşıldı" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "Yetkilendirme gerekli (kullanıcı adı: şifre)" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "Dosya geçici olarak kullanılamıyor" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "Netload: Yüklemeler %d s arasında bekliyor." + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "Netload: captcha için bekleniyor %d s." + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "İndirilen dosya boş" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "İndirilen dosyadaki (%s)... HTML kodunda yönlendirme hatası? Download yeniden başlatıldı." + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "long_url: %s" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "Hesap ile giriş yapılamadı %(user)s | %(msg)s" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "Hatalı Şifre" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "%s için hesap bilgilerini al" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "Hata: %s" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "Zaman biçimin %s yanlış formatta, şunu kullan: 1:22-3:44" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "Hesap %s yeterli trafik yok, 30 dk içinde yeniden deneyin" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "Hesap %s süresi doldu, 1 saat içinde yeniden deneyin" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "%s ile giriş" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "Eklentiyürütülürken hata oluştu: %s" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "Bitshare hesabınızdaki direk yüklemeyi aktifleştirin" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "İndirme sınırına erişildi" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "Lütfen önce premium.to hesabınızı ekleyin ve pyLoad'u yeniden başlatın" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "%s: için yüklü komut dosyaları" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "Komut dosya çalıştırılamaz:" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "Hata %(script)s: %(error)s" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "%s kredi kaldı" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "yanıt gönderilemedi." + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "Sizin CaptchaTrader hesabınızda yeterli kredi bulunmamakta" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "Yeni CaptchaID yükleme tarafından: %s : %s" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "Captcha 9kw.eu hesabı yeterli kredi yok" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "Lütfen önce rehost.to hesabınızı ekleyin ve pyLoad'u yeniden başlatın" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "HotFolder gelen ek %s" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "Click'N'Load: Port 9666 zaten kullanılıyor" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "Paket tamamlandı. %s" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "Yükleme bitti: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "ExpertDecoders hesabınızda yeterli kredi yok" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "*** Eklentiler güncellendi, pyLoad'ı yeniden başlatın ***" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "Eklentiler güncellendi ve yeniden yüklendi" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "Eklentiler için güncelleme yok" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "pyLoad için güncelleme yok" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "*** Yeni pyLoad Sürümü %s mevcuttur ***" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "*** Buradan indirin: http://pyload.org/download ***" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "Güncelleştirmeler için sunucuya bağlanmak mümkün değil" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "Yeni versiyonu %(type)s|%(name)s : %(version).2f" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "%s güncelleştirme hatası" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "Sürüm uyuşmazlığı" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "Yüklü %s yok" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "%s etkinleştirilemedi" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "Etkinleştirildi" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "Aktif Extract eklentisi yok" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "%s paketini daha sonra ayıklamak için sıraya alındı" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "%s paketi kontrol et" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "Arşiv ayıklanıyor şuraya %s" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "Arşivden ayıklanacak dosya bulunamadı" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "Ayıklanıyor" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "Şifre korumalı" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "Hatalı şifre" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "%s Dosya siliniyor" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "Ayıklama bitti" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "Arşiv hatası" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "CRC Uyuşmazlığı" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "Bilinmeyen Hata" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "Kullanıcı ve Grup Ayarları başarısız oldu" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "Şifreleme listesi bulunamadı" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "Şifreleme listesi boş" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "Yükleme bitti: %(name)s @ %(plugin)s " + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "Yeni Captcha İsteği: %s" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "captcha üstündeki 'c %s metnini cevapla" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "Lütfen önce bir geçerli premiumize.me hesabı ekleyin ve pyLoad yeniden başlatın." + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "%d kredi kaldı" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "Aktif %s" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "Hiçbir Sunucu yüklenemedi" + diff --git a/locale/tr/LC_MESSAGES/pyLoad.mo b/locale/tr/LC_MESSAGES/pyLoad.mo Binary files differdeleted file mode 100644 index 2c20cfbd4..000000000 --- a/locale/tr/LC_MESSAGES/pyLoad.mo +++ /dev/null diff --git a/locale/tr/LC_MESSAGES/pyLoadCli.mo b/locale/tr/LC_MESSAGES/pyLoadCli.mo Binary files differdeleted file mode 100644 index 30e94695b..000000000 --- a/locale/tr/LC_MESSAGES/pyLoadCli.mo +++ /dev/null diff --git a/locale/tr/LC_MESSAGES/pyLoadGui.mo b/locale/tr/LC_MESSAGES/pyLoadGui.mo Binary files differdeleted file mode 100644 index 1ae24953f..000000000 --- a/locale/tr/LC_MESSAGES/pyLoadGui.mo +++ /dev/null diff --git a/locale/tr/LC_MESSAGES/setup.po b/locale/tr/LC_MESSAGES/setup.po new file mode 100644 index 000000000..8ecd4ac90 --- /dev/null +++ b/locale/tr/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "PyLoad'u Webarayüzü ile yapılandırmak ister misiniz?" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "Bunun için bir internet tarayıcı ve bu PC için bağlantı ihtiyacın var." + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "URL şu şekilde olmalı: http://hostname:8000 /" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "Yapılandırma için ilk webarayüzü başlatılsın mı?" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "PyLoad ayar sihirbazına hoşgeldiniz." + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "Sisteminizi kontrol edecek ve pyLoad'ın çalışması için bir temel kurulum yapacaktır." + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "Parantez içindeki değerler [], her zaman varsayılan değerdir" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "değiştirmek istemediğinde yada neyi seçeceğinden emin değilsen, sadece enter'e bas." + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "Unutma: pyLoadCore -setup yada -s ekleyerek bu Asistanı her zaman yeniden başlatabilirsiniz." + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "Asistan ile herhangi bir sorun yaşarsanız STRG-C tuşuna basın," + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "iptal etmek ve pyLoadCore ile otomatik başlatmamak için." + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "Eğer sistem kontrolü için hazırsanız, enter'a basın." + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "Eksik Özellikler: " + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "hiçbir py-şifrelemeye ulaşılamıyor" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "Konteyner dosyaları şifresini çözmek istiyorsanız, buna ihtiyacın var." + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "SSL bulunmamaktadır" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "Sisteme yada Web arayüzüne güvenli bağlantı için bu gerekli." + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "pyLoad'a yanlızca yerel erişmek istiyorsanız, ssl önerilmez." + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "Erişilebilir Captcha tanılama yok" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "Sadece bazı misafirler ve bedava kullanıcılar için gerekli." + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "JavaScript motoru bulunamadı" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "Bazı Click'N'Load bağlantıları için gerekli. Spidermonkey, ossp-js, pyv8 yada rhino yükleyin" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "Şuan kurulum iptal edebilir ve gerekiyorsa bazı bağımlılık gerektiren düzeltmeleri yapabilirsiniz." + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "Kurulum ile devam edilsin mi?" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "Yapılandırma yolunu değiştirmek istiyor musunuz? Mevcut% s" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "Eğer pyLoad'u server üzerinde yada evdeki yerel bellek bölümünde kullanacaksan onu değiştirmek iyi fikir." + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "Yapılandırma yolunu değiştirme?" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "Giriş verileri ve temel ayarları yapılandırmak istiyor musunuz?" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "Bu ilk çalışma için tavsiye edilir." + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "Temel kurulum yap" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "Ssl yapılandırmak istiyor musunuz?" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "Ssl yapılandırması" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "Web arayüzünü yapılandırmak istiyor musunuz?" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "Web arayüzü yapılandırması?" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "Kur başarıyla tamamlandı." + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "Çıkmak için Enter tuşuna basın ve pyLoad'ı yeniden başlatın" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "Webarayüzü kurulun için çalışıyor." + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "## Temel Kurulum ##" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "Aşağıdaki giriş verileri geçerlidir şunlar için CLI, GUI ve webarayüzü." + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Kullanıcı Adı" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "Harici kullanıcılar (GUI, CLI veya diğerleri) ağ üzerinde çalışmak için uzaktan erişim ihtiyacı duyar." + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "Şayet sadece webarayüzü kullanmak istiyorsan onu devredışı bırakıp bellekten tasarruf edebilirsin." + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "Uzaktan Erişim" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Dil" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "Yükleme Klasörü" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "Max indirme sayısı" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "Bağlantı sıfırlama?" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "Komut dosyası konumu yeniden bağlanın" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "## Web arayüzü Kurulumu ##" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "Web arayüzü etkin?" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "Adrese bak, eğer 127.0.0.1 veya localhost kullanıyorsanız, webinterface yerel olarak erişilebilir olacaktır." + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Adres" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Port" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "pyLoad şimdi kısa bir açıklama sonrasında, birçok sunucu arka uçları sunmaktadır." + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "Varsayılan sunucu, bu sunucu SSL sunar ve yerleşik iyi bir alternatiftir." + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "Tarafından kullanılabilmek için apache, lighttpd, sana onları konfigüre etmek gerektirir; ki bu çok da kolay bir iş değildir." + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "C ile yazılmış çok hızlı bir alternatif libev ve linux bilgisi gerektirir." + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "Buradan edinin: https://github.com/jonashaag/bjoern, onu derleyin" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "ve bjoern.so pyload/lib için kopyalama" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "Dikkat: Bazı farklı durumlarda yerleşik sunucu çalışmıyorsa, eğer webarayüzüyle ilgili problem farke edersen" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "Buraya gel ve burada yerleşik sunucu dişli değiştir." + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Sunucu" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "## SSL Kurulumu ##" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "Bu komutlar ssl sertifikaları yapmak için pyLoad config klasöründen çalıştırın:" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "Eğer yaptıysan ve her şey iyi gittiyse, ssl'yi şimdi etkinleştirebilirsiniz." + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "SSL Etkinleştir?" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "Eylem seçin" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "1 - Kullanıcı Oluştur / Düzenle" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "2 - Kullanıcıları listele" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "3 - Kullanıcı kaldır" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "4 - Çıkış" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "Kullanıcılar" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "Yeni ayarlar yapılandırma yolu, eski ayarlar transfer edilmeyecek!" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "Yapılandırma yolu" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "Yapılandırma yolu değişti, kurulum şimdi kapanacak, devam etmek için yeniden başlatın." + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "Çıkmak için Enter tuşuna basın." + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "Yapılandırma ayar yolu başarısız oldu: %s" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "e" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "h" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Şifre: " + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "Şifre çok kısa. En az 4 sembol kullanın." + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "Şifre (tekrar):" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "Şifreler eşleşmedi." + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "evet" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "doğru" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "d" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "hayır" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "yanlış" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "y" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "Geçersiz giriş" + diff --git a/locale/tr/LC_MESSAGES/webUI.po b/locale/tr/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..24cfdce4c --- /dev/null +++ b/locale/tr/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "mevcut değil" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "sınırsız" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "Admin" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "Kurulum" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "Hesap Ekle" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Hesaplar" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "Yerel" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "Arama" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "Türü" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "Tüm" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "Tamamlandı" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "Bitmemiş" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "Başarısız oldu" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "%d paket" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "%d dosya" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "Hesap ekle" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "Lütfen hesap verilerinizi girin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "Bir eklenti seçin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "Lütfen yapılandırmak istediğiniz bir eklentiyi seçin" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Ekle" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Kapat" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "Lütfen onaylayın" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "Seçili öğeleri silmek istiyor musunuz?" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "sil" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "İptal" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Gönder" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "Çalışıyor..." + diff --git a/locale/uk/LC_MESSAGES/cli.po b/locale/uk/LC_MESSAGES/cli.po new file mode 100644 index 000000000..996bcedca --- /dev/null +++ b/locale/uk/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Ukrainian\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/uk/LC_MESSAGES/core.po b/locale/uk/LC_MESSAGES/core.po new file mode 100644 index 000000000..dec87fa11 --- /dev/null +++ b/locale/uk/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Ukrainian\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/uk/LC_MESSAGES/plugins.po b/locale/uk/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..5e9691b1f --- /dev/null +++ b/locale/uk/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Ukrainian\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/uk/LC_MESSAGES/setup.po b/locale/uk/LC_MESSAGES/setup.po new file mode 100644 index 000000000..ad5ad3ed0 --- /dev/null +++ b/locale/uk/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Ukrainian\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/uk/LC_MESSAGES/webUI.po b/locale/uk/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..94cdfe8de --- /dev/null +++ b/locale/uk/LC_MESSAGES/webUI.po @@ -0,0 +1,133 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:26-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Ukrainian\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2);\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/vi/LC_MESSAGES/cli.po b/locale/vi/LC_MESSAGES/cli.po new file mode 100644 index 000000000..3ceeabc64 --- /dev/null +++ b/locale/vi/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Vietnamese\n" +"Language: vi_VN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "Gói: %s" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "Phân tích liên kết bạn muốn thêm." + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "Nhập %s khi xong." + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "Liên kết đã thêm:" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "quay lại menu chính" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "Quản lý liên kết" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "- quay lại" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "- kế tiếp" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "Giao diện Dòng lệnh" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "%s Tải xuống:" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "Tốc độ:" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "Kích thước:" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "Kết thúc trong:" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "ID:" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "chờ:" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "Danh Mục:" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "Thêm liên kết" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "(Un)Tạm dừng máy chủ" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "Dừng máy chủ" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "Thoát" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "Đang kiểm tra %d liên kết:" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "Tập tin không tồn tại." + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "in trạng thái máy chủ" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "in hàng đợi tải xuống" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "Khởi động lại các tập tin." + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "tạm dừng máy chủ" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "tiếp tục tải xuống" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "chuyển giữa tạm dừng/tiếp tục" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "dừng máy chủ" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "địa chỉ:" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "Cổng:" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "Tên người dùng:" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "Mật khẩu:" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/vi/LC_MESSAGES/core.po b/locale/vi/LC_MESSAGES/core.po new file mode 100644 index 000000000..8d23b8357 --- /dev/null +++ b/locale/vi/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Vietnamese\n" +"Language: vi_VN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "Cổng" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "Địa chỉ" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "Thư mục" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "Tên người dùng" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "Chung" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "Ngôn ngữ" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "Máy chủ" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "Mật khẩu" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/vi/LC_MESSAGES/plugins.po b/locale/vi/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..2e31b2f0f --- /dev/null +++ b/locale/vi/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-07-20 18:02-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Vietnamese\n" +"Language: vi_VN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/vi/LC_MESSAGES/setup.po b/locale/vi/LC_MESSAGES/setup.po new file mode 100644 index 000000000..2b65dbc5f --- /dev/null +++ b/locale/vi/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-07-17 09:57-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Vietnamese\n" +"Language: vi_VN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "Tên người dùng" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "Ngôn ngữ" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "Địa chỉ" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "Cổng" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "Máy chủ" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "Mật khẩu:" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/vi/LC_MESSAGES/webUI.po b/locale/vi/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..5469db74d --- /dev/null +++ b/locale/vi/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-08-31 15:21-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Vietnamese\n" +"Language: vi_VN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "không giới hạn" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "Tài khoản" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "Thêm" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "Đóng" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "Xóa" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "Hủy bỏ" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "Gửi" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/locale/webUI.pot b/locale/webUI.pot new file mode 100644 index 000000000..1bfa8bf4b --- /dev/null +++ b/locale/webUI.pot @@ -0,0 +1,295 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR pyLoad Team +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: pyload 0.4.9.9-dev\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-10-13 18:16+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +#: pyload/web/app/templates/default/setup/system.html:45 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +#: pyload/web/app/scripts/helpers/formatTime.js:10 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/package.html:25 +#: pyload/web/app/scripts/helpers/linkStatus.js:7 +msgid "online" +msgstr "" +#: pyload/web/app/templates/default/linkgrabber/package.html:30 + +#: pyload/web/app/scripts/helpers/linkStatus.js:9 +msgid "offline" +msgstr "" +#: pyload/web/app/templates/default/linkgrabber/package.html:35 + +#: pyload/web/app/scripts/helpers/linkStatus.js:11 +#: pyload/web/app/scripts/helpers/formatTime.js:8 +msgid "unknown" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" +msgstr[1] "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/setup/finished.html:3 +msgid "Nearly Done" +msgstr "" + +#: pyload/web/app/templates/default/setup/finished.html:7 +msgid "Please check your settings." +msgstr "" + +#: pyload/web/app/templates/default/setup/finished.html:15 +msgid "Confirm" +msgstr "" + +#: pyload/web/app/templates/default/setup/finished.html:20 +msgid "Pleae add a user first." +msgstr "" + +#: pyload/web/app/templates/default/setup/welcome.html:0 +msgid "Welcome!" +msgstr "" + +#: pyload/web/app/templates/default/setup/welcome.html:2 +msgid "pyLoad is running and ready for configuration." +msgstr "" + +#: pyload/web/app/templates/default/setup/welcome.html:5 +msgid "Select your language:" +msgstr "" + +#: pyload/web/app/templates/default/setup/welcome.html:12 +msgid "Start configuration" +msgstr "" + +#: pyload/web/app/templates/default/setup/layout.html:2 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/setup/system.html:0 +msgid "System" +msgstr "" + +#: pyload/web/app/templates/default/setup/system.html:9 +msgid "Dependencies" +msgstr "" + +#: pyload/web/app/templates/default/setup/system.html:31 +msgid "Optional" +msgstr "" + +#: pyload/web/app/templates/default/setup/system.html:38 +msgid "available" +msgstr "" + +#: pyload/web/app/templates/default/setup/system.html:54 +msgid "Next" +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:1 +msgid "Setup timed out" +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:2 +msgid "Setup was closed due to inactivity. Please restart it to continue configuration." +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:5 +msgid "Setup finished" +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:6 +msgid "Setup was successful. You can restart pyLoad now." +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:9 +msgid "Setup failed" +msgstr "" + +#: pyload/web/app/templates/default/setup/error.html:11 +msgid "Try to restart it or open a bug report." +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/linkgrabber/modal.html:39 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/linkgrabber/modal.html:40 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/accounts/account.html:5 +msgid "premium" +msgstr "" + +#: pyload/web/app/templates/default/accounts/account.html:7 +msgid "valid" +msgstr "" + +#: pyload/web/app/templates/default/accounts/account.html:12 +msgid "invalid" +msgstr "" + +#: pyload/web/app/templates/default/accounts/account.html:23 +msgid "Traffic left:" +msgstr "" + +#: pyload/web/app/templates/default/accounts/account.html:27 +msgid "Valid until:" +msgstr "" + +#: pyload/web/app/templates/default/accounts/editAccount.html:2 +msgid "Edit account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/editAccount.html:27 +msgid "Configuration" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:3 +msgid "Add links" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:4 +msgid "paste & add links to pyLoad" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:13 +msgid " Paste your links here..." +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:17 +msgid "Container" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:18 +msgid "Upload" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:24 +msgid "URL" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:25 +msgid "Link to Website" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/modal.html:31 +msgid "Packages" +msgstr "" + +#: pyload/web/app/templates/default/linkgrabber/package.html:19 +msgid "%d link" +msgid_plural "%d links" +msgstr[0] "" +msgstr[1] "" diff --git a/locale/zh/LC_MESSAGES/cli.po b/locale/zh/LC_MESSAGES/cli.po new file mode 100644 index 000000000..5ea6badfc --- /dev/null +++ b/locale/zh/LC_MESSAGES/cli.po @@ -0,0 +1,291 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Chinese Traditional\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/cli/AddPackage.py:48 +msgid "Add Package:" +msgstr "" + +#: pyload/cli/AddPackage.py:53 +msgid "Enter a name for the new package" +msgstr "" + +#: pyload/cli/AddPackage.py:57 +#, python-format +msgid "Package: %s" +msgstr "" + +#: pyload/cli/AddPackage.py:58 +msgid "Parse the links you want to add." +msgstr "" + +#: pyload/cli/AddPackage.py:59 +#, python-format +msgid "Type %s when done." +msgstr "" + +#: pyload/cli/AddPackage.py:60 +msgid "Links added: " +msgstr "" + +#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149 +msgid " back to main menu" +msgstr "" + +#: pyload/cli/ManageFiles.py:97 +msgid "Manage Packages:" +msgstr "" + +#: pyload/cli/ManageFiles.py:99 +msgid "Manage Links:" +msgstr "" + +#: pyload/cli/ManageFiles.py:104 +msgid "What do you want to move?" +msgstr "" + +#: pyload/cli/ManageFiles.py:106 +msgid "What do you want to delete?" +msgstr "" + +#: pyload/cli/ManageFiles.py:108 +msgid "What do you want to restart?" +msgstr "" + +#: pyload/cli/ManageFiles.py:113 +msgid "Choose what you want to do, or enter package number." +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "delete" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "move" +msgstr "" + +#: pyload/cli/ManageFiles.py:115 +msgid "restart" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - previous" +msgstr "" + +#: pyload/cli/ManageFiles.py:148 +msgid " - next" +msgstr "" + +#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133 +msgid " Command Line Interface" +msgstr "" + +#: pyload/cli/Cli.py:165 +#, python-format +msgid "%s Downloads:" +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Speed: " +msgstr "" + +#: pyload/cli/Cli.py:177 +msgid " Size: " +msgstr "" + +#: pyload/cli/Cli.py:178 +msgid " Finished in: " +msgstr "" + +#: pyload/cli/Cli.py:179 +msgid " ID: " +msgstr "" + +#: pyload/cli/Cli.py:184 +msgid "waiting: " +msgstr "" + +#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193 +msgid "Status:" +msgstr "" + +#: pyload/cli/Cli.py:191 +msgid "paused" +msgstr "" + +#: pyload/cli/Cli.py:193 +msgid "running" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "total Speed" +msgstr "" + +#: pyload/cli/Cli.py:196 +msgid "Files in queue" +msgstr "" + +#: pyload/cli/Cli.py:197 +msgid "Total" +msgstr "" + +#: pyload/cli/Cli.py:203 +msgid "Menu:" +msgstr "" + +#: pyload/cli/Cli.py:205 +msgid " Add Links" +msgstr "" + +#: pyload/cli/Cli.py:206 +msgid " Manage Queue" +msgstr "" + +#: pyload/cli/Cli.py:207 +msgid " Manage Collector" +msgstr "" + +#: pyload/cli/Cli.py:208 +msgid " (Un)Pause Server" +msgstr "" + +#: pyload/cli/Cli.py:209 +msgid " Kill Server" +msgstr "" + +#: pyload/cli/Cli.py:210 +msgid " Quit" +msgstr "" + +#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296 +msgid "Please use this syntax: add <Package name> <link> <link2> ..." +msgstr "" + +#: pyload/cli/Cli.py:315 +#, python-format +msgid "Checking %d links:" +msgstr "" + +#: pyload/cli/Cli.py:324 +msgid "File does not exists." +msgstr "" + +#: pyload/cli/Cli.py:385 +msgid "pyLoad was terminated" +msgstr "" + +#: pyload/cli/Cli.py:443 +msgid "Prints server status" +msgstr "" + +#: pyload/cli/Cli.py:444 +msgid "Prints downloads in queue" +msgstr "" + +#: pyload/cli/Cli.py:445 +msgid "Prints downloads in collector" +msgstr "" + +#: pyload/cli/Cli.py:446 +msgid "Adds package to queue" +msgstr "" + +#: pyload/cli/Cli.py:447 +msgid "Adds package to collector" +msgstr "" + +#: pyload/cli/Cli.py:448 +msgid "Delete Files from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:449 +msgid "Delete Packages from Queue/Collector" +msgstr "" + +#: pyload/cli/Cli.py:450 +msgid "Move Packages from Queue to Collector or vice versa" +msgstr "" + +#: pyload/cli/Cli.py:451 +msgid "Restart files" +msgstr "" + +#: pyload/cli/Cli.py:452 +msgid "Restart packages" +msgstr "" + +#: pyload/cli/Cli.py:453 +msgid "Check online status, works with local container" +msgstr "" + +#: pyload/cli/Cli.py:454 +msgid "Checks online status of a container file" +msgstr "" + +#: pyload/cli/Cli.py:455 +msgid "Pause the server" +msgstr "" + +#: pyload/cli/Cli.py:456 +msgid "continue downloads" +msgstr "" + +#: pyload/cli/Cli.py:457 +msgid "Toggle pause/unpause" +msgstr "" + +#: pyload/cli/Cli.py:458 +msgid "kill server" +msgstr "" + +#: pyload/cli/Cli.py:460 +msgid "List of commands:" +msgstr "" + +#: pyload/cli/Cli.py:473 +msgid "Couldn't write user config file" +msgstr "" + +#: pyload/cli/Cli.py:548 pyload/cli/Cli.py:580 +msgid "You need py-openssl to connect to this pyLoad core." +msgstr "" + +#: pyload/cli/Cli.py:555 +msgid "Address: " +msgstr "" + +#: pyload/cli/Cli.py:556 +msgid "Port: " +msgstr "" + +#: pyload/cli/Cli.py:557 +msgid "Username: " +msgstr "" + +#: pyload/cli/Cli.py:561 +msgid "Password: " +msgstr "" + +#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575 +msgid "Login data is wrong." +msgstr "" + +#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577 +#, python-format +msgid "Could not establish connection to %(addr)s:%(port)s." +msgstr "" + +#: pyload/cli/Cli.py:582 +msgid "Interactive mode ignored since you passed some commands." +msgstr "" + diff --git a/locale/zh/LC_MESSAGES/core.po b/locale/zh/LC_MESSAGES/core.po new file mode 100644 index 000000000..e3dfcfcf8 --- /dev/null +++ b/locale/zh/LC_MESSAGES/core.po @@ -0,0 +1,630 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Chinese Traditional\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/AddonManager.py:62 +#, python-format +msgid "Error when executing %s" +msgstr "" + +#: pyload/AddonManager.py:93 +#, python-format +msgid "Failed activating %(name)s" +msgstr "" + +#: pyload/AddonManager.py:96 +#, python-format +msgid "Activated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:97 +#, python-format +msgid "Deactivated addons: %s" +msgstr "" + +#: pyload/AddonManager.py:153 +msgid "Activating Plugins..." +msgstr "" + +#: pyload/AddonManager.py:162 +msgid "Deactivating Plugins..." +msgstr "" + +#: pyload/web/ServerThread.py:49 +msgid "SSL certificates not found." +msgstr "" + +#: pyload/web/ServerThread.py:53 +msgid "WebUI built is not available" +msgstr "" + +#: pyload/web/ServerThread.py:55 +msgid "Running webUI in development mode" +msgstr "" + +#: pyload/web/ServerThread.py:73 +msgid "Failed starting webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:107 +msgid "Failed importing webserver: " +msgstr "" + +#: pyload/web/ServerThread.py:125 +msgid "This server offers no SSL, please consider using threaded instead" +msgstr "" + +#: pyload/web/ServerThread.py:139 +#, python-format +msgid "Starting %(name)s webserver: %(host)s:%(port)d" +msgstr "" + +#: pyload/config/default.py:14 +msgid "Remote" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Description" +msgstr "" + +#: pyload/config/default.py:14 pyload/config/default.py:21 +#: pyload/config/default.py:30 pyload/config/default.py:42 +#: pyload/config/default.py:53 pyload/config/default.py:60 +#: pyload/config/default.py:73 pyload/config/default.py:83 +#: pyload/config/default.py:91 pyload/config/default.py:103 +msgid "Long description" +msgstr "" + +#: pyload/config/default.py:16 pyload/config/default.py:56 +#: pyload/config/default.py:63 +msgid "Activated" +msgstr "" + +#: pyload/config/default.py:17 pyload/config/default.py:69 +#: pyload/config/default.py:80 +msgid "Port" +msgstr "" + +#: pyload/config/default.py:18 pyload/config/default.py:77 +msgid "Address" +msgstr "" + +#: pyload/config/default.py:21 +msgid "Log" +msgstr "" + +#: pyload/config/default.py:23 +msgid "Size in kb" +msgstr "" + +#: pyload/config/default.py:24 +msgid "Folder" +msgstr "" + +#: pyload/config/default.py:25 +msgid "File Log" +msgstr "" + +#: pyload/config/default.py:26 +msgid "Count" +msgstr "" + +#: pyload/config/default.py:27 +msgid "Log Rotate" +msgstr "" + +#: pyload/config/default.py:30 +msgid "Permissions" +msgstr "" + +#: pyload/config/default.py:32 +msgid "Groupname" +msgstr "" + +#: pyload/config/default.py:33 +msgid "Change Group and User of Downloads" +msgstr "" + +#: pyload/config/default.py:34 +msgid "Change file mode of downloads" +msgstr "" + +#: pyload/config/default.py:35 pyload/config/default.py:75 +msgid "Username" +msgstr "" + +#: pyload/config/default.py:36 +msgid "Filemode for Downloads" +msgstr "" + +#: pyload/config/default.py:37 +msgid "Change group of running process" +msgstr "" + +#: pyload/config/default.py:38 +msgid "Folder Permission mode" +msgstr "" + +#: pyload/config/default.py:39 +msgid "Change user of running process" +msgstr "" + +#: pyload/config/default.py:42 +msgid "General" +msgstr "" + +#: pyload/config/default.py:44 +msgid "Language" +msgstr "" + +#: pyload/config/default.py:45 +msgid "Download Folder" +msgstr "" + +#: pyload/config/default.py:46 +msgid "Use Checksum" +msgstr "" + +#: pyload/config/default.py:47 +msgid "Create folder for each package" +msgstr "" + +#: pyload/config/default.py:48 +msgid "Debug Mode" +msgstr "" + +#: pyload/config/default.py:49 +msgid "Min Free Space (MB)" +msgstr "" + +#: pyload/config/default.py:50 +msgid "CPU Priority" +msgstr "" + +#: pyload/config/default.py:53 +msgid "SSL" +msgstr "" + +#: pyload/config/default.py:55 +msgid "SSL Certificate" +msgstr "" + +#: pyload/config/default.py:57 +msgid "SSL Key" +msgstr "" + +#: pyload/config/default.py:60 +msgid "Webinterface" +msgstr "" + +#: pyload/config/default.py:62 +msgid "Template" +msgstr "" + +#: pyload/config/default.py:64 +msgid "Path Prefix" +msgstr "" + +#: pyload/config/default.py:65 +msgid "Server" +msgstr "" + +#: pyload/config/default.py:66 +msgid "Favor specific server" +msgstr "" + +#: pyload/config/default.py:67 +msgid "IP" +msgstr "" + +#: pyload/config/default.py:68 +msgid "Use HTTPS" +msgstr "" + +#: pyload/config/default.py:70 +msgid "Development mode" +msgstr "" + +#: pyload/config/default.py:73 +msgid "Proxy" +msgstr "" + +#: pyload/config/default.py:76 +msgid "Use Proxy" +msgstr "" + +#: pyload/config/default.py:78 +msgid "Password" +msgstr "" + +#: pyload/config/default.py:79 +msgid "Protocol" +msgstr "" + +#: pyload/config/default.py:83 +msgid "Reconnect" +msgstr "" + +#: pyload/config/default.py:85 pyload/config/default.py:106 +msgid "End" +msgstr "" + +#: pyload/config/default.py:86 +msgid "Use Reconnect" +msgstr "" + +#: pyload/config/default.py:87 +msgid "Method" +msgstr "" + +#: pyload/config/default.py:88 pyload/config/default.py:105 +msgid "Start" +msgstr "" + +#: pyload/config/default.py:91 +msgid "Download" +msgstr "" + +#: pyload/config/default.py:93 +msgid "Max Parallel Downloads" +msgstr "" + +#: pyload/config/default.py:94 +msgid "Limit Download Speed" +msgstr "" + +#: pyload/config/default.py:95 +msgid "Download interface to bind (ip or Name)" +msgstr "" + +#: pyload/config/default.py:96 +msgid "Skip already existing files" +msgstr "" + +#: pyload/config/default.py:97 +msgid "Max Download Speed in kb/s" +msgstr "" + +#: pyload/config/default.py:98 +msgid "Allow IPv6" +msgstr "" + +#: pyload/config/default.py:99 +msgid "Max connections for one download" +msgstr "" + +#: pyload/config/default.py:100 +msgid "Restart failed downloads on startup" +msgstr "" + +#: pyload/config/default.py:103 +msgid "Download Time" +msgstr "" + +#: pyload/network/HTTPDownload.py:249 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/api/DownloadApi.py:44 +#, python-format +msgid "Added package %(name)s as folder %(folder)s" +msgstr "" + +#: pyload/api/DownloadApi.py:95 +#, python-format +msgid "Added %d links to package" +msgstr "" + +#: pyload/AccountManager.py:69 +#, python-format +msgid "Unknown account plugin %s" +msgstr "" + +#: pyload/interaction/InteractionManager.py:88 +msgid "Query" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Captcha request" +msgstr "" + +#: pyload/interaction/InteractionManager.py:111 +msgid "Please solve the captcha." +msgstr "" + +#: pyload/remote/RemoteManager.py:35 +#, python-format +msgid "Remote backend error: %s" +msgstr "" + +#: pyload/remote/RemoteManager.py:80 +#, python-format +msgid "Starting %(name)s: %(addr)s:%(port)s" +msgstr "" + +#: pyload/remote/RemoteManager.py:82 +#, python-format +msgid "Failed loading backend %(name)s | %(error)s" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "none" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "offline" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "online" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "queued" +msgstr "" + +#: pyload/FileManager.py:53 +msgid "paused" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "finished" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "skipped" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "failed" +msgstr "" + +#: pyload/FileManager.py:54 +msgid "starting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "waiting" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "downloading" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "temp. offline" +msgstr "" + +#: pyload/FileManager.py:55 +msgid "aborted" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "decrypting" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "processing" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "custom" +msgstr "" + +#: pyload/FileManager.py:56 +msgid "unknown" +msgstr "" + +#: pyload/FileManager.py:426 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/Api.py:152 +#, python-format +msgid "User '%s' tries to log in" +msgstr "" + +#: pyload/Core.py:195 +msgid "Received Quit signal" +msgstr "" + +#: pyload/Core.py:323 +#, python-format +msgid "pyLoad already running with pid %s" +msgstr "" + +#: pyload/Core.py:337 +#, python-format +msgid "Failed changing group: %s" +msgstr "" + +#: pyload/Core.py:347 +#, python-format +msgid "Failed changing user: %s" +msgstr "" + +#: pyload/Core.py:358 +msgid "Starting" +msgstr "" + +#: pyload/Core.py:359 +#, python-format +msgid "Using home directory: %s" +msgstr "" + +#: pyload/Core.py:373 +msgid "All links removed" +msgstr "" + +#: pyload/Core.py:403 +#, python-format +msgid "Download time: %s" +msgstr "" + +#: pyload/Core.py:418 +#, python-format +msgid "Free space: %s" +msgstr "" + +#: pyload/Core.py:438 +msgid "Activating Accounts..." +msgstr "" + +#: pyload/Core.py:443 +msgid "Restarting failed downloads..." +msgstr "" + +#: pyload/Core.py:451 +msgid "pyLoad is up and running" +msgstr "" + +#: pyload/Core.py:474 +msgid "restarting pyLoad" +msgstr "" + +#: pyload/Core.py:478 +msgid "pyLoad quits" +msgstr "" + +#: pyload/Core.py:564 +msgid "shutting down..." +msgstr "" + +#: pyload/Core.py:579 +msgid "error while shutting down" +msgstr "" + +#: pyload/Core.py:661 +msgid "killed pyLoad from terminal" +msgstr "" + +#: pyload/database/DatabaseBackend.py:167 +msgid "Database was deleted due to incompatible version." +msgstr "" + +#: pyload/threads/DecrypterThread.py:50 +msgid "Decrypting failed" +msgstr "" + +#: pyload/threads/DecrypterThread.py:73 +#, python-format +msgid "Decrypted %(count)d links into package %(name)s" +msgstr "" + +#: pyload/threads/DecrypterThread.py:80 +msgid "No links decrypted" +msgstr "" + +#: pyload/threads/InfoThread.py:147 +#, python-format +msgid "Info Fetching for %(name)s failed | %(err)s" +msgstr "" + +#: pyload/threads/ThreadManager.py:155 +#, python-format +msgid "Reconnect Failed: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:192 +msgid "Reconnect script not found!" +msgstr "" + +#: pyload/threads/ThreadManager.py:198 +msgid "Starting reconnect" +msgstr "" + +#: pyload/threads/ThreadManager.py:212 +msgid "Failed executing reconnect script!" +msgstr "" + +#: pyload/threads/ThreadManager.py:224 +#, python-format +msgid "Reconnected, new IP: %s" +msgstr "" + +#: pyload/threads/ThreadManager.py:298 +msgid "Not enough space left on device" +msgstr "" + +#: pyload/threads/DownloadThread.py:64 +#, python-format +msgid "Download starts: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:70 +#, python-format +msgid "Download finished: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:75 +#, python-format +msgid "Plugin %s is missing a function." +msgstr "" + +#: pyload/threads/DownloadThread.py:83 pyload/threads/DownloadThread.py:147 +#, python-format +msgid "Download aborted: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:103 +#, python-format +msgid "Download restarted: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:113 +#, python-format +msgid "Download is offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:116 pyload/threads/DownloadThread.py:187 +#, python-format +msgid "Download is temporary offline: %s" +msgstr "" + +#: pyload/threads/DownloadThread.py:119 pyload/threads/DownloadThread.py:192 +#, python-format +msgid "Download failed: %(name)s | %(msg)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:136 +msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry." +msgstr "" + +#: pyload/threads/DownloadThread.py:171 +#, python-format +msgid "Download skipped: %(name)s due to %(plugin)s" +msgstr "" + +#: pyload/threads/DownloadThread.py:188 +msgid "Internal Server Error" +msgstr "" + +#: pyload/threads/AddonThread.py:55 +msgid "An Error occurred" +msgstr "" + +#: pyload/PluginManager.py:316 +#, python-format +msgid "Error importing %(name)s: %(msg)s" +msgstr "" + +#: pyload/utils/JsEngine.py:188 +msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino" +msgstr "" + diff --git a/locale/zh/LC_MESSAGES/plugins.po b/locale/zh/LC_MESSAGES/plugins.po new file mode 100644 index 000000000..bf281c6f4 --- /dev/null +++ b/locale/zh/LC_MESSAGES/plugins.po @@ -0,0 +1,424 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-20 22:13+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Chinese Traditional\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/plugins/network/CurlDownload.py:241 +#, python-format +msgid "Download chunks failed, fallback to single connection | %s" +msgstr "" + +#: pyload/plugins/Base.py:329 +msgid "Pil and tesseract not installed and no Client connected for captcha decrypting" +msgstr "" + +#: pyload/plugins/Base.py:333 +msgid "No captcha result obtained in appropriate time." +msgstr "" + +#: pyload/plugins/Hoster.py:282 pyload/plugins/Hoster.py:319 +#: pyload/plugins/hoster/ARD.py:76 +#, python-format +msgid "Setting User and Group failed: %s" +msgstr "" + +#: pyload/plugins/Crypter.py:136 +msgid "Not existing file or unsupported protocol" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:96 +msgid "Rapidshare: Traffic Share (direct download)" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:123 +#: pyload/plugins/hoster/RapidshareCom.py:189 +msgid "Already downloading from this ip address, waiting 60 seconds" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:127 +msgid "Invalid Auth Code, download will be restarted" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:193 +msgid "RapidShareCom: No free slots" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:196 +msgid "You need a premium account for this file" +msgstr "" + +#: pyload/plugins/hoster/RapidshareCom.py:198 +msgid "Filename reported invalid" +msgstr "" + +#: pyload/plugins/hoster/RealdebridCom.py:40 +#: pyload/plugins/hoster/Premium4Me.py:27 +#: pyload/plugins/hoster/MultiDebridCom.py:39 +#: pyload/plugins/hoster/ZeveraCom.py:24 +#: pyload/plugins/hoster/AlldebridCom.py:37 +#: pyload/plugins/hoster/RehostTo.py:25 pyload/plugins/hoster/ReloadCc.py:22 +#: pyload/plugins/hoster/DebridItaliaCom.py:38 +#: pyload/plugins/hoster/PremiumizeMe.py:19 +#, python-format +msgid "Please enter your %s account or deactivate this plugin" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:55 +msgid "Decryption failed" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:99 +msgid "No file key provided in the URL" +msgstr "" + +#: pyload/plugins/hoster/MegaNz.py:111 +msgid "Error code:" +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:97 +msgid "Parallel download error, now waiting 60s." +msgstr "" + +#: pyload/plugins/hoster/FileserveCom.py:208 +msgid "Not logged in." +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:135 +msgid "API key invalid" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:160 +#, python-format +msgid "%s: Not enough traffic left" +msgstr "" + +#: pyload/plugins/hoster/UploadedTo.py:163 +msgid "Traffic exceeded" +msgstr "" + +#: pyload/plugins/hoster/BasePlugin.py:63 +msgid "Authorization required (username:password)" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:143 +#: pyload/plugins/hoster/NetloadIn.py:167 +msgid "File temporarily not available" +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:180 +#, python-format +msgid "Netload: waiting between downloads %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:209 +#, python-format +msgid "Netload: waiting for captcha %d s." +msgstr "" + +#: pyload/plugins/hoster/NetloadIn.py:248 +msgid "Downloaded File was empty" +msgstr "" + +#: pyload/plugins/hoster/FilesMailRu.py:98 +#, python-format +msgid "There was HTML Code in the Downloaded File(%s)...redirect error? The Download will be restarted." +msgstr "" + +#: pyload/plugins/hoster/XHamsterCom.py:75 +#: pyload/plugins/hoster/XHamsterCom.py:84 +#: pyload/plugins/hoster/XHamsterCom.py:87 +#, python-format +msgid "long_url: %s" +msgstr "" + +#: pyload/plugins/Account.py:117 pyload/plugins/Account.py:123 +#, python-format +msgid "Could not login with account %(user)s | %(msg)s" +msgstr "" + +#: pyload/plugins/Account.py:118 +msgid "Wrong Password" +msgstr "" + +#: pyload/plugins/Account.py:184 +#, python-format +msgid "Get Account Info for %s" +msgstr "" + +#: pyload/plugins/Account.py:193 +#, python-format +msgid "Error: %s" +msgstr "" + +#: pyload/plugins/Account.py:242 +#, python-format +msgid "Your Time %s has a wrong format, use: 1:22-3:44" +msgstr "" + +#: pyload/plugins/Account.py:265 +#, python-format +msgid "Account %s has not enough traffic, checking again in 30min" +msgstr "" + +#: pyload/plugins/Account.py:273 +#, python-format +msgid "Account %s is expired, checking again in 1h" +msgstr "" + +#: pyload/plugins/Account.py:290 +#, python-format +msgid "Login with %s" +msgstr "" + +#: pyload/plugins/Addon.py:118 +#, python-format +msgid "Error executing addons: %s" +msgstr "" + +#: pyload/plugins/accounts/BitshareCom.py:36 +msgid "Activate direct Download in your Bitshare Account" +msgstr "" + +#: pyload/plugins/crypter/SerienjunkiesOrg.py:128 +msgid "Downloadlimit reached" +msgstr "" + +#: pyload/plugins/addons/Premium4Me.py:30 +msgid "Please add your premium.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:53 +#, python-format +msgid "Installed scripts for %s: " +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:69 +msgid "Script not executable:" +msgstr "" + +#: pyload/plugins/addons/ExternalScripts.py:79 +#, python-format +msgid "Error in %(script)s: %(error)s" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:72 +#: pyload/plugins/addons/Captcha9kw.py:56 +#: pyload/plugins/addons/ExpertDecoders.py:49 +#, python-format +msgid "%s credits left" +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:120 +msgid "Could not send response." +msgstr "" + +#: pyload/plugins/addons/CaptchaTrader.py:138 +msgid "Your CaptchaTrader Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:89 +#, python-format +msgid "New CaptchaID from upload: %s : %s" +msgstr "" + +#: pyload/plugins/addons/Captcha9kw.py:123 +msgid "Your Captcha 9kw.eu Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/RehostTo.py:34 +msgid "Please add your rehost.to account first and restart pyLoad" +msgstr "" + +#: pyload/plugins/addons/HotFolder.py:82 +#, python-format +msgid "Added %s from HotFolder" +msgstr "" + +#: pyload/plugins/addons/ClickAndLoad.py:74 +msgid "Click'N'Load: Port 9666 already in use" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:82 +#: pyload/plugins/addons/IRCInterface.py:75 +#, python-format +msgid "Package finished: %s" +msgstr "" + +#: pyload/plugins/addons/XMPPInterface.py:90 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s" +msgstr "" + +#: pyload/plugins/addons/ExpertDecoders.py:98 +msgid "Your ExpertDecoders Account has not enough credits" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:73 +msgid "*** Plugins have been updated, please restart pyLoad ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:75 +msgid "Plugins updated and reloaded" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:78 +msgid "No plugin updates available" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:95 +msgid "No Updates for pyLoad" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:100 +#, python-format +msgid "*** New pyLoad Version %s available ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:101 +msgid "*** Get it here: http://pyload.org/download ***" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:104 +msgid "Not able to connect server for updates" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:148 +#, python-format +msgid "New version of %(type)s|%(name)s : %(version).2f" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:157 +#: pyload/plugins/addons/UpdateManager.py:162 +#, python-format +msgid "Error when updating %s" +msgstr "" + +#: pyload/plugins/addons/UpdateManager.py:162 +msgid "Version mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:92 +#, python-format +msgid "No %s installed" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:94 +#: pyload/plugins/addons/ExtractArchive.py:99 +#, python-format +msgid "Could not activate %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:104 +msgid "Activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:106 +msgid "No Extract plugins activated" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:118 +#, python-format +msgid "Package %s queued for later extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:143 +#, python-format +msgid "Check package %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:183 +#, python-format +msgid "Extract to %s" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:197 +msgid "No files found to extract" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:203 +msgid "extracting" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:214 +msgid "Password protected" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:234 +msgid "Wrong password" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:242 +#, python-format +msgid "Deleting %s files" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:247 +msgid "Extracting finished" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:254 +msgid "Archive Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:256 +msgid "CRC Mismatch" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:260 +msgid "Unknown Error" +msgstr "" + +#: pyload/plugins/addons/ExtractArchive.py:312 +msgid "Setting User and Group failed" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:36 +msgid "Crypter list not found" +msgstr "" + +#: pyload/plugins/addons/LinkdecrypterCom.py:50 +msgid "Crypter list is empty" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:82 +#, python-format +msgid "Download finished: %(name)s @ %(plugin)s " +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:94 +#, python-format +msgid "New Captcha Request: %s" +msgstr "" + +#: pyload/plugins/addons/IRCInterface.py:95 +#, python-format +msgid "Answer with 'c %s text on the captcha'" +msgstr "" + +#: pyload/plugins/addons/PremiumizeMe.py:46 +msgid "Please add a valid premiumize.me account first and restart pyLoad." +msgstr "" + +#: pyload/plugins/addons/CaptchaBrotherhood.py:69 +#, python-format +msgid "%d credits left" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:29 +#, python-format +msgid "Activated %s" +msgstr "" + +#: pyload/plugins/addons/MultiHoster.py:47 +msgid "No Hoster loaded" +msgstr "" + diff --git a/locale/zh/LC_MESSAGES/setup.po b/locale/zh/LC_MESSAGES/setup.po new file mode 100644 index 000000000..6176937cd --- /dev/null +++ b/locale/zh/LC_MESSAGES/setup.po @@ -0,0 +1,376 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-07-17 15:39+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Chinese Traditional\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/Setup.py:72 +msgid "Would you like to configure pyLoad via Webinterface?" +msgstr "" + +#: pyload/Setup.py:73 +msgid "You need a Browser and a connection to this PC for it." +msgstr "" + +#: pyload/Setup.py:74 +msgid "Url would be: http://hostname:8000/" +msgstr "" + +#: pyload/Setup.py:75 +msgid "Start initial webinterface for configuration?" +msgstr "" + +#: pyload/Setup.py:86 +msgid "Welcome to the pyLoad Configuration Assistent." +msgstr "" + +#: pyload/Setup.py:87 +msgid "It will check your system and make a basic setup in order to run pyLoad." +msgstr "" + +#: pyload/Setup.py:89 +msgid "The value in brackets [] always is the default value," +msgstr "" + +#: pyload/Setup.py:90 +msgid "in case you don't want to change it or you are unsure what to choose, just hit enter." +msgstr "" + +#: pyload/Setup.py:92 +msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore." +msgstr "" + +#: pyload/Setup.py:93 +msgid "If you have any problems with this assistent hit CTRL+C," +msgstr "" + +#: pyload/Setup.py:94 +msgid "to abort and don't let him start with pyLoadCore automatically anymore." +msgstr "" + +#: pyload/Setup.py:96 +msgid "When you are ready for system check, hit enter." +msgstr "" + +#: pyload/Setup.py:103 +msgid "Features missing: " +msgstr "" + +#: pyload/Setup.py:107 +msgid "no py-crypto available" +msgstr "" + +#: pyload/Setup.py:108 +msgid "You need this if you want to decrypt container files." +msgstr "" + +#: pyload/Setup.py:112 +msgid "no SSL available" +msgstr "" + +#: pyload/Setup.py:113 +msgid "This is needed if you want to establish a secure connection to core or webinterface." +msgstr "" + +#: pyload/Setup.py:114 +msgid "If you only want to access locally to pyLoad ssl is not useful." +msgstr "" + +#: pyload/Setup.py:118 +msgid "no Captcha Recognition available" +msgstr "" + +#: pyload/Setup.py:119 +msgid "Only needed for some hosters and as freeuser." +msgstr "" + +#: pyload/Setup.py:123 +msgid "no JavaScript engine found" +msgstr "" + +#: pyload/Setup.py:124 +msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino" +msgstr "" + +#: pyload/Setup.py:126 +msgid "You can abort the setup now and fix some dependencies if you want." +msgstr "" + +#: pyload/Setup.py:128 +msgid "Continue with setup?" +msgstr "" + +#: pyload/Setup.py:134 +#, python-format +msgid "Do you want to change the config path? Current is %s" +msgstr "" + +#: pyload/Setup.py:136 +msgid "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." +msgstr "" + +#: pyload/Setup.py:137 +msgid "Change config path?" +msgstr "" + +#: pyload/Setup.py:143 +msgid "Do you want to configure login data and basic settings?" +msgstr "" + +#: pyload/Setup.py:144 +msgid "This is recommend for first run." +msgstr "" + +#: pyload/Setup.py:145 +msgid "Make basic setup?" +msgstr "" + +#: pyload/Setup.py:152 +msgid "Do you want to configure ssl?" +msgstr "" + +#: pyload/Setup.py:153 +msgid "Configure ssl?" +msgstr "" + +#: pyload/Setup.py:159 +msgid "Do you want to configure webinterface?" +msgstr "" + +#: pyload/Setup.py:160 +msgid "Configure webinterface?" +msgstr "" + +#: pyload/Setup.py:165 +msgid "Setup finished successfully." +msgstr "" + +#: pyload/Setup.py:166 +msgid "Hit enter to exit and restart pyLoad" +msgstr "" + +#: pyload/Setup.py:173 +msgid "Webinterface running for setup." +msgstr "" + +#: pyload/Setup.py:190 +msgid "## Basic Setup ##" +msgstr "" + +#: pyload/Setup.py:193 +msgid "The following logindata is valid for CLI, GUI and webinterface." +msgstr "" + +#: pyload/Setup.py:199 pyload/Setup.py:288 pyload/Setup.py:304 +msgid "Username" +msgstr "" + +#: pyload/Setup.py:205 +msgid "External clients (GUI, CLI or other) need remote access to work over the network." +msgstr "" + +#: pyload/Setup.py:206 +msgid "However, if you only want to use the webinterface you may disable it to save ram." +msgstr "" + +#: pyload/Setup.py:207 +msgid "Enable remote access" +msgstr "" + +#: pyload/Setup.py:211 +msgid "Language" +msgstr "" + +#: pyload/Setup.py:213 +msgid "Download folder" +msgstr "" + +#: pyload/Setup.py:214 +msgid "Max parallel downloads" +msgstr "" + +#: pyload/Setup.py:218 +msgid "Use Reconnect?" +msgstr "" + +#: pyload/Setup.py:221 +msgid "Reconnect script location" +msgstr "" + +#: pyload/Setup.py:226 +msgid "## Webinterface Setup ##" +msgstr "" + +#: pyload/Setup.py:229 +msgid "Activate webinterface?" +msgstr "" + +#: pyload/Setup.py:231 +msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally." +msgstr "" + +#: pyload/Setup.py:232 +msgid "Address" +msgstr "" + +#: pyload/Setup.py:233 +msgid "Port" +msgstr "" + +#: pyload/Setup.py:235 +msgid "pyLoad offers several server backends, now following a short explanation." +msgstr "" + +#: pyload/Setup.py:236 +msgid "Default server, this server offers SSL and is a good alternative to builtin." +msgstr "" + +#: pyload/Setup.py:238 +msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job." +msgstr "" + +#: pyload/Setup.py:239 +msgid "Very fast alternative written in C, requires libev and linux knowledge." +msgstr "" + +#: pyload/Setup.py:240 +msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it" +msgstr "" + +#: pyload/Setup.py:241 +msgid "and copy bjoern.so to pyload/lib" +msgstr "" + +#: pyload/Setup.py:245 +msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface" +msgstr "" + +#: pyload/Setup.py:246 +msgid "come back here and change the builtin server to the threaded one here." +msgstr "" + +#: pyload/Setup.py:248 +msgid "Server" +msgstr "" + +#: pyload/Setup.py:253 +msgid "## SSL Setup ##" +msgstr "" + +#: pyload/Setup.py:255 +msgid "Execute these commands from pyLoad config folder to make ssl certificates:" +msgstr "" + +#: pyload/Setup.py:261 +msgid "If you're done and everything went fine, you can activate ssl now." +msgstr "" + +#: pyload/Setup.py:262 +msgid "Activate SSL?" +msgstr "" + +#: pyload/Setup.py:278 +msgid "Select action" +msgstr "" + +#: pyload/Setup.py:279 +msgid "1 - Create/Edit user" +msgstr "" + +#: pyload/Setup.py:280 +msgid "2 - List users" +msgstr "" + +#: pyload/Setup.py:281 +msgid "3 - Remove user" +msgstr "" + +#: pyload/Setup.py:282 +msgid "4 - Quit" +msgstr "" + +#: pyload/Setup.py:294 +msgid "Users" +msgstr "" + +#: pyload/Setup.py:322 +msgid "Setting new configpath, current configuration will not be transferred!" +msgstr "" + +#: pyload/Setup.py:323 +msgid "Config path" +msgstr "" + +#: pyload/Setup.py:331 +msgid "Config path changed, setup will now close, please restart to go on." +msgstr "" + +#: pyload/Setup.py:332 +msgid "Press Enter to exit." +msgstr "" + +#: pyload/Setup.py:336 +#, python-format +msgid "Setting config path failed: %s" +msgstr "" + +#: pyload/Setup.py:347 +msgid "y" +msgstr "" + +#: pyload/Setup.py:349 +msgid "n" +msgstr "" + +#: pyload/Setup.py:373 +msgid "Password: " +msgstr "" + +#: pyload/Setup.py:377 +msgid "Password too short. Use at least 4 symbols." +msgstr "" + +#: pyload/Setup.py:380 +msgid "Password (again): " +msgstr "" + +#: pyload/Setup.py:386 +msgid "Passwords did not match." +msgstr "" + +#: pyload/Setup.py:397 +msgid "yes" +msgstr "" + +#: pyload/Setup.py:397 +msgid "true" +msgstr "" + +#: pyload/Setup.py:397 +msgid "t" +msgstr "" + +#: pyload/Setup.py:400 +msgid "no" +msgstr "" + +#: pyload/Setup.py:400 +msgid "false" +msgstr "" + +#: pyload/Setup.py:400 +msgid "f" +msgstr "" + +#: pyload/Setup.py:403 pyload/Setup.py:413 +msgid "Invalid Input" +msgstr "" + diff --git a/locale/zh/LC_MESSAGES/webUI.po b/locale/zh/LC_MESSAGES/webUI.po new file mode 100644 index 000000000..4a695943b --- /dev/null +++ b/locale/zh/LC_MESSAGES/webUI.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "Project-Id-Version: pyload\n" +"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n" +"POT-Creation-Date: 2013-08-10 11:58+0200\n" +"PO-Revision-Date: 2013-10-13 12:25-0400\n" +"Last-Translator: pyloadTeam <team@pyload.org>\n" +"Language-Team: Chinese Traditional\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.net\n" + +#: pyload/web/app/scripts/helpers/formatSize.js:9 +msgid "not available" +msgstr "" + +#: pyload/web/app/scripts/helpers/formatSize.js:11 +msgid "unlimited" +msgstr "" + +#: pyload/web/app/templates/default/admin.html:3 +#: pyload/web/app/templates/default/admin.html:4 +msgid "Admin" +msgstr "" + +#: pyload/web/app/templates/default/setup.html:3 +msgid "Setup" +msgstr "" + +#: pyload/web/app/templates/default/accounts/actionbar.html:2 +msgid "Add Account" +msgstr "" + +#: pyload/web/app/templates/default/accounts/layout.html:2 +msgid "Accounts" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:9 +msgid "Local" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:18 +msgid "Search" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:26 +msgid "Type" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:44 +#: pyload/web/app/templates/default/dashboard/actionbar.html:49 +msgid "All" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:50 +msgid "Finished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:51 +msgid "Unfinished" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/actionbar.html:52 +msgid "Failed" +msgstr "" + +#: pyload/web/app/templates/default/dashboard/select.html:1 +msgid "1 package" +msgid_plural "%d packages" +msgstr[0] "" + +#: pyload/web/app/templates/default/dashboard/select.html:4 +msgid "1 file" +msgid_plural "%d files" +msgstr[0] "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:2 +msgid "Add an account" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addAccount.html:7 +msgid "Please enter your account data" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:3 +msgid "Choose a plugin" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:9 +msgid "Please choose a plugin, which you want to configure" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:23 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:46 +msgid "Add" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/addPluginConfig.html:24 +#: pyload/web/app/templates/default/dialogs/linkgrabber.html:47 +#: pyload/web/app/templates/default/dialogs/interactionTask.html:35 +msgid "Close" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:2 +msgid "Please confirm" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:5 +msgid "Do you want to delete the selected items?" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:8 +msgid "Delete" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/confirmDelete.html:9 +msgid "Cancel" +msgstr "" + +#: pyload/web/app/templates/default/dialogs/interactionTask.html:34 +msgid "Submit" +msgstr "" + +#: pyload/web/app/templates/default/header/layout.html:12 +msgid "Running..." +msgstr "" + diff --git a/module/Api.py b/module/Api.py deleted file mode 100644 index f0bf5e264..000000000 --- a/module/Api.py +++ /dev/null @@ -1,1033 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from base64 import standard_b64encode -from os.path import join -from time import time -import re - -from PyFile import PyFile -from utils import freeSpace, compare_time -from common.packagetools import parseNames -from network.RequestFactory import getURL -from remote import activated - -if activated: - try: - from remote.thriftbackend.thriftgen.pyload.ttypes import * - from remote.thriftbackend.thriftgen.pyload.Pyload import Iface - BaseObject = TBase - except ImportError: - print "Thrift not imported" - from remote.socketbackend.ttypes import * -else: - from remote.socketbackend.ttypes import * - -# contains function names mapped to their permissions -# unlisted functions are for admins only -permMap = {} - -# decorator only called on init, never initialized, so has no effect on runtime -def permission(bits): - class _Dec(object): - def __new__(cls, func, *args, **kwargs): - permMap[func.__name__] = bits - return func - - return _Dec - - -urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE) - -class PERMS: - ALL = 0 # requires no permission, but login - ADD = 1 # can add packages - DELETE = 2 # can delete packages - STATUS = 4 # see and change server status - LIST = 16 # see queue and collector - MODIFY = 32 # moddify some attribute of downloads - DOWNLOAD = 64 # can download from webinterface - SETTINGS = 128 # can access settings - ACCOUNTS = 256 # can access accounts - LOGS = 512 # can see server logs - -class ROLE: - ADMIN = 0 #admin has all permissions implicit - USER = 1 - -def has_permission(userperms, perms): - # bytewise or perms before if needed - return perms == (userperms & perms) - - -class Api(Iface): - """ - **pyLoads API** - - This is accessible either internal via core.api or via thrift backend. - - see Thrift specification file remote/thriftbackend/pyload.thrift\ - for information about data structures and what methods are usuable with rpc. - - Most methods requires specific permissions, please look at the source code if you need to know.\ - These can be configured via webinterface. - Admin user have all permissions, and are the only ones who can access the methods with no specific permission. - """ - - EXTERNAL = Iface # let the json api know which methods are external - - def __init__(self, core): - self.core = core - - def _convertPyFile(self, p): - f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"], - p["format_size"], p["status"], p["statusmsg"], - p["package"], p["error"], p["order"]) - return f - - def _convertConfigFormat(self, c): - sections = {} - for sectionName, sub in c.iteritems(): - section = ConfigSection(sectionName, sub["desc"]) - items = [] - for key, data in sub.iteritems(): - if key in ("desc", "outline"): - continue - item = ConfigItem() - item.name = key - item.description = data["desc"] - item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"] - item.type = data["type"] - items.append(item) - section.items = items - sections[sectionName] = section - if "outline" in sub: - section.outline = sub["outline"] - return sections - - @permission(PERMS.SETTINGS) - def getConfigValue(self, category, option, section="core"): - """Retrieve config value. - - :param category: name of category, or plugin - :param option: config option - :param section: 'plugin' or 'core' - :return: config value as string - """ - if section == "core": - value = self.core.config[category][option] - else: - value = self.core.config.getPlugin(category, option) - - return str(value) if not isinstance(value, basestring) else value - - @permission(PERMS.SETTINGS) - def setConfigValue(self, category, option, value, section="core"): - """Set new config value. - - :param category: - :param option: - :param value: new config value - :param section: 'plugin' or 'core - """ - self.core.hookManager.dispatchEvent("configChanged", category, option, value, section) - - if section == "core": - self.core.config[category][option] = value - - if option in ("limit_speed", "max_speed"): #not so nice to update the limit - self.core.requestFactory.updateBucket() - - elif section == "plugin": - self.core.config.setPlugin(category, option, value) - - @permission(PERMS.SETTINGS) - def getConfig(self): - """Retrieves complete config of core. - - :return: list of `ConfigSection` - """ - return self._convertConfigFormat(self.core.config.config) - - def getConfigDict(self): - """Retrieves complete config in dict format, not for RPC. - - :return: dict - """ - return self.core.config.config - - @permission(PERMS.SETTINGS) - def getPluginConfig(self): - """Retrieves complete config for all plugins. - - :return: list of `ConfigSection` - """ - return self._convertConfigFormat(self.core.config.plugin) - - def getPluginConfigDict(self): - """Plugin config as dict, not for RPC. - - :return: dict - """ - return self.core.config.plugin - - - @permission(PERMS.STATUS) - def pauseServer(self): - """Pause server: Tt wont start any new downloads, but nothing gets aborted.""" - self.core.threadManager.pause = True - - @permission(PERMS.STATUS) - def unpauseServer(self): - """Unpause server: New Downloads will be started.""" - self.core.threadManager.pause = False - - @permission(PERMS.STATUS) - def togglePause(self): - """Toggle pause state. - - :return: new pause state - """ - self.core.threadManager.pause ^= True - return self.core.threadManager.pause - - @permission(PERMS.STATUS) - def toggleReconnect(self): - """Toggle reconnect activation. - - :return: new reconnect state - """ - self.core.config["reconnect"]["activated"] ^= True - return self.core.config["reconnect"]["activated"] - - @permission(PERMS.LIST) - def statusServer(self): - """Some general information about the current status of pyLoad. - - :return: `ServerStatus` - """ - serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()), - self.core.files.getQueueCount(), self.core.files.getFileCount(), 0, - not self.core.threadManager.pause and self.isTimeDownload(), - self.core.config['reconnect']['activated'] and self.isTimeReconnect()) - - for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]: - serverStatus.speed += pyfile.getSpeed() #bytes/s - - return serverStatus - - @permission(PERMS.STATUS) - def freeSpace(self): - """Available free space at download directory in bytes""" - return freeSpace(self.core.config["general"]["download_folder"]) - - @permission(PERMS.ALL) - def getServerVersion(self): - """pyLoad Core version """ - return self.core.version - - def kill(self): - """Clean way to quit pyLoad""" - self.core.do_kill = True - - def restart(self): - """Restart pyload core""" - self.core.do_restart = True - - @permission(PERMS.LOGS) - def getLog(self, offset=0): - """Returns most recent log entries. - - :param offset: line offset - :return: List of log entries - """ - filename = join(self.core.config['log']['log_folder'], 'log.txt') - try: - fh = open(filename, "r") - lines = fh.readlines() - fh.close() - if offset >= len(lines): - return [] - return lines[offset:] - except: - return ['No log available'] - - @permission(PERMS.STATUS) - def isTimeDownload(self): - """Checks if pyload will start new downloads according to time in config. - - :return: bool - """ - start = self.core.config['downloadTime']['start'].split(":") - end = self.core.config['downloadTime']['end'].split(":") - return compare_time(start, end) - - @permission(PERMS.STATUS) - def isTimeReconnect(self): - """Checks if pyload will try to make a reconnect - - :return: bool - """ - start = self.core.config['reconnect']['startTime'].split(":") - end = self.core.config['reconnect']['endTime'].split(":") - return compare_time(start, end) and self.core.config["reconnect"]["activated"] - - @permission(PERMS.LIST) - def statusDownloads(self): - """ Status off all currently running downloads. - - :return: list of `DownloadStatus` - """ - data = [] - for pyfile in self.core.threadManager.getActiveFiles(): - if not isinstance(pyfile, PyFile): - continue - - data.append(DownloadInfo( - pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(), - pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(), - pyfile.status, pyfile.getStatusName(), pyfile.formatWait(), - pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname)) - - return data - - @permission(PERMS.ADD) - def addPackage(self, name, links, dest=Destination.Queue): - """Adds a package, with links to desired destination. - - :param name: name of the new package - :param links: list of urls - :param dest: `Destination` - :return: package id of the new package - """ - if self.core.config['general']['folder_per_package']: - folder = name - else: - folder = "" - - folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_") - - pid = self.core.files.addPackage(name, folder, dest) - - self.core.files.addLinks(links, pid) - - self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)}) - - self.core.files.save() - - return pid - - @permission(PERMS.ADD) - def parseURLs(self, html=None, url=None): - """Parses html content or any arbitaty text for links and returns result of `checkURLs` - - :param html: html source - :return: - """ - urls = [] - - if html: - urls += [x[0] for x in urlmatcher.findall(html)] - - if url: - page = getURL(url) - urls += [x[0] for x in urlmatcher.findall(page)] - - # remove duplicates - return self.checkURLs(set(urls)) - - - @permission(PERMS.ADD) - def checkURLs(self, urls): - """ Gets urls and returns pluginname mapped to list of matches urls. - - :param urls: - :return: {plugin: urls} - """ - data = self.core.pluginManager.parseUrls(urls) - plugins = {} - - for url, plugin in data: - if plugin in plugins: - plugins[plugin].append(url) - else: - plugins[plugin] = [url] - - return plugins - - @permission(PERMS.ADD) - def checkOnlineStatus(self, urls): - """ initiates online status check - - :param urls: - :return: initial set of data as `OnlineCheck` instance containing the result id - """ - data = self.core.pluginManager.parseUrls(urls) - - rid = self.core.threadManager.createResultThread(data, False) - - tmp = [(url, (url, OnlineStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data] - data = parseNames(tmp) - result = {} - - for k, v in data.iteritems(): - for url, status in v: - status.packagename = k - result[url] = status - - return OnlineCheck(rid, result) - - @permission(PERMS.ADD) - def checkOnlineStatusContainer(self, urls, container, data): - """ checks online status of urls and a submited container file - - :param urls: list of urls - :param container: container file name - :param data: file content - :return: online check - """ - th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb") - th.write(str(data)) - th.close() - - return self.checkOnlineStatus(urls + [th.name]) - - @permission(PERMS.ADD) - def pollResults(self, rid): - """ Polls the result available for ResultID - - :param rid: `ResultID` - :return: `OnlineCheck`, if rid is -1 then no more data available - """ - result = self.core.threadManager.getInfoResult(rid) - - if "ALL_INFO_FETCHED" in result: - del result["ALL_INFO_FETCHED"] - return OnlineCheck(-1, result) - else: - return OnlineCheck(rid, result) - - - @permission(PERMS.ADD) - def generatePackages(self, links): - """ Parses links, generates packages names from urls - - :param links: list of urls - :return: package names mapped to urls - """ - result = parseNames((x, x) for x in links) - return result - - @permission(PERMS.ADD) - def generateAndAddPackages(self, links, dest=Destination.Queue): - """Generates and add packages - - :param links: list of urls - :param dest: `Destination` - :return: list of package ids - """ - return [self.addPackage(name, urls, dest) for name, urls - in self.generatePackages(links).iteritems()] - - @permission(PERMS.ADD) - def checkAndAddPackages(self, links, dest=Destination.Queue): - """Checks online status, retrieves names, and will add packages.\ - Because of this packages are not added immediatly, only for internal use. - - :param links: list of urls - :param dest: `Destination` - :return: None - """ - data = self.core.pluginManager.parseUrls(links) - self.core.threadManager.createResultThread(data, True) - - - @permission(PERMS.LIST) - def getPackageData(self, pid): - """Returns complete information about package, and included files. - - :param pid: package id - :return: `PackageData` with .links attribute - """ - data = self.core.files.getPackageData(int(pid)) - - if not data: - raise PackageDoesNotExists(pid) - - pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"], - data["queue"], data["order"], - links=[self._convertPyFile(x) for x in data["links"].itervalues()]) - - return pdata - - @permission(PERMS.LIST) - def getPackageInfo(self, pid): - """Returns information about package, without detailed information about containing files - - :param pid: package id - :return: `PackageData` with .fid attribute - """ - data = self.core.files.getPackageData(int(pid)) - - if not data: - raise PackageDoesNotExists(pid) - - pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"], - data["queue"], data["order"], - fids=[int(x) for x in data["links"]]) - - return pdata - - @permission(PERMS.LIST) - def getFileData(self, fid): - """Get complete information about a specific file. - - :param fid: file id - :return: `FileData` - """ - info = self.core.files.getFileData(int(fid)) - if not info: - raise FileDoesNotExists(fid) - - fdata = self._convertPyFile(info.values()[0]) - return fdata - - @permission(PERMS.DELETE) - def deleteFiles(self, fids): - """Deletes several file entries from pyload. - - :param fids: list of file ids - """ - for id in fids: - self.core.files.deleteLink(int(id)) - - self.core.files.save() - - @permission(PERMS.DELETE) - def deletePackages(self, pids): - """Deletes packages and containing links. - - :param pids: list of package ids - """ - for id in pids: - self.core.files.deletePackage(int(id)) - - self.core.files.save() - - @permission(PERMS.LIST) - def getQueue(self): - """Returns info about queue and packages, **not** about files, see `getQueueData` \ - or `getPackageData` instead. - - :return: list of `PackageInfo` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - pack["linkstotal"]) - for pack in self.core.files.getInfoData(Destination.Queue).itervalues()] - - @permission(PERMS.LIST) - def getQueueData(self): - """Return complete data about everything in queue, this is very expensive use it sparely.\ - See `getQueue` for alternative. - - :return: list of `PackageData` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - links=[self._convertPyFile(x) for x in pack["links"].itervalues()]) - for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()] - - @permission(PERMS.LIST) - def getCollector(self): - """same as `getQueue` for collector. - - :return: list of `PackageInfo` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - pack["linkstotal"]) - for pack in self.core.files.getInfoData(Destination.Collector).itervalues()] - - @permission(PERMS.LIST) - def getCollectorData(self): - """same as `getQueueData` for collector. - - :return: list of `PackageInfo` - """ - return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"], - pack["password"], pack["queue"], pack["order"], - pack["linksdone"], pack["sizedone"], pack["sizetotal"], - links=[self._convertPyFile(x) for x in pack["links"].itervalues()]) - for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()] - - - @permission(PERMS.ADD) - def addFiles(self, pid, links): - """Adds files to specific package. - - :param pid: package id - :param links: list of urls - """ - self.core.files.addLinks(links, int(pid)) - - self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid}) - self.core.files.save() - - @permission(PERMS.MODIFY) - def pushToQueue(self, pid): - """Moves package from Collector to Queue. - - :param pid: package id - """ - self.core.files.setPackageLocation(pid, Destination.Queue) - - @permission(PERMS.MODIFY) - def pullFromQueue(self, pid): - """Moves package from Queue to Collector. - - :param pid: package id - """ - self.core.files.setPackageLocation(pid, Destination.Collector) - - @permission(PERMS.MODIFY) - def restartPackage(self, pid): - """Restarts a package, resets every containing files. - - :param pid: package id - """ - self.core.files.restartPackage(int(pid)) - - @permission(PERMS.MODIFY) - def restartFile(self, fid): - """Resets file status, so it will be downloaded again. - - :param fid: file id - """ - self.core.files.restartFile(int(fid)) - - @permission(PERMS.MODIFY) - def recheckPackage(self, pid): - """Proofes online status of all files in a package, also a default action when package is added. - - :param pid: - :return: - """ - self.core.files.reCheckPackage(int(pid)) - - @permission(PERMS.MODIFY) - def stopAllDownloads(self): - """Aborts all running downloads.""" - - pyfiles = self.core.files.cache.values() - for pyfile in pyfiles: - pyfile.abortDownload() - - @permission(PERMS.MODIFY) - def stopDownloads(self, fids): - """Aborts specific downloads. - - :param fids: list of file ids - :return: - """ - pyfiles = self.core.files.cache.values() - - for pyfile in pyfiles: - if pyfile.id in fids: - pyfile.abortDownload() - - @permission(PERMS.MODIFY) - def setPackageName(self, pid, name): - """Renames a package. - - :param pid: package id - :param name: new package name - """ - pack = self.core.files.getPackage(pid) - pack.name = name - pack.sync() - - @permission(PERMS.MODIFY) - def movePackage(self, destination, pid): - """Set a new package location. - - :param destination: `Destination` - :param pid: package id - """ - if destination not in (0, 1): return - self.core.files.setPackageLocation(pid, destination) - - @permission(PERMS.MODIFY) - def moveFiles(self, fids, pid): - """Move multiple files to another package - - :param fids: list of file ids - :param pid: destination package - :return: - """ - #TODO: implement - pass - - - @permission(PERMS.ADD) - def uploadContainer(self, filename, data): - """Uploads and adds a container file to pyLoad. - - :param filename: filename, extension is important so it can correctly decrypted - :param data: file content - """ - th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb") - th.write(str(data)) - th.close() - - self.addPackage(th.name, [th.name], Destination.Queue) - - @permission(PERMS.MODIFY) - def orderPackage(self, pid, position): - """Gives a package a new position. - - :param pid: package id - :param position: - """ - self.core.files.reorderPackage(pid, position) - - @permission(PERMS.MODIFY) - def orderFile(self, fid, position): - """Gives a new position to a file within its package. - - :param fid: file id - :param position: - """ - self.core.files.reorderFile(fid, position) - - @permission(PERMS.MODIFY) - def setPackageData(self, pid, data): - """Allows to modify several package attributes. - - :param pid: package id - :param data: dict that maps attribute to desired value - """ - p = self.core.files.getPackage(pid) - if not p: raise PackageDoesNotExists(pid) - - for key, value in data.iteritems(): - if key == "id": continue - setattr(p, key, value) - - p.sync() - self.core.files.save() - - @permission(PERMS.DELETE) - def deleteFinished(self): - """Deletes all finished files and completly finished packages. - - :return: list of deleted package ids - """ - return self.core.files.deleteFinishedLinks() - - @permission(PERMS.MODIFY) - def restartFailed(self): - """Restarts all failed failes.""" - self.core.files.restartFailed() - - @permission(PERMS.LIST) - def getPackageOrder(self, destination): - """Returns information about package order. - - :param destination: `Destination` - :return: dict mapping order to package id - """ - - packs = self.core.files.getInfoData(destination) - order = {} - - for pid in packs: - pack = self.core.files.getPackageData(int(pid)) - while pack["order"] in order.keys(): #just in case - pack["order"] += 1 - order[pack["order"]] = pack["id"] - return order - - @permission(PERMS.LIST) - def getFileOrder(self, pid): - """Information about file order within package. - - :param pid: - :return: dict mapping order to file id - """ - rawData = self.core.files.getPackageData(int(pid)) - order = {} - for id, pyfile in rawData["links"].iteritems(): - while pyfile["order"] in order.keys(): #just in case - pyfile["order"] += 1 - order[pyfile["order"]] = pyfile["id"] - return order - - - @permission(PERMS.STATUS) - def isCaptchaWaiting(self): - """Indicates wether a captcha task is available - - :return: bool - """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTask() - return not task is None - - @permission(PERMS.STATUS) - def getCaptchaTask(self, exclusive=False): - """Returns a captcha task - - :param exclusive: unused - :return: `CaptchaTask` - """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTask() - if task: - task.setWatingForUser(exclusive=exclusive) - data, type, result = task.getCaptcha() - t = CaptchaTask(int(task.id), standard_b64encode(data), type, result) - return t - else: - return CaptchaTask(-1) - - @permission(PERMS.STATUS) - def getCaptchaTaskStatus(self, tid): - """Get information about captcha task - - :param tid: task id - :return: string - """ - self.core.lastClientConnected = time() - t = self.core.captchaManager.getTaskByID(tid) - return t.getStatus() if t else "" - - @permission(PERMS.STATUS) - def setCaptchaResult(self, tid, result): - """Set result for a captcha task - - :param tid: task id - :param result: captcha result - """ - self.core.lastClientConnected = time() - task = self.core.captchaManager.getTaskByID(tid) - if task: - task.setResult(result) - self.core.captchaManager.removeTask(task) - - - @permission(PERMS.STATUS) - def getEvents(self, uuid): - """Lists occured events, may be affected to changes in future. - - :param uuid: - :return: list of `Events` - """ - events = self.core.pullManager.getEvents(uuid) - newEvents = [] - - def convDest(d): - return Destination.Queue if d == "queue" else Destination.Collector - - for e in events: - event = EventInfo() - event.eventname = e[0] - if e[0] in ("update", "remove", "insert"): - event.id = e[3] - event.type = ElementType.Package if e[2] == "pack" else ElementType.File - event.destination = convDest(e[1]) - elif e[0] == "order": - if e[1]: - event.id = e[1] - event.type = ElementType.Package if e[2] == "pack" else ElementType.File - event.destination = convDest(e[3]) - elif e[0] == "reload": - event.destination = convDest(e[1]) - newEvents.append(event) - return newEvents - - @permission(PERMS.ACCOUNTS) - def getAccounts(self, refresh): - """Get information about all entered accounts. - - :param refresh: reload account info - :return: list of `AccountInfo` - """ - accs = self.core.accountManager.getAccountInfos(False, refresh) - accounts = [] - for group in accs.values(): - accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"], - acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"]) - for acc in group]) - return accounts - - @permission(PERMS.ALL) - def getAccountTypes(self): - """All available account types. - - :return: list - """ - return self.core.accountManager.accounts.keys() - - @permission(PERMS.ACCOUNTS) - def updateAccount(self, plugin, account, password=None, options={}): - """Changes pw/options for specific account.""" - self.core.accountManager.updateAccount(plugin, account, password, options) - - @permission(PERMS.ACCOUNTS) - def removeAccount(self, plugin, account): - """Remove account from pyload. - - :param plugin: pluginname - :param account: accountname - """ - self.core.accountManager.removeAccount(plugin, account) - - @permission(PERMS.ALL) - def login(self, username, password, remoteip=None): - """Login into pyLoad, this **must** be called when using rpc before any methods can be used. - - :param username: - :param password: - :param remoteip: Omit this argument, its only used internal - :return: bool indicating login was successful - """ - return True if self.checkAuth(username, password, remoteip) else False - - def checkAuth(self, username, password, remoteip=None): - """Check authentication and returns details - - :param username: - :param password: - :param remoteip: - :return: dict with info, empty when login is incorrect - """ - if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1": - return "local" - if self.core.startedInGui and remoteip == "127.0.0.1": - return "local" - - return self.core.db.checkAuth(username, password) - - def isAuthorized(self, func, userdata): - """checks if the user is authorized for specific method - - :param func: function name - :param userdata: dictionary of user data - :return: boolean - """ - if userdata == "local" or userdata["role"] == ROLE.ADMIN: - return True - elif func in permMap and has_permission(userdata["permission"], permMap[func]): - return True - else: - return False - - - @permission(PERMS.ALL) - def getUserData(self, username, password): - """similar to `checkAuth` but returns UserData thrift type """ - user = self.checkAuth(username, password) - if user: - return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"]) - else: - return UserData() - - - def getAllUserData(self): - """returns all known user and info""" - res = {} - for user, data in self.core.db.getAllUserData().iteritems(): - res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"]) - - return res - - @permission(PERMS.STATUS) - def getServices(self): - """ A dict of available services, these can be defined by hook plugins. - - :return: dict with this style: {"plugin": {"method": "description"}} - """ - data = {} - for plugin, funcs in self.core.hookManager.methods.iteritems(): - data[plugin] = funcs - - return data - - @permission(PERMS.STATUS) - def hasService(self, plugin, func): - """Checks wether a service is available. - - :param plugin: - :param func: - :return: bool - """ - cont = self.core.hookManager.methods - return plugin in cont and func in cont[plugin] - - @permission(PERMS.STATUS) - def call(self, info): - """Calls a service (a method in hook plugin). - - :param info: `ServiceCall` - :return: result - :raises: ServiceDoesNotExists, when its not available - :raises: ServiceException, when a exception was raised - """ - plugin = info.plugin - func = info.func - args = info.arguments - parse = info.parseArguments - - if not self.hasService(plugin, func): - raise ServiceDoesNotExists(plugin, func) - - try: - ret = self.core.hookManager.callRPC(plugin, func, args, parse) - return str(ret) - except Exception, e: - raise ServiceException(e.message) - - @permission(PERMS.STATUS) - def getAllInfo(self): - """Returns all information stored by hook plugins. Values are always strings - - :return: {"plugin": {"name": value } } - """ - return self.core.hookManager.getAllInfo() - - @permission(PERMS.STATUS) - def getInfoByPlugin(self, plugin): - """Returns information stored by a specific plugin. - - :param plugin: pluginname - :return: dict of attr names mapped to value {"name": value} - """ - return self.core.hookManager.getInfo(plugin) - - def changePassword(self, user, oldpw, newpw): - """ changes password for specific user """ - return self.core.db.changePassword(user, oldpw, newpw) - - def setUserPermission(self, user, permission, role): - self.core.db.setPermission(user, permission) - self.core.db.setRole(user, role)
\ No newline at end of file diff --git a/module/CaptchaManager.py b/module/CaptchaManager.py deleted file mode 100644 index 02cd10a11..000000000 --- a/module/CaptchaManager.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay, RaNaN -""" - -from time import time -from traceback import print_exc -from threading import Lock - -class CaptchaManager(): - def __init__(self, core): - self.lock = Lock() - self.core = core - self.tasks = [] #task store, for outgoing tasks only - - self.ids = 0 #only for internal purpose - - def newTask(self, img, format, file, result_type): - task = CaptchaTask(self.ids, img, format, file, result_type) - self.ids += 1 - return task - - def removeTask(self, task): - self.lock.acquire() - if task in self.tasks: - self.tasks.remove(task) - self.lock.release() - - def getTask(self): - self.lock.acquire() - for task in self.tasks: - if task.status in ("waiting", "shared-user"): - self.lock.release() - return task - self.lock.release() - return None - - def getTaskByID(self, tid): - self.lock.acquire() - for task in self.tasks: - if task.id == str(tid): #task ids are strings - self.lock.release() - return task - self.lock.release() - return None - - def handleCaptcha(self, task): - cli = self.core.isClientConnected() - - if cli: #client connected -> should solve the captcha - task.setWaiting(50) #wait 50 sec for response - - for plugin in self.core.hookManager.activePlugins(): - try: - plugin.newCaptchaTask(task) - except: - if self.core.debug: - print_exc() - - if task.handler or cli: #the captcha was handled - self.tasks.append(task) - return True - - task.error = _("No Client connected for captcha decrypting") - - return False - - -class CaptchaTask(): - def __init__(self, id, img, format, file, result_type='textual'): - self.id = str(id) - self.captchaImg = img - self.captchaFormat = format - self.captchaFile = file - self.captchaResultType = result_type - self.handler = [] #the hook plugins that will take care of the solution - self.result = None - self.waitUntil = None - self.error = None #error message - - self.status = "init" - self.data = {} #handler can store data here - - def getCaptcha(self): - return self.captchaImg, self.captchaFormat, self.captchaResultType - - def setResult(self, text): - if self.isTextual(): - self.result = text - if self.isPositional(): - try: - parts = text.split(',') - self.result = (int(parts[0]), int(parts[1])) - except: - self.result = None - - def getResult(self): - try: - res = self.result.encode("utf8", "replace") - except: - res = self.result - - return res - - def getStatus(self): - return self.status - - def setWaiting(self, sec): - """ let the captcha wait secs for the solution """ - self.waitUntil = max(time() + sec, self.waitUntil) - self.status = "waiting" - - def isWaiting(self): - if self.result or self.error or time() > self.waitUntil: - return False - - return True - - def isTextual(self): - """ returns if text is written on the captcha """ - return self.captchaResultType == 'textual' - - def isPositional(self): - """ returns if user have to click a specific region on the captcha """ - return self.captchaResultType == 'positional' - - def setWatingForUser(self, exclusive): - if exclusive: - self.status = "user" - else: - self.status = "shared-user" - - def timedOut(self): - return time() > self.waitUntil - - def invalid(self): - """ indicates the captcha was not correct """ - [x.captchaInvalid(self) for x in self.handler] - - def correct(self): - [x.captchaCorrect(self) for x in self.handler] - - def __str__(self): - return "<CaptchaTask '%s'>" % self.id diff --git a/module/ConfigParser.py b/module/ConfigParser.py deleted file mode 100644 index 78b612f13..000000000 --- a/module/ConfigParser.py +++ /dev/null @@ -1,392 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import with_statement -from time import sleep -from os.path import exists, join -from shutil import copy - -from traceback import print_exc -from utils import chmod - -# ignore these plugin configs, mainly because plugins were wiped out -IGNORE = ( - "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'), - 'EasyShareCom', 'FlyshareCz' - ) - -CONF_VERSION = 1 - -class ConfigParser: - """ - holds and manage the configuration - - current dict layout: - - { - - section : { - option : { - value: - type: - desc: - } - desc: - - } - - - """ - - - def __init__(self): - """Constructor""" - self.config = {} # the config values - self.plugin = {} # the config for plugins - self.oldRemoteData = {} - - self.pluginCB = None # callback when plugin config value is changed - - self.checkVersion() - - self.readConfig() - - self.deleteOldPlugins() - - - def checkVersion(self, n=0): - """determines if config need to be copied""" - try: - if not exists("pyload.conf"): - copy(join(pypath, "module", "config", "default.conf"), "pyload.conf") - - if not exists("plugin.conf"): - f = open("plugin.conf", "wb") - f.write("version: " + str(CONF_VERSION)) - f.close() - - f = open("pyload.conf", "rb") - v = f.readline() - f.close() - v = v[v.find(":") + 1:].strip() - - if not v or int(v) < CONF_VERSION: - copy(join(pypath, "module", "config", "default.conf"), "pyload.conf") - print "Old version of config was replaced" - - f = open("plugin.conf", "rb") - v = f.readline() - f.close() - v = v[v.find(":") + 1:].strip() - - if not v or int(v) < CONF_VERSION: - f = open("plugin.conf", "wb") - f.write("version: " + str(CONF_VERSION)) - f.close() - print "Old version of plugin-config replaced" - except: - if n < 3: - sleep(0.3) - self.checkVersion(n + 1) - else: - raise - - def readConfig(self): - """reads the config file""" - - self.config = self.parseConfig(join(pypath, "module", "config", "default.conf")) - self.plugin = self.parseConfig("plugin.conf") - - try: - homeconf = self.parseConfig("pyload.conf") - if "username" in homeconf["remote"]: - if "password" in homeconf["remote"]: - self.oldRemoteData = {"username": homeconf["remote"]["username"]["value"], - "password": homeconf["remote"]["username"]["value"]} - del homeconf["remote"]["password"] - del homeconf["remote"]["username"] - self.updateValues(homeconf, self.config) - - except Exception, e: - print "Config Warning" - print_exc() - - - def parseConfig(self, config): - """parses a given configfile""" - - f = open(config) - - config = f.read() - - config = config.splitlines()[1:] - - conf = {} - - section, option, value, typ, desc = "", "", "", "", "" - - listmode = False - - for line in config: - comment = line.rfind("#") - if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace(): - line = line.rpartition("#") # removes comments - if line[1]: - line = line[0] - else: - line = line[2] - - line = line.strip() - - try: - if line == "": - continue - elif line.endswith(":"): - section, none, desc = line[:-1].partition('-') - section = section.strip() - desc = desc.replace('"', "").strip() - conf[section] = {"desc": desc} - else: - if listmode: - if line.endswith("]"): - listmode = False - line = line.replace("]", "") - - value += [self.cast(typ, x.strip()) for x in line.split(",") if x] - - if not listmode: - conf[section][option] = {"desc": desc, - "type": typ, - "value": value} - - - else: - content, none, value = line.partition("=") - - content, none, desc = content.partition(":") - - desc = desc.replace('"', "").strip() - - typ, none, option = content.strip().rpartition(" ") - - value = value.strip() - - if value.startswith("["): - if value.endswith("]"): - listmode = False - value = value[:-1] - else: - listmode = True - - value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x] - else: - value = self.cast(typ, value) - - if not listmode: - conf[section][option] = {"desc": desc, - "type": typ, - "value": value} - - except Exception, e: - print "Config Warning" - print_exc() - - f.close() - return conf - - - def updateValues(self, config, dest): - """sets the config values from a parsed config file to values in destination""" - - for section in config.iterkeys(): - if section in dest: - for option in config[section].iterkeys(): - if option in ("desc", "outline"): continue - - if option in dest[section]: - dest[section][option]["value"] = config[section][option]["value"] - - #else: - # dest[section][option] = config[section][option] - - - #else: - # dest[section] = config[section] - - def saveConfig(self, config, filename): - """saves config to filename""" - with open(filename, "wb") as f: - chmod(filename, 0600) - f.write("version: %i \n" % CONF_VERSION) - for section in config.iterkeys(): - f.write('\n%s - "%s":\n' % (section, config[section]["desc"])) - - for option, data in config[section].iteritems(): - if option in ("desc", "outline"): continue - - if isinstance(data["value"], list): - value = "[ \n" - for x in data["value"]: - value += "\t\t" + str(x) + ",\n" - value += "\t\t]\n" - else: - if type(data["value"]) in (str, unicode): - value = data["value"] + "\n" - else: - value = str(data["value"]) + "\n" - try: - f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value)) - except UnicodeEncodeError: - f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value.encode("utf8"))) - - def cast(self, typ, value): - """cast value to given format""" - if type(value) not in (str, unicode): - return value - - elif typ == "int": - return int(value) - elif typ == "bool": - return True if value.lower() in ("1", "true", "on", "an", "yes") else False - elif typ == "time": - if not value: value = "0:00" - if not ":" in value: value += ":00" - return value - elif typ in ("str", "file", "folder"): - try: - return value.encode("utf8") - except: - return value - else: - return value - - - def save(self): - """saves the configs to disk""" - - self.saveConfig(self.config, "pyload.conf") - self.saveConfig(self.plugin, "plugin.conf") - - - def __getitem__(self, section): - """provides dictonary like access: c['section']['option']""" - return Section(self, section) - - - def get(self, section, option): - """get value""" - val = self.config[section][option]["value"] - try: - if type(val) in (str, unicode): - return val.decode("utf8") - else: - return val - except: - return val - - def set(self, section, option, value): - """set value""" - - value = self.cast(self.config[section][option]["type"], value) - - self.config[section][option]["value"] = value - self.save() - - def getPlugin(self, plugin, option): - """gets a value for a plugin""" - val = self.plugin[plugin][option]["value"] - try: - if type(val) in (str, unicode): - return val.decode("utf8") - else: - return val - except: - return val - - def setPlugin(self, plugin, option, value): - """sets a value for a plugin""" - - value = self.cast(self.plugin[plugin][option]["type"], value) - - if self.pluginCB: self.pluginCB(plugin, option, value) - - self.plugin[plugin][option]["value"] = value - self.save() - - def getMetaData(self, section, option): - """ get all config data for an option """ - return self.config[section][option] - - def addPluginConfig(self, name, config, outline=""): - """adds config options with tuples (name, type, desc, default)""" - if name not in self.plugin: - conf = {"desc": name, - "outline": outline} - self.plugin[name] = conf - else: - conf = self.plugin[name] - conf["outline"] = outline - - for item in config: - if item[0] in conf: - conf[item[0]]["type"] = item[1] - conf[item[0]]["desc"] = item[2] - else: - conf[item[0]] = { - "desc": item[2], - "type": item[1], - "value": self.cast(item[1], item[3]) - } - - values = [x[0] for x in config] + ["desc", "outline"] - #delete old values - for item in conf.keys(): - if item not in values: - del conf[item] - - def deleteConfig(self, name): - """Removes a plugin config""" - if name in self.plugin: - del self.plugin[name] - - - def deleteOldPlugins(self): - """ remove old plugins from config """ - - for name in IGNORE: - if name in self.plugin: - del self.plugin[name] - - -class Section: - """provides dictionary like access for configparser""" - - def __init__(self, parser, section): - """Constructor""" - self.parser = parser - self.section = section - - def __getitem__(self, item): - """getitem""" - return self.parser.get(self.section, item) - - def __setitem__(self, item, value): - """setitem""" - self.parser.set(self.section, item, value) - - -if __name__ == "__main__": - pypath = "" - - from time import time - - a = time() - - c = ConfigParser() - - b = time() - - print "sec", b - a - - print c.config - - c.saveConfig(c.config, "user.conf") diff --git a/module/HookManager.py b/module/HookManager.py deleted file mode 100644 index 16f692d76..000000000 --- a/module/HookManager.py +++ /dev/null @@ -1,315 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN, mkaay - @interface-version: 0.1 -""" -import __builtin__ - -import traceback -from thread import start_new_thread -from threading import RLock - -from types import MethodType - -from module.PluginThread import HookThread -from module.plugins.PluginManager import literal_eval -from utils import lock - -class HookManager: - """Manages hooks, delegates and handles Events. - - Every plugin can define events, \ - but some very usefull events are called by the Core. - Contrary to overwriting hook methods you can use event listener, - which provides additional entry point in the control flow. - Only do very short tasks or use threads. - - **Known Events:** - Most hook methods exists as events. These are the additional known events. - - ===================== ============== ================================== - Name Arguments Description - ===================== ============== ================================== - downloadPreparing fid A download was just queued and will be prepared now. - downloadStarts fid A plugin will immediately starts the download afterwards. - linksAdded links, pid Someone just added links, you are able to modify the links. - allDownloadsProcessed Every link was handled, pyload would idle afterwards. - allDownloadsFinished Every download in queue is finished. - unrarFinished folder, fname An Unrar job finished - configChanged The config was changed via the api. - pluginConfigChanged The plugin config changed, due to api or internal process. - ===================== ============== ================================== - - | Notes: - | allDownloadsProcessed is *always* called before allDownloadsFinished. - | configChanged is *always* called before pluginConfigChanged. - - - """ - - def __init__(self, core): - self.core = core - self.config = self.core.config - - __builtin__.hookManager = self #needed to let hooks register themself - - self.log = self.core.log - self.plugins = [] - self.pluginMap = {} - self.methods = {} #dict of names and list of methods usable by rpc - - self.events = {} # contains events - - #registering callback for config event - self.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring) - - self.addEvent("pluginConfigChanged", self.manageHooks) - - self.lock = RLock() - self.createIndex() - - def try_catch(func): - def new(*args): - try: - return func(*args) - except Exception, e: - args[0].log.error(_("Error executing hooks: %s") % str(e)) - if args[0].core.debug: - traceback.print_exc() - - return new - - - def addRPC(self, plugin, func, doc): - plugin = plugin.rpartition(".")[2] - doc = doc.strip() if doc else "" - - if plugin in self.methods: - self.methods[plugin][func] = doc - else: - self.methods[plugin] = {func: doc} - - def callRPC(self, plugin, func, args, parse): - if not args: args = tuple() - if parse: - args = tuple([literal_eval(x) for x in args]) - - plugin = self.pluginMap[plugin] - f = getattr(plugin, func) - return f(*args) - - - def createIndex(self): - plugins = [] - - active = [] - deactive = [] - - for pluginname in self.core.pluginManager.hookPlugins: - try: - #hookClass = getattr(plugin, plugin.__name__) - - if self.core.config.getPlugin(pluginname, "activated"): - pluginClass = self.core.pluginManager.loadClass("hooks", pluginname) - if not pluginClass: continue - - plugin = pluginClass(self.core, self) - plugins.append(plugin) - self.pluginMap[pluginClass.__name__] = plugin - if plugin.isActivated(): - active.append(pluginClass.__name__) - else: - deactive.append(pluginname) - - - except: - self.log.warning(_("Failed activating %(name)s") % {"name": pluginname}) - if self.core.debug: - traceback.print_exc() - - self.log.info(_("Activated plugins: %s") % ", ".join(sorted(active))) - self.log.info(_("Deactivate plugins: %s") % ", ".join(sorted(deactive))) - - self.plugins = plugins - - def manageHooks(self, plugin, name, value): - if name == "activated" and value: - self.activateHook(plugin) - elif name == "activated" and not value: - self.deactivateHook(plugin) - - def activateHook(self, plugin): - - #check if already loaded - for inst in self.plugins: - if inst.__name__ == plugin: - return - - pluginClass = self.core.pluginManager.loadClass("hooks", plugin) - - if not pluginClass: return - - self.log.debug("Plugin loaded: %s" % plugin) - - plugin = pluginClass(self.core, self) - self.plugins.append(plugin) - self.pluginMap[pluginClass.__name__] = plugin - - # call core Ready - start_new_thread(plugin.coreReady, tuple()) - - def deactivateHook(self, plugin): - - hook = None - for inst in self.plugins: - if inst.__name__ == plugin: - hook = inst - - if not hook: return - - self.log.debug("Plugin unloaded: %s" % plugin) - - hook.unload() - - #remove periodic call - self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(hook.cb)) - self.plugins.remove(hook) - del self.pluginMap[hook.__name__] - - - @try_catch - def coreReady(self): - for plugin in self.plugins: - if plugin.isActivated(): - plugin.coreReady() - - self.dispatchEvent("coreReady") - - @try_catch - def coreExiting(self): - for plugin in self.plugins: - if plugin.isActivated(): - plugin.coreExiting() - - self.dispatchEvent("coreExiting") - - @lock - def downloadPreparing(self, pyfile): - for plugin in self.plugins: - if plugin.isActivated(): - plugin.downloadPreparing(pyfile) - - self.dispatchEvent("downloadPreparing", pyfile) - - @lock - def downloadFinished(self, pyfile): - for plugin in self.plugins: - if plugin.isActivated(): - if "downloadFinished" in plugin.__threaded__: - self.startThread(plugin.downloadFinished, pyfile) - else: - plugin.downloadFinished(pyfile) - - self.dispatchEvent("downloadFinished", pyfile) - - @lock - @try_catch - def downloadFailed(self, pyfile): - for plugin in self.plugins: - if plugin.isActivated(): - if "downloadFailed" in plugin.__threaded__: - self.startThread(plugin.downloadFinished, pyfile) - else: - plugin.downloadFailed(pyfile) - - self.dispatchEvent("downloadFailed", pyfile) - - @lock - def packageFinished(self, package): - for plugin in self.plugins: - if plugin.isActivated(): - if "packageFinished" in plugin.__threaded__: - self.startThread(plugin.packageFinished, package) - else: - plugin.packageFinished(package) - - self.dispatchEvent("packageFinished", package) - - @lock - def beforeReconnecting(self, ip): - for plugin in self.plugins: - plugin.beforeReconnecting(ip) - - self.dispatchEvent("beforeReconnecting", ip) - - @lock - def afterReconnecting(self, ip): - for plugin in self.plugins: - if plugin.isActivated(): - plugin.afterReconnecting(ip) - - self.dispatchEvent("afterReconnecting", ip) - - def startThread(self, function, *args, **kwargs): - t = HookThread(self.core.threadManager, function, args, kwargs) - - def activePlugins(self): - """ returns all active plugins """ - return [x for x in self.plugins if x.isActivated()] - - def getAllInfo(self): - """returns info stored by hook plugins""" - info = {} - for name, plugin in self.pluginMap.iteritems(): - if plugin.info: - #copy and convert so str - info[name] = dict([(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()]) - return info - - - def getInfo(self, plugin): - info = {} - if plugin in self.pluginMap and self.pluginMap[plugin].info: - info = dict([(x, str(y) if not isinstance(y, basestring) else y) - for x, y in self.pluginMap[plugin].info.iteritems()]) - - return info - - def addEvent(self, event, func): - """Adds an event listener for event name""" - if event in self.events: - self.events[event].append(func) - else: - self.events[event] = [func] - - def removeEvent(self, event, func): - """removes previously added event listener""" - if event in self.events: - self.events[event].remove(func) - - def dispatchEvent(self, event, *args): - """dispatches event with args""" - if event in self.events: - for f in self.events[event]: - try: - f(*args) - except Exception, e: - self.log.warning("Error calling event handler %s: %s, %s, %s" - % (event, f, args, str(e))) - if self.core.debug: - traceback.print_exc() - diff --git a/module/InitHomeDir.py b/module/InitHomeDir.py deleted file mode 100644 index 156c9f932..000000000 --- a/module/InitHomeDir.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN - - This modules inits working directories and global variables, pydir and homedir -""" - -from os import makedirs, path, chdir -from os.path import join -import sys -from sys import argv, platform - -import __builtin__ -__builtin__.owd = path.abspath("") #original working directory -__builtin__.pypath = path.abspath(path.join(__file__, "..", "..")) - -sys.path.append(join(pypath, "module", "lib")) - -homedir = "" - -if platform == 'nt': - homedir = path.expanduser("~") - if homedir == "~": - import ctypes - - CSIDL_APPDATA = 26 - _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW - _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND, - ctypes.c_int, - ctypes.wintypes.HANDLE, - ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR] - - path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) - result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf) - homedir = path_buf.value -else: - homedir = path.expanduser("~") - -__builtin__.homedir = homedir - -args = " ".join(argv[1:]) - -# dirty method to set configdir from commandline arguments -if "--configdir=" in args: - pos = args.find("--configdir=") - end = args.find("-", pos + 12) - - if end == -1: - configdir = args[pos + 12:].strip() - else: - configdir = args[pos + 12:end].strip() -elif path.exists(path.join(pypath, "module", "config", "configdir")): - f = open(path.join(pypath, "module", "config", "configdir"), "rb") - c = f.read().strip() - f.close() - configdir = path.join(pypath, c) -else: - if platform in ("posix", "linux2"): - configdir = path.join(homedir, ".pyload") - else: - configdir = path.join(homedir, "pyload") - -if not path.exists(configdir): - makedirs(configdir, 0700) - -__builtin__.configdir = configdir -chdir(configdir) - -#print "Using %s as working directory." % configdir diff --git a/module/PluginThread.py b/module/PluginThread.py deleted file mode 100644 index 56c36c778..000000000 --- a/module/PluginThread.py +++ /dev/null @@ -1,677 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from Queue import Queue -from threading import Thread -from os import listdir, stat -from os.path import join -from time import sleep, time, strftime, gmtime -from traceback import print_exc, format_exc -from pprint import pformat -from sys import exc_info, exc_clear -from copy import copy -from types import MethodType - -from pycurl import error - -from PyFile import PyFile -from plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload -from common.packagetools import parseNames -from utils import save_join -from Api import OnlineStatus - -class PluginThread(Thread): - """abstract base class for thread types""" - - #---------------------------------------------------------------------- - def __init__(self, manager): - """Constructor""" - Thread.__init__(self) - self.setDaemon(True) - self.m = manager #thread manager - - - def writeDebugReport(self, pyfile): - """ writes a - :return: - """ - - dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S")) - dump = self.getDebugDump(pyfile) - - try: - import zipfile - - zip = zipfile.ZipFile(dump_name, "w") - - for f in listdir(join("tmp", pyfile.pluginname)): - try: - # avoid encoding errors - zip.write(join("tmp", pyfile.pluginname, f), save_join(pyfile.pluginname, f)) - except: - pass - - info = zipfile.ZipInfo(save_join(pyfile.pluginname, "debug_Report.txt"), gmtime()) - info.external_attr = 0644 << 16L # change permissions - - zip.writestr(info, dump) - zip.close() - - if not stat(dump_name).st_size: - raise Exception("Empty Zipfile") - - except Exception, e: - self.m.log.debug("Error creating zip file: %s" % e) - - dump_name = dump_name.replace(".zip", ".txt") - f = open(dump_name, "wb") - f.write(dump) - f.close() - - self.m.core.log.info("Debug Report written to %s" % dump_name) - - def getDebugDump(self, pyfile): - dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % ( - self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version__, format_exc()) - - tb = exc_info()[2] - stack = [] - while tb: - stack.append(tb.tb_frame) - tb = tb.tb_next - - for frame in stack[1:]: - dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name, - frame.f_code.co_filename, - frame.f_lineno) - - for key, value in frame.f_locals.items(): - dump += "\t%20s = " % key - try: - dump += pformat(value) + "\n" - except Exception, e: - dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" - - del frame - - del stack #delete it just to be sure... - - dump += "\n\nPLUGIN OBJECT DUMP: \n\n" - - for name in dir(pyfile.plugin): - attr = getattr(pyfile.plugin, name) - if not name.endswith("__") and type(attr) != MethodType: - dump += "\t%20s = " % name - try: - dump += pformat(attr) + "\n" - except Exception, e: - dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" - - dump += "\nPYFILE OBJECT DUMP: \n\n" - - for name in dir(pyfile): - attr = getattr(pyfile, name) - if not name.endswith("__") and type(attr) != MethodType: - dump += "\t%20s = " % name - try: - dump += pformat(attr) + "\n" - except Exception, e: - dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" - - if pyfile.pluginname in self.m.core.config.plugin: - dump += "\n\nCONFIG: \n\n" - dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n" - - return dump - - def clean(self, pyfile): - """ set thread unactive and release pyfile """ - self.active = False - pyfile.release() - - -class DownloadThread(PluginThread): - """thread for downloading files from 'real' hoster plugins""" - - #---------------------------------------------------------------------- - def __init__(self, manager): - """Constructor""" - PluginThread.__init__(self, manager) - - self.queue = Queue() # job queue - self.active = False - - self.start() - - #---------------------------------------------------------------------- - def run(self): - """run method""" - pyfile = None - - while True: - del pyfile - self.active = self.queue.get() - pyfile = self.active - - if self.active == "quit": - self.active = False - self.m.threads.remove(self) - return True - - try: - if not pyfile.hasPlugin(): continue - #this pyfile was deleted while queueing - - pyfile.plugin.checkForSameFiles(starting=True) - self.m.log.info(_("Download starts: %s" % pyfile.name)) - - # start download - self.m.core.hookManager.downloadPreparing(pyfile) - pyfile.plugin.preprocessing(self) - - self.m.log.info(_("Download finished: %s") % pyfile.name) - self.m.core.hookManager.downloadFinished(pyfile) - self.m.core.files.checkPackageFinished(pyfile) - - except NotImplementedError: - self.m.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname) - pyfile.setStatus("failed") - pyfile.error = "Plugin does not work" - self.clean(pyfile) - continue - - except Abort: - try: - self.m.log.info(_("Download aborted: %s") % pyfile.name) - except: - pass - - pyfile.setStatus("aborted") - - self.clean(pyfile) - continue - - except Reconnect: - self.queue.put(pyfile) - #pyfile.req.clearCookies() - - while self.m.reconnecting.isSet(): - sleep(0.5) - - continue - - except Retry, e: - reason = e.args[0] - self.m.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason}) - self.queue.put(pyfile) - continue - - except Fail, e: - msg = e.args[0] - - if msg == "offline": - pyfile.setStatus("offline") - self.m.log.warning(_("Download is offline: %s") % pyfile.name) - elif msg == "temp. offline": - pyfile.setStatus("temp. offline") - self.m.log.warning(_("Download is temporary offline: %s") % pyfile.name) - else: - pyfile.setStatus("failed") - self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg}) - pyfile.error = msg - - self.m.core.hookManager.downloadFailed(pyfile) - self.clean(pyfile) - continue - - except error, e: - if len(e.args) == 2: - code, msg = e.args - else: - code = 0 - msg = e.args - - self.m.log.debug("pycurl exception %s: %s" % (code, msg)) - - if code in (7, 18, 28, 52, 56): - self.m.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry.")) - wait = time() + 60 - - pyfile.waitUntil = wait - pyfile.setStatus("waiting") - while time() < wait: - sleep(1) - if pyfile.abort: - break - - if pyfile.abort: - self.m.log.info(_("Download aborted: %s") % pyfile.name) - pyfile.setStatus("aborted") - - self.clean(pyfile) - else: - self.queue.put(pyfile) - - continue - - else: - pyfile.setStatus("failed") - self.m.log.error("pycurl error %s: %s" % (code, msg)) - if self.m.core.debug: - print_exc() - self.writeDebugReport(pyfile) - - self.m.core.hookManager.downloadFailed(pyfile) - - self.clean(pyfile) - continue - - except SkipDownload, e: - pyfile.setStatus("skipped") - - self.m.log.info( - _("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message}) - - self.clean(pyfile) - - self.m.core.files.checkPackageFinished(pyfile) - - self.active = False - self.m.core.files.save() - - continue - - - except Exception, e: - pyfile.setStatus("failed") - self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)}) - pyfile.error = str(e) - - if self.m.core.debug: - print_exc() - self.writeDebugReport(pyfile) - - self.m.core.hookManager.downloadFailed(pyfile) - self.clean(pyfile) - continue - - finally: - self.m.core.files.save() - pyfile.checkIfProcessed() - exc_clear() - - - #pyfile.plugin.req.clean() - - self.active = False - pyfile.finishIfDone() - self.m.core.files.save() - - - def put(self, job): - """assing job to thread""" - self.queue.put(job) - - - def stop(self): - """stops the thread""" - self.put("quit") - - -class DecrypterThread(PluginThread): - """thread for decrypting""" - - def __init__(self, manager, pyfile): - """constructor""" - PluginThread.__init__(self, manager) - - self.active = pyfile - manager.localThreads.append(self) - - pyfile.setStatus("decrypting") - - self.start() - - def getActiveFiles(self): - return [self.active] - - def run(self): - """run method""" - - pyfile = self.active - retry = False - - try: - self.m.log.info(_("Decrypting starts: %s") % self.active.name) - self.active.plugin.preprocessing(self) - - except NotImplementedError: - self.m.log.error(_("Plugin %s is missing a function.") % self.active.pluginname) - return - - except Fail, e: - msg = e.args[0] - - if msg == "offline": - self.active.setStatus("offline") - self.m.log.warning(_("Download is offline: %s") % self.active.name) - else: - self.active.setStatus("failed") - self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": msg}) - self.active.error = msg - - return - - except Abort: - self.m.log.info(_("Download aborted: %s") % pyfile.name) - pyfile.setStatus("aborted") - - return - - except Retry: - self.m.log.info(_("Retrying %s") % self.active.name) - retry = True - return self.run() - - except Exception, e: - self.active.setStatus("failed") - self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": str(e)}) - self.active.error = str(e) - - if self.m.core.debug: - print_exc() - self.writeDebugReport(pyfile) - - return - - - finally: - if not retry: - self.active.release() - self.active = False - self.m.core.files.save() - self.m.localThreads.remove(self) - exc_clear() - - - #self.m.core.hookManager.downloadFinished(pyfile) - - - #self.m.localThreads.remove(self) - #self.active.finishIfDone() - if not retry: - pyfile.delete() - - -class HookThread(PluginThread): - """thread for hooks""" - - #---------------------------------------------------------------------- - def __init__(self, m, function, args, kwargs): - """Constructor""" - PluginThread.__init__(self, m) - - self.f = function - self.args = args - self.kwargs = kwargs - - self.active = [] - - m.localThreads.append(self) - - self.start() - - def getActiveFiles(self): - return self.active - - def addActive(self, pyfile): - """ Adds a pyfile to active list and thus will be displayed on overview""" - if pyfile not in self.active: - self.active.append(pyfile) - - def finishFile(self, pyfile): - if pyfile in self.active: - self.active.remove(pyfile) - - pyfile.finishIfDone() - - def run(self): - try: - try: - self.kwargs["thread"] = self - self.f(*self.args, **self.kwargs) - except TypeError, e: - #dirty method to filter out exceptions - if "unexpected keyword argument 'thread'" not in e.args[0]: - raise - - del self.kwargs["thread"] - self.f(*self.args, **self.kwargs) - finally: - local = copy(self.active) - for x in local: - self.finishFile(x) - - self.m.localThreads.remove(self) - - -class InfoThread(PluginThread): - def __init__(self, manager, data, pid=-1, rid=-1, add=False): - """Constructor""" - PluginThread.__init__(self, manager) - - self.data = data - self.pid = pid # package id - # [ .. (name, plugin) .. ] - - self.rid = rid #result id - self.add = add #add packages instead of return result - - self.cache = [] #accumulated data - - self.start() - - def run(self): - """run method""" - - plugins = {} - container = [] - - for url, plugin in self.data: - if plugin in plugins: - plugins[plugin].append(url) - else: - plugins[plugin] = [url] - - - # filter out container plugins - for name in self.m.core.pluginManager.containerPlugins: - if name in plugins: - container.extend([(name, url) for url in plugins[name]]) - - del plugins[name] - - #directly write to database - if self.pid > -1: - for pluginname, urls in plugins.iteritems(): - plugin = self.m.core.pluginManager.getPlugin(pluginname, True) - if hasattr(plugin, "getInfo"): - self.fetchForPlugin(pluginname, plugin, urls, self.updateDB) - self.m.core.files.save() - - elif self.add: - for pluginname, urls in plugins.iteritems(): - plugin = self.m.core.pluginManager.getPlugin(pluginname, True) - if hasattr(plugin, "getInfo"): - self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True) - - else: - #generate default result - result = [(url, 0, 3, url) for url in urls] - - self.updateCache(pluginname, result) - - packs = parseNames([(name, url) for name, x, y, url in self.cache]) - - self.m.log.debug("Fetched and generated %d packages" % len(packs)) - - for k, v in packs: - self.m.core.api.addPackage(k, v) - - #empty cache - del self.cache[:] - - else: #post the results - - - for name, url in container: - #attach container content - try: - data = self.decryptContainer(name, url) - except: - print_exc() - self.m.log.error("Could not decrypt container.") - data = [] - - for url, plugin in data: - if plugin in plugins: - plugins[plugin].append(url) - else: - plugins[plugin] = [url] - - self.m.infoResults[self.rid] = {} - - for pluginname, urls in plugins.iteritems(): - plugin = self.m.core.pluginManager.getPlugin(pluginname, True) - if hasattr(plugin, "getInfo"): - self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True) - - #force to process cache - if self.cache: - self.updateResult(pluginname, [], True) - - else: - #generate default result - result = [(url, 0, 3, url) for url in urls] - - self.updateResult(pluginname, result, True) - - self.m.infoResults[self.rid]["ALL_INFO_FETCHED"] = {} - - self.m.timestamp = time() + 5 * 60 - - - def updateDB(self, plugin, result): - self.m.core.files.updateFileInfo(result, self.pid) - - def updateResult(self, plugin, result, force=False): - #parse package name and generate result - #accumulate results - - self.cache.extend(result) - - if len(self.cache) >= 20 or force: - #used for package generating - tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size)))) - for name, size, status, url in self.cache] - - data = parseNames(tmp) - result = {} - for k, v in data.iteritems(): - for url, status in v: - status.packagename = k - result[url] = status - - self.m.setInfoResults(self.rid, result) - - self.cache = [] - - def updateCache(self, plugin, result): - self.cache.extend(result) - - def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None): - try: - result = [] #result loaded from cache - process = [] #urls to process - for url in urls: - if url in self.m.infoCache: - result.append(self.m.infoCache[url]) - else: - process.append(url) - - if result: - self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname)) - cb(pluginname, result) - - if process: - self.m.log.debug("Run Info Fetching for %s" % pluginname) - for result in plugin.getInfo(process): - #result = [ .. (name, size, status, url) .. ] - if not type(result) == list: result = [result] - - for res in result: - self.m.infoCache[res[3]] = res - - cb(pluginname, result) - - self.m.log.debug("Finished Info Fetching for %s" % pluginname) - except Exception, e: - self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") % - {"name": pluginname, "err": str(e)}) - if self.m.core.debug: - print_exc() - - # generate default results - if err: - result = [(url, 0, 3, url) for url in urls] - cb(pluginname, result) - - - def decryptContainer(self, plugin, url): - data = [] - # only works on container plugins - - self.m.log.debug("Pre decrypting %s with %s" % (url, plugin)) - - # dummy pyfile - pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1) - - pyfile.initPlugin() - - # little plugin lifecycle - try: - pyfile.plugin.setup() - pyfile.plugin.loadToDisk() - pyfile.plugin.decrypt(pyfile) - pyfile.plugin.deleteTmp() - - for pack in pyfile.plugin.packages: - pyfile.plugin.urls.extend(pack[1]) - - data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls) - - self.m.log.debug("Got %d links." % len(data)) - - except Exception, e: - self.m.log.debug("Pre decrypting error: %s" % str(e)) - finally: - pyfile.release() - - return data diff --git a/module/PullEvents.py b/module/PullEvents.py deleted file mode 100644 index 5ec76765e..000000000 --- a/module/PullEvents.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from time import time -from module.utils import uniqify - -class PullManager(): - def __init__(self, core): - self.core = core - self.clients = [] - - def newClient(self, uuid): - self.clients.append(Client(uuid)) - - def clean(self): - for n, client in enumerate(self.clients): - if client.lastActive + 30 < time(): - del self.clients[n] - - def getEvents(self, uuid): - events = [] - validUuid = False - for client in self.clients: - if client.uuid == uuid: - client.lastActive = time() - validUuid = True - while client.newEvents(): - events.append(client.popEvent().toList()) - break - if not validUuid: - self.newClient(uuid) - events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] - return uniqify(events, repr) - - def addEvent(self, event): - for client in self.clients: - client.addEvent(event) - -class Client(): - def __init__(self, uuid): - self.uuid = uuid - self.lastActive = time() - self.events = [] - - def newEvents(self): - return len(self.events) > 0 - - def popEvent(self): - if not len(self.events): - return None - return self.events.pop(0) - - def addEvent(self, event): - self.events.append(event) - -class UpdateEvent(): - def __init__(self, itype, iid, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.destination = destination - - def toList(self): - return ["update", self.destination, self.type, self.id] - -class RemoveEvent(): - def __init__(self, itype, iid, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.destination = destination - - def toList(self): - return ["remove", self.destination, self.type, self.id] - -class InsertEvent(): - def __init__(self, itype, iid, after, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.after = after - self.destination = destination - - def toList(self): - return ["insert", self.destination, self.type, self.id, self.after] - -class ReloadAllEvent(): - def __init__(self, destination): - assert destination == "queue" or destination == "collector" - self.destination = destination - - def toList(self): - return ["reload", self.destination] - -class AccountUpdateEvent(): - def toList(self): - return ["account"] - -class ConfigUpdateEvent(): - def toList(self): - return ["config"] diff --git a/module/PyFile.py b/module/PyFile.py deleted file mode 100644 index 3dede9360..000000000 --- a/module/PyFile.py +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env python -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN - @author: mkaay -""" - -from module.PullEvents import UpdateEvent -from module.utils import formatSize, lock - -from time import sleep, time - -from threading import RLock - -statusMap = { - "finished": 0, - "offline": 1, - "online": 2, - "queued": 3, - "skipped": 4, - "waiting": 5, - "temp. offline": 6, - "starting": 7, - "failed": 8, - "aborted": 9, - "decrypting": 10, - "custom": 11, - "downloading": 12, - "processing": 13, - "unknown": 14, -} - - -def setSize(self, value): - self._size = int(value) - -class PyFile(object): - """ - Represents a file object at runtime - """ - __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "pluginname", "packageid", - "error", "order", "lock", "plugin", "waitUntil", "active", "abort", "statusname", - "reconnected", "progress", "maxprogress", "pluginmodule", "pluginclass") - - def __init__(self, manager, id, url, name, size, status, error, pluginname, package, order): - self.m = manager - - self.id = int(id) - self.url = url - self.name = name - self.size = size - self.status = status - self.pluginname = pluginname - self.packageid = package #should not be used, use package() instead - self.error = error - self.order = order - # database information ends here - - self.lock = RLock() - - self.plugin = None - #self.download = None - - self.waitUntil = 0 # time() + time to wait - - # status attributes - self.active = False #obsolete? - self.abort = False - self.reconnected = False - - self.statusname = None - - self.progress = 0 - self.maxprogress = 100 - - self.m.cache[int(id)] = self - - - # will convert all sizes to ints - size = property(lambda self: self._size, setSize) - - def __repr__(self): - return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname) - - @lock - def initPlugin(self): - """ inits plugin instance """ - if not self.plugin: - self.pluginmodule = self.m.core.pluginManager.getPlugin(self.pluginname) - self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.pluginname)) - self.plugin = self.pluginclass(self) - - @lock - def hasPlugin(self): - """Thread safe way to determine this file has initialized plugin attribute - - :return: - """ - return hasattr(self, "plugin") and self.plugin - - def package(self): - """ return package instance""" - return self.m.getPackage(self.packageid) - - def setStatus(self, status): - self.status = statusMap[status] - self.sync() #@TODO needed aslong no better job approving exists - - def setCustomStatus(self, msg, status="processing"): - self.statusname = msg - self.setStatus(status) - - def getStatusName(self): - if self.status not in (13, 14) or not self.statusname: - return self.m.statusMsg[self.status] - else: - return self.statusname - - def hasStatus(self, status): - return statusMap[status] == self.status - - def sync(self): - """sync PyFile instance with database""" - self.m.updateLink(self) - - @lock - def release(self): - """sync and remove from cache""" - # file has valid package - if self.packageid > 0: - self.sync() - - if hasattr(self, "plugin") and self.plugin: - self.plugin.clean() - del self.plugin - - self.m.releaseLink(self.id) - - def delete(self): - """delete pyfile from database""" - self.m.deleteLink(self.id) - - def toDict(self): - """return dict with all information for interface""" - return self.toDbDict() - - def toDbDict(self): - """return data as dict for databse - - format: - - { - id: {'url': url, 'name': name ... } - } - - """ - return { - self.id: { - 'id': self.id, - 'url': self.url, - 'name': self.name, - 'plugin': self.pluginname, - 'size': self.getSize(), - 'format_size': self.formatSize(), - 'status': self.status, - 'statusmsg': self.getStatusName(), - 'package': self.packageid, - 'error': self.error, - 'order': self.order - } - } - - def abortDownload(self): - """abort pyfile if possible""" - while self.id in self.m.core.threadManager.processingIds(): - self.abort = True - if self.plugin and self.plugin.req: - self.plugin.req.abortDownloads() - sleep(0.1) - - self.abort = False - if self.hasPlugin() and self.plugin.req: - self.plugin.req.abortDownloads() - - self.release() - - def finishIfDone(self): - """set status to finish and release file if every thread is finished with it""" - - if self.id in self.m.core.threadManager.processingIds(): - return False - - self.setStatus("finished") - self.release() - self.m.checkAllLinksFinished() - return True - - def checkIfProcessed(self): - self.m.checkAllLinksProcessed(self.id) - - def formatWait(self): - """ formats and return wait time in humanreadable format """ - seconds = self.waitUntil - time() - - if seconds < 0: return "00:00:00" - - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) - - def formatSize(self): - """ formats size to readable format """ - return formatSize(self.getSize()) - - def formatETA(self): - """ formats eta to readable format """ - seconds = self.getETA() - - if seconds < 0: return "00:00:00" - - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) - - def getSpeed(self): - """ calculates speed """ - try: - return self.plugin.req.speed - except: - return 0 - - def getETA(self): - """ gets established time of arrival""" - try: - return self.getBytesLeft() / self.getSpeed() - except: - return 0 - - def getBytesLeft(self): - """ gets bytes left """ - try: - return self.plugin.req.size - self.plugin.req.arrived - except: - return 0 - - def getPercent(self): - """ get % of download """ - if self.status == 12: - try: - return self.plugin.req.percent - except: - return 0 - else: - return self.progress - - def getSize(self): - """ get size of download """ - try: - if self.plugin.req.size: - return self.plugin.req.size - else: - return self.size - except: - return self.size - - def notifyChange(self): - e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue") - self.m.core.pullManager.addEvent(e) - - def setProgress(self, value): - if not value == self.progress: - self.progress = value - self.notifyChange() diff --git a/module/PyPackage.py b/module/PyPackage.py deleted file mode 100644 index f3be6c886..000000000 --- a/module/PyPackage.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN - @author: mkaay -""" - -from module.PullEvents import UpdateEvent -from module.utils import save_path - -class PyPackage(): - """ - Represents a package object at runtime - """ - def __init__(self, manager, id, name, folder, site, password, queue, order): - self.m = manager - self.m.packageCache[int(id)] = self - - self.id = int(id) - self.name = name - self._folder = folder - self.site = site - self.password = password - self.queue = queue - self.order = order - self.setFinished = False - - @property - def folder(self): - return save_path(self._folder) - - def toDict(self): - """ Returns a dictionary representation of the data. - - :return: dict: {id: { attr: value }} - """ - return { - self.id: { - 'id': self.id, - 'name': self.name, - 'folder': self.folder, - 'site': self.site, - 'password': self.password, - 'queue': self.queue, - 'order': self.order, - 'links': {} - } - } - - def getChildren(self): - """get information about contained links""" - return self.m.getPackageData(self.id)["links"] - - def sync(self): - """sync with db""" - self.m.updatePackage(self) - - def release(self): - """sync and delete from cache""" - self.sync() - self.m.releasePackage(self.id) - - def delete(self): - self.m.deletePackage(self.id) - - def notifyChange(self): - e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue") - self.m.core.pullManager.addEvent(e) diff --git a/module/ThreadManager.py b/module/ThreadManager.py deleted file mode 100644 index 8937f4a29..000000000 --- a/module/ThreadManager.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from os.path import exists, join -import re -from subprocess import Popen -from threading import Event, Lock -from time import sleep, time -from traceback import print_exc -from random import choice - -import pycurl - -import PluginThread -from module.PyFile import PyFile -from module.network.RequestFactory import getURL -from module.utils import freeSpace, lock - - -class ThreadManager: - """manages the download threads, assign jobs, reconnect etc""" - - - def __init__(self, core): - """Constructor""" - self.core = core - self.log = core.log - - self.threads = [] # thread list - self.localThreads = [] #hook+decrypter threads - - self.pause = True - - self.reconnecting = Event() - self.reconnecting.clear() - self.downloaded = 0 #number of files downloaded since last cleanup - - self.lock = Lock() - - # some operations require to fetch url info from hoster, so we caching them so it wont be done twice - # contains a timestamp and will be purged after timeout - self.infoCache = {} - - # pool of ids for online check - self.resultIDs = 0 - - # threads which are fetching hoster results - self.infoResults = {} - #timeout for cache purge - self.timestamp = 0 - - pycurl.global_init(pycurl.GLOBAL_DEFAULT) - - for i in range(0, self.core.config.get("download", "max_downloads")): - self.createThread() - - - def createThread(self): - """create a download thread""" - - thread = PluginThread.DownloadThread(self) - self.threads.append(thread) - - def createInfoThread(self, data, pid): - """ - start a thread whichs fetches online status and other infos - data = [ .. () .. ] - """ - self.timestamp = time() + 5 * 60 - - PluginThread.InfoThread(self, data, pid) - - @lock - def createResultThread(self, data, add=False): - """ creates a thread to fetch online status, returns result id """ - self.timestamp = time() + 5 * 60 - - rid = self.resultIDs - self.resultIDs += 1 - - PluginThread.InfoThread(self, data, rid=rid, add=add) - - return rid - - - @lock - def getInfoResult(self, rid): - """returns result and clears it""" - self.timestamp = time() + 5 * 60 - - if rid in self.infoResults: - data = self.infoResults[rid] - self.infoResults[rid] = {} - return data - else: - return {} - - @lock - def setInfoResults(self, rid, result): - self.infoResults[rid].update(result) - - def getActiveFiles(self): - active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)] - - for t in self.localThreads: - active.extend(t.getActiveFiles()) - - return active - - def processingIds(self): - """get a id list of all pyfiles processed""" - return [x.id for x in self.getActiveFiles()] - - - def work(self): - """run all task which have to be done (this is for repetivive call by core)""" - try: - self.tryReconnect() - except Exception, e: - self.log.error(_("Reconnect Failed: %s") % str(e) ) - self.reconnecting.clear() - if self.core.debug: - print_exc() - self.checkThreadCount() - - try: - self.assignJob() - except Exception, e: - self.log.warning("Assign job error", e) - if self.core.debug: - print_exc() - - sleep(0.5) - self.assignJob() - #it may be failed non critical so we try it again - - if (self.infoCache or self.infoResults) and self.timestamp < time(): - self.infoCache.clear() - self.infoResults.clear() - self.log.debug("Cleared Result cache") - - #---------------------------------------------------------------------- - def tryReconnect(self): - """checks if reconnect needed""" - - if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()): - return False - - active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active] - - if not (0 < active.count(True) == len(active)): - return False - - if not exists(self.core.config['reconnect']['method']): - if exists(join(pypath, self.core.config['reconnect']['method'])): - self.core.config['reconnect']['method'] = join(pypath, self.core.config['reconnect']['method']) - else: - self.core.config["reconnect"]["activated"] = False - self.log.warning(_("Reconnect script not found!")) - return - - self.reconnecting.set() - - #Do reconnect - self.log.info(_("Starting reconnect")) - - while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0: - sleep(0.25) - - ip = self.getIP() - - self.core.hookManager.beforeReconnecting(ip) - - self.log.debug("Old IP: %s" % ip) - - try: - reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE) - except: - self.log.warning(_("Failed executing reconnect script!")) - self.core.config["reconnect"]["activated"] = False - self.reconnecting.clear() - if self.core.debug: - print_exc() - return - - reconn.wait() - sleep(1) - ip = self.getIP() - self.core.hookManager.afterReconnecting(ip) - - self.log.info(_("Reconnected, new IP: %s") % ip) - - self.reconnecting.clear() - - def getIP(self): - """retrieve current ip""" - services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"), - ("http://checkip.dyndns.org/",".*Current IP Address: (\S+)</body>.*")] - - ip = "" - for i in range(10): - try: - sv = choice(services) - ip = getURL(sv[0]) - ip = re.match(sv[1], ip).group(1) - break - except: - ip = "" - sleep(1) - - return ip - - #---------------------------------------------------------------------- - def checkThreadCount(self): - """checks if there are need for increasing or reducing thread count""" - - if len(self.threads) == self.core.config.get("download", "max_downloads"): - return True - elif len(self.threads) < self.core.config.get("download", "max_downloads"): - self.createThread() - else: - free = [x for x in self.threads if not x.active] - if free: - free[0].put("quit") - - - def cleanPycurl(self): - """ make a global curl cleanup (currently ununused) """ - if self.processingIds(): - return False - pycurl.global_cleanup() - pycurl.global_init(pycurl.GLOBAL_DEFAULT) - self.downloaded = 0 - self.log.debug("Cleaned up pycurl") - return True - - #---------------------------------------------------------------------- - def assignJob(self): - """assing a job to a thread if possible""" - - if self.pause or not self.core.api.isTimeDownload(): return - - #if self.downloaded > 20: - # if not self.cleanPyCurl(): return - - free = [x for x in self.threads if not x.active] - - inuse = set([(x.active.pluginname,self.getLimit(x)) for x in self.threads if x.active and x.active.hasPlugin() and x.active.plugin.account]) - inuse = map(lambda x : (x[0], x[1], len([y for y in self.threads if y.active and y.active.pluginname == x[0]])) ,inuse) - onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]] - - occ = [x.active.pluginname for x in self.threads if x.active and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit - - occ.sort() - occ = tuple(set(occ)) - job = self.core.files.getJob(occ) - if job: - try: - job.initPlugin() - except Exception, e: - self.log.critical(str(e)) - print_exc() - job.setStatus("failed") - job.error = str(e) - job.release() - return - - if job.plugin.__type__ == "hoster": - spaceLeft = freeSpace(self.core.config["general"]["download_folder"]) / 1024 / 1024 - if spaceLeft < self.core.config["general"]["min_free_space"]: - self.log.warning(_("Not enough space left on device")) - self.pause = True - - if free and not self.pause: - thread = free[0] - #self.downloaded += 1 - - thread.put(job) - else: - #put job back - if occ not in self.core.files.jobCache: - self.core.files.jobCache[occ] = [] - self.core.files.jobCache[occ].append(job.id) - - #check for decrypt jobs - job = self.core.files.getDecryptJob() - if job: - job.initPlugin() - thread = PluginThread.DecrypterThread(self, job) - - - else: - thread = PluginThread.DecrypterThread(self, job) - - def getLimit(self, thread): - limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL",["0"])[0] - return int(limit) - - def cleanup(self): - """do global cleanup, should be called when finished with pycurl""" - pycurl.global_cleanup() diff --git a/module/cli/ManageFiles.py b/module/cli/ManageFiles.py deleted file mode 100644 index 4d0377d9d..000000000 --- a/module/cli/ManageFiles.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -#Copyright (C) 2011 RaNaN -# -#This program is free software; you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation; either version 3 of the License, -#or (at your option) any later version. -# -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -#See the GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. -# -### - -from itertools import islice -from time import time - -from Handler import Handler -from printer import * - -from module.Api import Destination, PackageData - -class ManageFiles(Handler): - """ possibility to manage queue/collector """ - - def init(self): - self.target = Destination.Queue - self.pos = 0 #position in queue - self.package = -1 #choosen package - self.mode = "" # move/delete/restart - - self.cache = None - self.links = None - self.time = 0 - - def onChar(self, char): - if char in ("m", "d", "r"): - self.mode = char - self.setInput() - elif char == "p": - self.pos = max(0, self.pos - 5) - self.backspace() - elif char == "n": - self.pos += 5 - self.backspace() - - def onBackSpace(self): - if not self.input and self.mode: - self.mode = "" - if not self.input and self.package > -1: - self.package = -1 - - def onEnter(self, input): - if input == "0": - self.cli.reset() - elif self.package < 0 and self.mode: - #mode select - packs = self.parseInput(input) - if self.mode == "m": - [self.client.movePackage((self.target + 1) % 2, x) for x in packs] - elif self.mode == "d": - self.client.deletePackages(packs) - elif self.mode == "r": - [self.client.restartPackage(x) for x in packs] - - elif self.mode: - #edit links - links = self.parseInput(input, False) - - if self.mode == "d": - self.client.deleteFiles(links) - elif self.mode == "r": - map(self.client.restartFile, links) - - else: - #look into package - try: - self.package = int(input) - except: - pass - - self.cache = None - self.links = None - self.pos = 0 - self.mode = "" - self.setInput() - - - def renderBody(self, line): - if self.package < 0: - println(line, white(_("Manage Packages:"))) - else: - println(line, white((_("Manage Links:")))) - line += 1 - - if self.mode: - if self.mode == "m": - println(line, _("What do you want to move?")) - elif self.mode == "d": - println(line, _("What do you want to delete?")) - elif self.mode == "r": - println(line, _("What do you want to restart?")) - - println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1,2,3 or 1-3.") - line += 2 - else: - println(line, _("Choose what yout want to do or enter package number.")) - println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % ( - _("delete"), _("move"), _("restart"))) - line += 2 - - if self.package < 0: - #print package info - pack = self.getPackages() - i = 0 - for value in islice(pack, self.pos, self.pos + 5): - try: - println(line, mag(str(value.pid)) + ": " + value.name) - line += 1 - i += 1 - except Exception, e: - pass - for x in range(5 - i): - println(line, "") - line += 1 - else: - #print links info - pack = self.getLinks() - i = 0 - for value in islice(pack.links, self.pos, self.pos + 5): - try: - println(line, mag(value.fid) + ": %s | %s | %s" % ( - value.name, value.statusmsg, value.plugin)) - line += 1 - i += 1 - except Exception, e: - pass - for x in range(5 - i): - println(line, "") - line += 1 - - println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next")) - println(line + 1, mag("0.") + _(" back to main menu")) - - return line + 2 - - - def getPackages(self): - if self.cache and self.time + 2 < time(): - return self.cache - - if self.target == Destination.Queue: - data = self.client.getQueue() - else: - data = self.client.getCollector() - - - self.cache = data - self.time = time() - - return data - - def getLinks(self): - if self.links and self.time + 1 < time(): - return self.links - - try: - data = self.client.getPackageData(self.package) - except: - data = PackageData(links=[]) - - self.links = data - self.time = time() - - return data - - def parseInput(self, inp, package=True): - inp = inp.strip() - if "-" in inp: - l, n, h = inp.partition("-") - l = int(l) - h = int(h) - r = range(l, h + 1) - - ret = [] - if package: - for p in self.cache: - if p.pid in r: - ret.append(p.pid) - else: - for l in self.links.links: - if l.lid in r: - ret.append(l.lid) - - return ret - - else: - return [int(x) for x in inp.split(",")] diff --git a/module/common/APIExerciser.py b/module/common/APIExerciser.py deleted file mode 100644 index 96f5ce9cf..000000000 --- a/module/common/APIExerciser.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- - -import string -from threading import Thread -from random import choice, random, sample, randint -from time import time, sleep -from math import floor -import gc - -from traceback import print_exc, format_exc - -from module.remote.thriftbackend.ThriftClient import ThriftClient, Destination - -def createURLs(): - """ create some urls, some may fail """ - urls = [] - for x in range(0, randint(20, 100)): - name = "DEBUG_API" - if randint(0, 5) == 5: - name = "" #this link will fail - - urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20)))) - - return urls - -AVOID = (0,3,8) - -idPool = 0 -sumCalled = 0 - - -def startApiExerciser(core, n): - for i in range(n): - APIExerciser(core).start() - -class APIExerciser(Thread): - - - def __init__(self, core, thrift=False, user=None, pw=None): - global idPool - - Thread.__init__(self) - self.setDaemon(True) - self.core = core - self.count = 0 #number of methods - self.time = time() - - if thrift: - self.api = ThriftClient(user=user, password=pw) - else: - self.api = core.api - - - self.id = idPool - - idPool += 1 - - #self.start() - - def run(self): - - self.core.log.info("API Excerciser started %d" % self.id) - - out = open("error.log", "ab") - #core errors are not logged of course - out.write("\n" + "Starting\n") - out.flush() - - while True: - try: - self.testAPI() - except Exception: - self.core.log.error("Excerciser %d throw an execption" % self.id) - print_exc() - out.write(format_exc() + 2 * "\n") - out.flush() - - if not self.count % 100: - self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count)) - if not self.count % 1000: - out.flush() - - if not sumCalled % 1000: #not thread safe - self.core.log.info("Exercisers tested %d api calls" % sumCalled) - persec = sumCalled / (time() - self.time) - self.core.log.info("Approx. %.2f calls per second." % persec) - self.core.log.info("Approx. %.2f ms per call." % (1000 / persec)) - self.core.log.info("Collected garbage: %d" % gc.collect()) - - - #sleep(random() / 500) - - def testAPI(self): - global sumCalled - - m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles", - "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting", - "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices" , "getAccounts", "getAllUserData"] - - method = choice(m) - #print "Testing:", method - - if hasattr(self, method): - res = getattr(self, method)() - else: - res = getattr(self.api, method)() - - self.count += 1 - sumCalled += 1 - - #print res - - def addPackage(self): - name = "".join(sample(string.ascii_letters, 10)) - urls = createURLs() - - self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector])) - - - def deleteFiles(self): - info = self.api.getQueueData() - if not info: return - - pack = choice(info) - fids = pack.links - - if len(fids): - fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))] - self.api.deleteFiles(fids) - - - def deletePackages(self): - info = choice([self.api.getQueue(), self.api.getCollector()]) - if not info: return - - pids = [p.pid for p in info] - if len(pids): - pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1))) - self.api.deletePackages(pids) - - def getFileData(self): - info = self.api.getQueueData() - if info: - p = choice(info) - if p.links: - self.api.getFileData(choice(p.links).fid) - - def getPackageData(self): - info = self.api.getQueue() - if info: - self.api.getPackageData(choice(info).pid) - - def getAccounts(self): - self.api.getAccounts(False) - - def getCaptchaTask(self): - self.api.getCaptchaTask(False)
\ No newline at end of file diff --git a/module/common/JsEngine.py b/module/common/JsEngine.py deleted file mode 100644 index 576be2a1b..000000000 --- a/module/common/JsEngine.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from imp import find_module -from os.path import join, exists -from urllib import quote - - -ENGINE = "" - -DEBUG = False -JS = False -PYV8 = False -RHINO = False - - -if not ENGINE: - try: - import subprocess - - subprocess.Popen(["js", "-v"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() - p = subprocess.Popen(["js", "-e", "print(23+19)"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - #integrity check - if out.strip() == "42": - ENGINE = "js" - JS = True - except: - pass - -if not ENGINE or DEBUG: - try: - find_module("PyV8") - ENGINE = "pyv8" - PYV8 = True - except: - pass - -if not ENGINE or DEBUG: - try: - path = "" #path where to find rhino - - if exists("/usr/share/java/js.jar"): - path = "/usr/share/java/js.jar" - elif exists("js.jar"): - path = "js.jar" - elif exists(join(pypath, "js.jar")): #may raises an exception, but js.jar wasnt found anyway - path = join(pypath, "js.jar") - - if not path: - raise Exception - - import subprocess - - p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", "print(23+19)"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - #integrity check - if out.strip() == "42": - ENGINE = "rhino" - RHINO = True - except: - pass - -class JsEngine(): - def __init__(self): - self.engine = ENGINE - self.init = False - - def __nonzero__(self): - return False if not ENGINE else True - - def eval(self, script): - if not self.init: - if ENGINE == "pyv8" or (DEBUG and PYV8): - import PyV8 - global PyV8 - - self.init = True - - if type(script) == unicode: - script = script.encode("utf8") - - if not ENGINE: - raise Exception("No JS Engine") - - if not DEBUG: - if ENGINE == "pyv8": - return self.eval_pyv8(script) - elif ENGINE == "js": - return self.eval_js(script) - elif ENGINE == "rhino": - return self.eval_rhino(script) - else: - results = [] - if PYV8: - res = self.eval_pyv8(script) - print "PyV8:", res - results.append(res) - if JS: - res = self.eval_js(script) - print "JS:", res - results.append(res) - if RHINO: - res = self.eval_rhino(script) - print "Rhino:", res - results.append(res) - - warning = False - for x in results: - for y in results: - if x != y: - warning = True - - if warning: print "### WARNING ###: Different results" - - return results[0] - - def eval_pyv8(self, script): - rt = PyV8.JSContext() - rt.enter() - return rt.eval(script) - - def eval_js(self, script): - script = "print(eval(unescape('%s')))" % quote(script) - p = subprocess.Popen(["js", "-e", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1) - out, err = p.communicate() - res = out.strip() - return res - - def eval_rhino(self, script): - script = "print(eval(unescape('%s')))" % quote(script) - p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", script], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1) - out, err = p.communicate() - res = out.strip() - return res.decode("utf8").encode("ISO-8859-1") - - def error(self): - return _("No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino") - -if __name__ == "__main__": - js = JsEngine() - - test = u'"ü"+"ä"' - js.eval(test)
\ No newline at end of file diff --git a/module/common/json_layer.py b/module/common/json_layer.py deleted file mode 100644 index 4d57a9f38..000000000 --- a/module/common/json_layer.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# abstraction layer for json operations - -try: # since python 2.6 - import json - from json import loads as json_loads - from json import dumps as json_dumps -except ImportError: #use system simplejson if available - import simplejson as json - from simplejson import loads as json_loads - from simplejson import dumps as json_dumps diff --git a/module/common/packagetools.py b/module/common/packagetools.py deleted file mode 100644 index 5bfbcba95..000000000 --- a/module/common/packagetools.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env python - -# JDownloader/src/jd/controlling/LinkGrabberPackager.java - -import re -from urlparse import urlparse - -def matchFirst(string, *args): - """ matches against list of regexp and returns first match""" - for patternlist in args: - for pattern in patternlist: - r = pattern.search(string) - if r is not None: - name = r.group(1) - return name - - return string - - -def parseNames(files): - """ Generates packages names from name, data lists - - :param files: list of (name, data) - :return: packagenames mapt to data lists (eg. urls) - """ - packs = {} - - endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)" - - rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I), - re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I), - re.compile("(.*)\\.rar$", re.I), - re.compile("(.*)\\.r\\d+$", re.I), - re.compile("(.*)(\\.|_|-)\\d+$", re.I)] - - zipPats = [re.compile("(.*)\\.zip$", re.I), - re.compile("(.*)\\.z\\d+$", re.I), - re.compile("(?is).*\\.7z\\.[\\d]+$", re.I), - re.compile("(.*)\\.a.$", re.I)] - - ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"), - re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)] - - iszPats = [re.compile("(.*)\\.isz$", re.I), - re.compile("(.*)\\.i\\d{2}$", re.I)] - - pat1 = re.compile("(\\.?CD\\d+)", re.I) - pat2 = re.compile("(\\.?part\\d+)", re.I) - - pat3 = re.compile("(.+)[\\.\\-_]+$") - pat4 = re.compile("(.+)\\.\\d+\\.xtm$") - - for file, url in files: - patternMatch = False - - if file is None: - continue - - # remove trailing / - name = file.rstrip('/') - - # extract last path part .. if there is a path - split = name.rsplit("/", 1) - if len(split) > 1: - name = split.pop(1) - - #check if a already existing package may be ok for this file - # found = False - # for pack in packs: - # if pack in file: - # packs[pack].append(url) - # found = True - # break - # - # if found: continue - - # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern - before = name - name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats) - if before != name: - patternMatch = True - - # xtremsplit pattern - r = pat4.search(name) - if r is not None: - name = r.group(1) - - # remove part and cd pattern - r = pat1.search(name) - if r is not None: - name = name.replace(r.group(0), "") - patternMatch = True - - r = pat2.search(name) - if r is not None: - name = name.replace(r.group(0), "") - patternMatch = True - - # additional checks if extension pattern matched - if patternMatch: - # remove extension - index = name.rfind(".") - if index <= 0: - index = name.rfind("_") - if index > 0: - length = len(name) - index - if length <= 4: - name = name[:-length] - - # remove endings like . _ - - r = pat3.search(name) - if r is not None: - name = r.group(1) - - # replace . and _ with space - name = name.replace(".", " ") - name = name.replace("_", " ") - - name = name.strip() - else: - name = "" - - # fallback: package by hoster - if not name: - name = urlparse(file).hostname - if name: name = name.replace("www.", "") - - # fallback : default name - if not name: - name = "unknown" - - # build mapping - if name in packs: - packs[name].append(url) - else: - packs[name] = [url] - - return packs - - -if __name__ == "__main__": - from os.path import join - from pprint import pprint - - f = open(join("..", "..", "testlinks2.txt"), "rb") - urls = [(x.strip(), x.strip()) for x in f.readlines() if x.strip()] - f.close() - - print "Having %d urls." % len(urls) - - packs = parseNames(urls) - - pprint(packs) - - print "Got %d urls." % sum([len(x) for x in packs.itervalues()]) diff --git a/module/config/default.conf b/module/config/default.conf deleted file mode 100644 index 2e9152ba2..000000000 --- a/module/config/default.conf +++ /dev/null @@ -1,65 +0,0 @@ -version: 1 - -remote - "Remote": - int port : "Port" = 7227 - ip listenaddr : "Adress" = 0.0.0.0 - bool nolocalauth : "No authentication on local connections" = True - bool activated : "Activated" = True -ssl - "SSL": - bool activated : "Activated"= False - file cert : "SSL Certificate" = ssl.crt - file key : "SSL Key" = ssl.key -webinterface - "Webinterface": - bool activated : "Activated" = True - builtin;threaded;fastcgi;lightweight server : "Server" = builtin - bool https : "Use HTTPS" = False - ip host : "IP" = 0.0.0.0 - int port : "Port" = 8001 - str template : "Template" = default - str prefix: "Path Prefix" = -log - "Log": - bool file_log : "File Log" = True - folder log_folder : "Folder" = Logs - int log_count : "Count" = 5 - int log_size : "Size in kb" = 100 - bool log_rotate : "Log Rotate" = True -general - "General": - en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR language : "Language" = en - folder download_folder : "Download Folder" = Downloads - bool debug_mode : "Debug Mode" = False - bool checksum : "Use Checksum" = False - int min_free_space : "Min Free Space (MB)" = 200 - bool folder_per_package : "Create folder for each package" = True - int renice : "CPU Priority" = 0 -download - "Download": - int chunks : "Max connections for one download" = 3 - int max_downloads : "Max Parallel Downloads" = 3 - int max_speed : "Max Download Speed in kb/s" = -1 - bool limit_speed : "Limit Download Speed" = False - str interface : "Download interface to bind (ip or Name)" = None - bool ipv6 : "Allow IPv6" = False - bool skip_existing : "Skip already existing files" = False -permission - "Permissions": - bool change_user : "Change user of running process" = False - str user : "Username" = user - str folder : "Folder Permission mode" = 0755 - bool change_file : "Change file mode of downloads" = False - str file : "Filemode for Downloads" = 0644 - bool change_group : "Change group of running process" = False - str group : "Groupname" = users - bool change_dl : "Change Group and User of Downloads" = False -reconnect - "Reconnect": - bool activated : "Use Reconnect" = False - str method : "Method" = None - time startTime : "Start" = 0:00 - time endTime : "End" = 0:00 -downloadTime - "Download Time": - time start : "Start" = 0:00 - time end : "End" = 0:00 -proxy - "Proxy": - str address : "Address" = "localhost" - int port : "Port" = 7070 - http;socks4;socks5 type : "Protocol" = http - str username : "Username" = None - password password : "Password" = None - bool proxy : "Use Proxy" = False diff --git a/module/config/gui_default.xml b/module/config/gui_default.xml deleted file mode 100644 index 1faed776f..000000000 --- a/module/config/gui_default.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" ?> -<root> - <connections> - <connection default="True" type="local" id="33965310e19b4a869112c43b39a16440"> - <name>Local</name> - </connection> - </connections> - <mainWindow> - <state></state> - <geometry></geometry> - </mainWindow> - <language>en</language> -</root> diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py deleted file mode 100644 index 9530390c3..000000000 --- a/module/database/DatabaseBackend.py +++ /dev/null @@ -1,352 +0,0 @@ -#!/usr/bin/env python -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN - @author: mkaay -""" -from threading import Thread -from threading import Event -from os import remove -from os.path import exists -from shutil import move - -from Queue import Queue -from traceback import print_exc - -from module.utils import chmod - -try: - from pysqlite2 import dbapi2 as sqlite3 -except: - import sqlite3 - -DB_VERSION = 4 - -class style(): - db = None - - @classmethod - def setDB(cls, db): - cls.db = db - - @classmethod - def inner(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return f(cls.db, *args, **kwargs) - return x - - @classmethod - def queue(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return cls.db.queue(f, *args, **kwargs) - return x - - @classmethod - def async(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return cls.db.async(f, *args, **kwargs) - return x - -class DatabaseJob(): - def __init__(self, f, *args, **kwargs): - self.done = Event() - - self.f = f - self.args = args - self.kwargs = kwargs - - self.result = None - self.exception = False - -# import inspect -# self.frame = inspect.currentframe() - - def __repr__(self): - from os.path import basename - frame = self.frame.f_back - output = "" - for i in range(5): - output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name) - frame = frame.f_back - del frame - del self.frame - - return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result) - - def processJob(self): - try: - self.result = self.f(*self.args, **self.kwargs) - except Exception, e: - print_exc() - try: - print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e - except: - pass - - self.exception = e - finally: - self.done.set() - - def wait(self): - self.done.wait() - -class DatabaseBackend(Thread): - subs = [] - def __init__(self, core): - Thread.__init__(self) - self.setDaemon(True) - self.core = core - - self.jobs = Queue() - - self.setuplock = Event() - - style.setDB(self) - - def setup(self): - self.start() - self.setuplock.wait() - - def run(self): - """main loop, which executes commands""" - convert = self._checkVersion() #returns None or current version - - self.conn = sqlite3.connect("files.db") - chmod("files.db", 0600) - - self.c = self.conn.cursor() #compatibility - - if convert is not None: - self._convertDB(convert) - - self._createTables() - self._migrateUser() - - self.conn.commit() - - self.setuplock.set() - - while True: - j = self.jobs.get() - if j == "quit": - self.c.close() - self.conn.close() - break - j.processJob() - - @style.queue - def shutdown(self): - self.conn.commit() - self.jobs.put("quit") - - def _checkVersion(self): - """ check db version and delete it if needed""" - if not exists("files.version"): - f = open("files.version", "wb") - f.write(str(DB_VERSION)) - f.close() - return - - f = open("files.version", "rb") - v = int(f.read().strip()) - f.close() - if v < DB_VERSION: - if v < 2: - try: - self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) - except: - print "Filedatabase was deleted due to incompatible version." - remove("files.version") - move("files.db", "files.backup.db") - f = open("files.version", "wb") - f.write(str(DB_VERSION)) - f.close() - return v - - def _convertDB(self, v): - try: - getattr(self, "_convertV%i" % v)() - except: - try: - self.core.log.error(_("Filedatabase could NOT be converted.")) - except: - print "Filedatabase could NOT be converted." - - #--convert scripts start - - def _convertV2(self): - self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') - try: - self.manager.core.log.info(_("Database was converted from v2 to v3.")) - except: - print "Database was converted from v2 to v3." - self._convertV3() - - def _convertV3(self): - self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') - try: - self.manager.core.log.info(_("Database was converted from v3 to v4.")) - except: - print "Database was converted from v3 to v4." - - #--convert scripts end - - def _createTables(self): - """create tables for database""" - - self.c.execute('CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)') - self.c.execute('CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))') - self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)') - self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') - self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') - - self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \ - SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\ - FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\ - (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \ - FROM packages p JOIN links l ON p.id = l.package AND l.status in (0,4,13) GROUP BY p.id) s ON s.id = p.id \ - GROUP BY p.id') - - #try to lower ids - self.c.execute('SELECT max(id) FROM LINKS') - fid = self.c.fetchone()[0] - if fid: - fid = int(fid) - else: - fid = 0 - self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links")) - - - self.c.execute('SELECT max(id) FROM packages') - pid = self.c.fetchone()[0] - if pid: - pid = int(pid) - else: - pid = 0 - self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages")) - - self.c.execute('VACUUM') - - - def _migrateUser(self): - if exists("pyload.db"): - try: - self.core.log.info(_("Converting old Django DB")) - except: - print "Converting old Django DB" - conn = sqlite3.connect('pyload.db') - c = conn.cursor() - c.execute("SELECT username, password, email from auth_user WHERE is_superuser") - users = [] - for r in c: - pw = r[1].split("$") - users.append((r[0], pw[1] + pw[2], r[2])) - c.close() - conn.close() - - self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users) - move("pyload.db", "pyload.old.db") - - def createCursor(self): - return self.conn.cursor() - - @style.async - def commit(self): - self.conn.commit() - - @style.queue - def syncSave(self): - self.conn.commit() - - @style.async - def rollback(self): - self.conn.rollback() - - def async(self, f, *args, **kwargs): - args = (self, ) + args - job = DatabaseJob(f, *args, **kwargs) - self.jobs.put(job) - - def queue(self, f, *args, **kwargs): - args = (self, ) + args - job = DatabaseJob(f, *args, **kwargs) - self.jobs.put(job) - job.wait() - return job.result - - @classmethod - def registerSub(cls, klass): - cls.subs.append(klass) - - @classmethod - def unregisterSub(cls, klass): - cls.subs.remove(klass) - - def __getattr__(self, attr): - for sub in DatabaseBackend.subs: - if hasattr(sub, attr): - return getattr(sub, attr) - -if __name__ == "__main__": - db = DatabaseBackend() - db.setup() - - class Test(): - @style.queue - def insert(db): - c = db.createCursor() - for i in range(1000): - c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) - @style.async - def insert2(db): - c = db.createCursor() - for i in range(1000*1000): - c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) - - @style.queue - def select(db): - c = db.createCursor() - for i in range(10): - res = c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", ("foo", i)) - print res.fetchone() - - @style.queue - def error(db): - c = db.createCursor() - print "a" - c.execute("SELECT myerror FROM storage WHERE identifier=? AND key=?", ("foo", i)) - print "e" - - db.registerSub(Test) - from time import time - start = time() - for i in range(100): - db.insert() - end = time() - print end-start - - start = time() - db.insert2() - end = time() - print end-start - - db.error() - diff --git a/module/database/FileDatabase.py b/module/database/FileDatabase.py deleted file mode 100644 index 7e7efb028..000000000 --- a/module/database/FileDatabase.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/env python -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN - @author: mkaay -""" - - -from threading import RLock -from time import time - -from module.utils import formatSize, lock -from module.PullEvents import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent -from module.PyPackage import PyPackage -from module.PyFile import PyFile -from module.database import style, DatabaseBackend - -try: - from pysqlite2 import dbapi2 as sqlite3 -except: - import sqlite3 - - -class FileHandler: - """Handles all request made to obtain information, - modify status or other request for links or packages""" - - def __init__(self, core): - """Constructor""" - self.core = core - - # translations - self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"), _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"), _("downloading"), _("processing"), _("unknown")] - - self.cache = {} #holds instances for files - self.packageCache = {} # same for packages - #@TODO: purge the cache - - self.jobCache = {} - - self.lock = RLock() #@TODO should be a Lock w/o R - #self.lock._Verbose__verbose = True - - self.filecount = -1 # if an invalid value is set get current value from db - self.queuecount = -1 #number of package to be loaded - self.unchanged = False #determines if any changes was made since last call - - self.db = self.core.db - - def change(func): - def new(*args): - args[0].unchanged = False - args[0].filecount = -1 - args[0].queuecount = -1 - args[0].jobCache = {} - return func(*args) - return new - - #---------------------------------------------------------------------- - def save(self): - """saves all data to backend""" - self.db.commit() - - #---------------------------------------------------------------------- - def syncSave(self): - """saves all data to backend and waits until all data are written""" - pyfiles = self.cache.values() - for pyfile in pyfiles: - pyfile.sync() - - pypacks = self.packageCache.values() - for pypack in pypacks: - pypack.sync() - - self.db.syncSave() - - @lock - def getCompleteData(self, queue=1): - """gets a complete data representation""" - - data = self.db.getAllLinks(queue) - packs = self.db.getAllPackages(queue) - - data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()]) - - for x in self.packageCache.itervalues(): - if x.queue != queue or x.id not in packs: continue - packs[x.id].update(x.toDict()[x.id]) - - for key, value in data.iteritems(): - if value["package"] in packs: - packs[value["package"]]["links"][key] = value - - return packs - - @lock - def getInfoData(self, queue=1): - """gets a data representation without links""" - - packs = self.db.getAllPackages(queue) - for x in self.packageCache.itervalues(): - if x.queue != queue or x.id not in packs: continue - packs[x.id].update(x.toDict()[x.id]) - - return packs - - @lock - @change - def addLinks(self, urls, package): - """adds links""" - - self.core.hookManager.dispatchEvent("linksAdded", urls, package) - - data = self.core.pluginManager.parseUrls(urls) - - self.db.addLinks(data, package) - self.core.threadManager.createInfoThread(data, package) - - #@TODO change from reloadAll event to package update event - self.core.pullManager.addEvent(ReloadAllEvent("collector")) - - #---------------------------------------------------------------------- - @lock - @change - def addPackage(self, name, folder, queue=0): - """adds a package, default to link collector""" - lastID = self.db.addPackage(name, folder, queue) - p = self.db.getPackage(lastID) - e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue") - self.core.pullManager.addEvent(e) - return lastID - - #---------------------------------------------------------------------- - @lock - @change - def deletePackage(self, id): - """delete package and all contained links""" - - p = self.getPackage(id) - if not p: - if id in self.packageCache: del self.packageCache[id] - return - - oldorder = p.order - queue = p.queue - - e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") - - pyfiles = self.cache.values() - - for pyfile in pyfiles: - if pyfile.packageid == id: - pyfile.abortDownload() - pyfile.release() - - self.db.deletePackage(p) - self.core.pullManager.addEvent(e) - self.core.hookManager.dispatchEvent("packageDeleted", id) - - if id in self.packageCache: - del self.packageCache[id] - - packs = self.packageCache.values() - for pack in packs: - if pack.queue == queue and pack.order > oldorder: - pack.order -= 1 - pack.notifyChange() - - #---------------------------------------------------------------------- - @lock - @change - def deleteLink(self, id): - """deletes links""" - - f = self.getFile(id) - if not f: - return None - - pid = f.packageid - e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue") - - oldorder = f.order - - if id in self.core.threadManager.processingIds(): - self.cache[id].abortDownload() - - if id in self.cache: - del self.cache[id] - - self.db.deleteLink(f) - - self.core.pullManager.addEvent(e) - - p = self.getPackage(pid) - if not len(p.getChildren()): - p.delete() - - pyfiles = self.cache.values() - for pyfile in pyfiles: - if pyfile.packageid == pid and pyfile.order > oldorder: - pyfile.order -= 1 - pyfile.notifyChange() - - #---------------------------------------------------------------------- - def releaseLink(self, id): - """removes pyfile from cache""" - if id in self.cache: - del self.cache[id] - - #---------------------------------------------------------------------- - def releasePackage(self, id): - """removes package from cache""" - if id in self.packageCache: - del self.packageCache[id] - - #---------------------------------------------------------------------- - def updateLink(self, pyfile): - """updates link""" - self.db.updateLink(pyfile) - - e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue") - self.core.pullManager.addEvent(e) - - #---------------------------------------------------------------------- - def updatePackage(self, pypack): - """updates a package""" - self.db.updatePackage(pypack) - - e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue") - self.core.pullManager.addEvent(e) - - #---------------------------------------------------------------------- - def getPackage(self, id): - """return package instance""" - - if id in self.packageCache: - return self.packageCache[id] - else: - return self.db.getPackage(id) - - #---------------------------------------------------------------------- - def getPackageData(self, id): - """returns dict with package information""" - pack = self.getPackage(id) - - if not pack: - return None - - pack = pack.toDict()[id] - - data = self.db.getPackageData(id) - - tmplist = [] - - cache = self.cache.values() - for x in cache: - if int(x.toDbDict()[x.id]["package"]) == int(id): - tmplist.append((x.id, x.toDbDict()[x.id])) - data.update(tmplist) - - pack["links"] = data - - return pack - - #---------------------------------------------------------------------- - def getFileData(self, id): - """returns dict with file information""" - if id in self.cache: - return self.cache[id].toDbDict() - - return self.db.getLinkData(id) - - #---------------------------------------------------------------------- - def getFile(self, id): - """returns pyfile instance""" - if id in self.cache: - return self.cache[id] - else: - return self.db.getFile(id) - - #---------------------------------------------------------------------- - @lock - def getJob(self, occ): - """get suitable job""" - - #@TODO clean mess - #@TODO improve selection of valid jobs - - if occ in self.jobCache: - if self.jobCache[occ]: - id = self.jobCache[occ].pop() - if id == "empty": - pyfile = None - self.jobCache[occ].append("empty") - else: - pyfile = self.getFile(id) - else: - jobs = self.db.getJob(occ) - jobs.reverse() - if not jobs: - self.jobCache[occ].append("empty") - pyfile = None - else: - self.jobCache[occ].extend(jobs) - pyfile = self.getFile(self.jobCache[occ].pop()) - - else: - self.jobCache = {} #better not caching to much - jobs = self.db.getJob(occ) - jobs.reverse() - self.jobCache[occ] = jobs - - if not jobs: - self.jobCache[occ].append("empty") - pyfile = None - else: - pyfile = self.getFile(self.jobCache[occ].pop()) - - #@TODO: maybe the new job has to be approved... - - - #pyfile = self.getFile(self.jobCache[occ].pop()) - return pyfile - - @lock - def getDecryptJob(self): - """return job for decrypting""" - if "decrypt" in self.jobCache: - return None - - plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys() - plugins = str(tuple(plugins)) - - jobs = self.db.getPluginJob(plugins) - if jobs: - return self.getFile(jobs[0]) - else: - self.jobCache["decrypt"] = "empty" - return None - - def getFileCount(self): - """returns number of files""" - - if self.filecount == -1: - self.filecount = self.db.filecount(1) - - return self.filecount - - def getQueueCount(self, force=False): - """number of files that have to be processed""" - if self.queuecount == -1 or force: - self.queuecount = self.db.queuecount(1) - - return self.queuecount - - def checkAllLinksFinished(self): - """checks if all files are finished and dispatch event""" - - if not self.getQueueCount(True): - self.core.hookManager.dispatchEvent("allDownloadsFinished") - self.core.log.debug("All downloads finished") - return True - - return False - - def checkAllLinksProcessed(self, fid): - """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting""" - - # reset count so statistic will update (this is called when dl was processed) - self.resetCount() - - if not self.db.processcount(1, fid): - self.core.hookManager.dispatchEvent("allDownloadsProcessed") - self.core.log.debug("All downloads processed") - return True - - return False - - def resetCount(self): - self.queuecount = -1 - - @lock - @change - def restartPackage(self, id): - """restart package""" - pyfiles = self.cache.values() - for pyfile in pyfiles: - if pyfile.packageid == id: - self.restartFile(pyfile.id) - - self.db.restartPackage(id) - - if id in self.packageCache: - self.packageCache[id].setFinished = False - - e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue") - self.core.pullManager.addEvent(e) - - @lock - @change - def restartFile(self, id): - """ restart file""" - if id in self.cache: - self.cache[id].status = 3 - self.cache[id].name = self.cache[id].url - self.cache[id].error = "" - self.cache[id].abortDownload() - - - self.db.restartFile(id) - - e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue") - self.core.pullManager.addEvent(e) - - @lock - @change - def setPackageLocation(self, id, queue): - """push package to queue""" - - p = self.db.getPackage(id) - oldorder = p.order - - e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") - self.core.pullManager.addEvent(e) - - self.db.clearPackageOrder(p) - - p = self.db.getPackage(id) - - p.queue = queue - self.db.updatePackage(p) - - self.db.reorderPackage(p, -1, True) - - packs = self.packageCache.values() - for pack in packs: - if pack.queue != queue and pack.order > oldorder: - pack.order -= 1 - pack.notifyChange() - - self.db.commit() - self.releasePackage(id) - p = self.getPackage(id) - - e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue") - self.core.pullManager.addEvent(e) - - @lock - @change - def reorderPackage(self, id, position): - p = self.getPackage(id) - - e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") - self.core.pullManager.addEvent(e) - self.db.reorderPackage(p, position) - - packs = self.packageCache.values() - for pack in packs: - if pack.queue != p.queue or pack.order < 0 or pack == p: continue - if p.order > position: - if pack.order >= position and pack.order < p.order: - pack.order += 1 - pack.notifyChange() - elif p.order < position: - if pack.order <= position and pack.order > p.order: - pack.order -= 1 - pack.notifyChange() - - p.order = position - self.db.commit() - - e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue") - self.core.pullManager.addEvent(e) - - @lock - @change - def reorderFile(self, id, position): - f = self.getFileData(id) - f = f[id] - - e = RemoveEvent("file", id, "collector" if not self.getPackage(f["package"]).queue else "queue") - self.core.pullManager.addEvent(e) - - self.db.reorderLink(f, position) - - pyfiles = self.cache.values() - for pyfile in pyfiles: - if pyfile.packageid != f["package"] or pyfile.order < 0: continue - if f["order"] > position: - if pyfile.order >= position and pyfile.order < f["order"]: - pyfile.order += 1 - pyfile.notifyChange() - elif f["order"] < position: - if pyfile.order <= position and pyfile.order > f["order"]: - pyfile.order -= 1 - pyfile.notifyChange() - - if id in self.cache: - self.cache[id].order = position - - self.db.commit() - - e = InsertEvent("file", id, position, "collector" if not self.getPackage(f["package"]).queue else "queue") - self.core.pullManager.addEvent(e) - - @change - def updateFileInfo(self, data, pid): - """ updates file info (name, size, status, url)""" - ids = self.db.updateLinkInfo(data) - e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue") - self.core.pullManager.addEvent(e) - - def checkPackageFinished(self, pyfile): - """ checks if package is finished and calls hookmanager """ - - ids = self.db.getUnfinished(pyfile.packageid) - if not ids or (pyfile.id in ids and len(ids) == 1): - if not pyfile.package().setFinished: - self.core.log.info(_("Package finished: %s") % pyfile.package().name) - self.core.hookManager.packageFinished(pyfile.package()) - pyfile.package().setFinished = True - - - def reCheckPackage(self, pid): - """ recheck links in package """ - data = self.db.getPackageData(pid) - - urls = [] - - for pyfile in data.itervalues(): - if pyfile["status"] not in (0, 12, 13): - urls.append((pyfile["url"], pyfile["plugin"])) - - self.core.threadManager.createInfoThread(urls, pid) - - @lock - @change - def deleteFinishedLinks(self): - """ deletes finished links and packages, return deleted packages """ - - old_packs = self.getInfoData(0) - old_packs.update(self.getInfoData(1)) - - self.db.deleteFinished() - - new_packs = self.db.getAllPackages(0) - new_packs.update(self.db.getAllPackages(1)) - #get new packages only from db - - deleted = [] - for id in old_packs.iterkeys(): - if id not in new_packs: - deleted.append(id) - self.deletePackage(int(id)) - - return deleted - - @lock - @change - def restartFailed(self): - """ restart all failed links """ - self.db.restartFailed() - -class FileMethods(): - @style.queue - def filecount(self, queue): - """returns number of files in queue""" - self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?", (queue, )) - return self.c.fetchone()[0] - - @style.queue - def queuecount(self, queue): - """ number of files in queue not finished yet""" - self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0,4)", (queue, )) - return self.c.fetchone()[0] - - @style.queue - def processcount(self, queue, fid): - """ number of files which have to be proccessed """ - self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2,3,5,7,12) AND l.id != ?", (queue, str(fid))) - return self.c.fetchone()[0] - - @style.inner - def _nextPackageOrder(self, queue=0): - self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,)) - max = self.c.fetchone()[0] - if max is not None: - return max + 1 - else: - return 0 - - @style.inner - def _nextFileOrder(self, package): - self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,)) - max = self.c.fetchone()[0] - if max is not None: - return max + 1 - else: - return 0 - - @style.queue - def addLink(self, url, name, plugin, package): - order = self._nextFileOrder(package) - self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', (url, name, plugin, package, order)) - return self.c.lastrowid - - @style.queue - def addLinks(self, links, package): - """ links is a list of tupels (url,plugin)""" - order = self._nextFileOrder(package) - orders = [order + x for x in range(len(links))] - links = [(x[0], x[0], x[1], package, o) for x, o in zip(links, orders)] - self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links) - - @style.queue - def addPackage(self, name, folder, queue): - order = self._nextPackageOrder(queue) - self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)', (name, folder, queue, order)) - return self.c.lastrowid - - @style.queue - def deletePackage(self, p): - - self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),)) - self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),)) - self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?', (p.order, p.queue)) - - @style.queue - def deleteLink(self, f): - - self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),)) - self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?', (f.order, str(f.packageid))) - - - @style.queue - def getAllLinks(self, q): - """return information about all links in queue q - - q0 queue - q1 collector - - format: - - { - id: {'name': name, ... 'package': id }, ... - } - - """ - self.c.execute('SELECT l.id,l.url,l.name,l.size,l.status,l.error,l.plugin,l.package,l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder', (q,)) - data = {} - for r in self.c: - data[r[0]] = { - 'id': r[0], - 'url': r[1], - 'name': r[2], - 'size': r[3], - 'format_size': formatSize(r[3]), - 'status': r[4], - 'statusmsg': self.manager.statusMsg[r[4]], - 'error': r[5], - 'plugin': r[6], - 'package': r[7], - 'order': r[8], - } - - return data - - @style.queue - def getAllPackages(self, q): - """return information about packages in queue q - (only useful in get all data) - - q0 queue - q1 collector - - format: - - { - id: {'name': name ... 'links': {} }, ... - } - """ - self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \ - FROM packages p JOIN pstats s ON p.id = s.id \ - WHERE p.queue=? ORDER BY p.packageorder', str(q)) - - data = {} - for r in self.c: - data[r[0]] = { - 'id': r[0], - 'name': r[1], - 'folder': r[2], - 'site': r[3], - 'password': r[4], - 'queue': r[5], - 'order': r[6], - 'sizetotal': int(r[7]), - 'sizedone': r[8] if r[8] else 0, #these can be None - 'linksdone': r[9] if r[9] else 0, - 'linkstotal': r[10], - 'links': {} - } - - return data - - @style.queue - def getLinkData(self, id): - """get link information as dict""" - self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE id=?', (str(id), )) - data = {} - r = self.c.fetchone() - if not r: - return None - data[r[0]] = { - 'id': r[0], - 'url': r[1], - 'name': r[2], - 'size': r[3], - 'format_size': formatSize(r[3]), - 'status': r[4], - 'statusmsg': self.manager.statusMsg[r[4]], - 'error': r[5], - 'plugin': r[6], - 'package': r[7], - 'order': r[8], - } - - return data - - @style.queue - def getPackageData(self, id): - """get data about links for a package""" - self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE package=? ORDER BY linkorder', (str(id), )) - - data = {} - for r in self.c: - data[r[0]] = { - 'id': r[0], - 'url': r[1], - 'name': r[2], - 'size': r[3], - 'format_size': formatSize(r[3]), - 'status': r[4], - 'statusmsg': self.manager.statusMsg[r[4]], - 'error': r[5], - 'plugin': r[6], - 'package': r[7], - 'order': r[8], - } - - return data - - - @style.async - def updateLink(self, f): - self.c.execute('UPDATE links SET url=?,name=?,size=?,status=?,error=?,package=? WHERE id=?', (f.url, f.name, f.size, f.status, f.error, str(f.packageid), str(f.id))) - - @style.queue - def updatePackage(self, p): - self.c.execute('UPDATE packages SET name=?,folder=?,site=?,password=?,queue=? WHERE id=?', (p.name, p.folder, p.site, p.password, p.queue, str(p.id))) - - @style.queue - def updateLinkInfo(self, data): - """ data is list of tupels (name, size, status, url) """ - self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1,2,3,14)', data) - ids = [] - self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data])) - for r in self.c: - ids.append(int(r[0])) - return ids - - @style.queue - def reorderPackage(self, p, position, noMove=False): - if position == -1: - position = self._nextPackageOrder(p.queue) - if not noMove: - if p.order > position: - self.c.execute('UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue)) - elif p.order < position: - self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue)) - - self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id))) - - @style.queue - def reorderLink(self, f, position): - """ reorder link with f as dict for pyfile """ - if f["order"] > position: - self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?', (position, f["order"], f["package"])) - elif f["order"] < position: - self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?', (position, f["order"], f["package"])) - - self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f["id"])) - - - @style.queue - def clearPackageOrder(self, p): - self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id))) - self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?', (p.order, p.queue, str(p.id))) - - @style.async - def restartFile(self, id): - self.c.execute('UPDATE links SET status=3,error="" WHERE id=?', (str(id),)) - - @style.async - def restartPackage(self, id): - self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),)) - - @style.queue - def getPackage(self, id): - """return package instance from id""" - self.c.execute("SELECT name,folder,site,password,queue,packageorder FROM packages WHERE id=?", (str(id), )) - r = self.c.fetchone() - if not r: return None - return PyPackage(self.manager, id, * r) - - #---------------------------------------------------------------------- - @style.queue - def getFile(self, id): - """return link instance from id""" - self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?", (str(id), )) - r = self.c.fetchone() - if not r: return None - return PyFile(self.manager, id, * r) - - - @style.queue - def getJob(self, occ): - """return pyfile ids, which are suitable for download and dont use a occupied plugin""" - - #@TODO improve this hardcoded method - pre = "('DLC', 'LinkList', 'SerienjunkiesOrg', 'CCF', 'RSDF')" #plugins which are processed in collector - - cmd = "(" - for i, item in enumerate(occ): - if i: cmd += ", " - cmd += "'%s'" % item - - cmd += ")" - - cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre) - - self.c.execute(cmd) # very bad! - - return [x[0] for x in self.c] - - @style.queue - def getPluginJob(self, plugins): - """returns pyfile ids with suited plugins""" - cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins - - self.c.execute(cmd) # very bad! - - return [x[0] for x in self.c] - - @style.queue - def getUnfinished(self, pid): - """return list of max length 3 ids with pyfiles in package not finished or processed""" - - self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),)) - return [r[0] for r in self.c] - - @style.queue - def deleteFinished(self): - self.c.execute("DELETE FROM links WHERE status IN (0,4)") - self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)") - - @style.queue - def restartFailed(self): - self.c.execute("UPDATE links SET status=3,error='' WHERE status IN (6, 8, 9)") - - @style.queue - def findDuplicates(self, id, folder, filename): - """ checks if filename exists with different id and same package """ - self.c.execute("SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?", (folder, id, filename)) - return self.c.fetchone() - - @style.queue - def purgeLinks(self): - self.c.execute("DELETE FROM links;") - self.c.execute("DELETE FROM packages;") - -DatabaseBackend.registerSub(FileMethods) - -if __name__ == "__main__": - - pypath = "." - _ = lambda x: x - - db = FileHandler(None) - - #p = PyFile(db, 5) - #sleep(0.1) - - a = time() - - #print db.addPackage("package", "folder" , 1) - - pack = db.db.addPackage("package", "folder", 1) - - updates = [] - - - for x in range(0, 200): - x = str(x) - db.db.addLink("http://somehost.com/hoster/file/download?file_id=" + x, x, "BasePlugin", pack) - updates.append(("new name" + x, 0, 3, "http://somehost.com/hoster/file/download?file_id=" + x)) - - - for x in range(0, 100): - updates.append(("unimportant%s" % x, 0, 3, "a really long non existent url%s" % x)) - - db.db.commit() - - b = time() - print "adding 200 links, single sql execs, no commit", b-a - - print db.getCompleteData(1) - - c = time() - - - db.db.updateLinkInfo(updates) - - d = time() - - print "updates", d-c - - print db.getCompleteData(1) - - - e = time() - - print "complete data", e-d diff --git a/module/database/StorageDatabase.py b/module/database/StorageDatabase.py deleted file mode 100644 index 3ed29625f..000000000 --- a/module/database/StorageDatabase.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from module.database import style -from module.database import DatabaseBackend - -class StorageMethods(): - @style.queue - def setStorage(db, identifier, key, value): - db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key)) - if db.c.fetchone() is not None: - db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key)) - else: - db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value)) - - @style.queue - def getStorage(db, identifier, key=None): - if key is not None: - db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key)) - row = db.c.fetchone() - if row is not None: - return row[0] - else: - db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier, )) - d = {} - for row in db.c: - d[row[0]] = row[1] - return d - - @style.queue - def delStorage(db, identifier, key): - db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key)) - -DatabaseBackend.registerSub(StorageMethods) diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py deleted file mode 100644 index 0c781057d..000000000 --- a/module/database/UserDatabase.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from hashlib import sha1 -import random - -from DatabaseBackend import DatabaseBackend -from DatabaseBackend import style - -class UserMethods(): - @style.queue - def checkAuth(db, user, password): - c = db.c - c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user, )) - r = c.fetchone() - if not r: - return {} - - salt = r[2][:5] - pw = r[2][5:] - h = sha1(salt + password) - if h.hexdigest() == pw: - return {"id": r[0], "name": r[1], "role": r[3], - "permission": r[4], "template": r[5], "email": r[6]} - else: - return {} - - @style.queue - def addUser(db, user, password): - salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)]) - h = sha1(salt + password) - password = salt + h.hexdigest() - - c = db.c - c.execute('SELECT name FROM users WHERE name=?', (user, )) - if c.fetchone() is not None: - c.execute('UPDATE users SET password=? WHERE name=?', (password, user)) - else: - c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password)) - - - @style.queue - def changePassword(db, user, oldpw, newpw): - db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user, )) - r = db.c.fetchone() - if not r: - return False - - salt = r[2][:5] - pw = r[2][5:] - h = sha1(salt + oldpw) - if h.hexdigest() == pw: - salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)]) - h = sha1(salt + newpw) - password = salt + h.hexdigest() - - db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user)) - return True - - return False - - - @style.async - def setPermission(db, user, perms): - db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user)) - - @style.async - def setRole(db, user, role): - db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user)) - - - @style.queue - def listUsers(db): - db.c.execute('SELECT name FROM users') - users = [] - for row in db.c: - users.append(row[0]) - return users - - @style.queue - def getAllUserData(db): - db.c.execute("SELECT name, permission, role, template, email FROM users") - user = {} - for r in db.c: - user[r[0]] = {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]} - - return user - - @style.queue - def removeUser(db, user): - db.c.execute('DELETE FROM users WHERE name=?', (user, )) - -DatabaseBackend.registerSub(UserMethods) diff --git a/module/database/__init__.py b/module/database/__init__.py deleted file mode 100644 index 545789c0c..000000000 --- a/module/database/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from DatabaseBackend import DatabaseBackend -from DatabaseBackend import style - -from FileDatabase import FileHandler -from UserDatabase import UserMethods -from StorageDatabase import StorageMethods
\ No newline at end of file diff --git a/module/debug.py b/module/debug.py deleted file mode 100644 index 8d1ddd3d0..000000000 --- a/module/debug.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -#coding:utf-8 - -import re -import InitHomeDir -from os import listdir - -class Wrapper(object): - pass - -def filter_info(line): - if "object at 0x" in line: - return False - elif " at line " in line: - return False - elif " <DownloadThread(" in line: - return False - elif "<class '" in line: - return False - elif "PyFile " in line: - return False - elif " <module '" in line: - return False - - else: - return True - -def appendName(lines, name): - test = re.compile("^[a-zA-z0-9]+ = ") - - for i, line in enumerate(lines): - if test.match(line): - lines[i] = name+"."+line - - return lines - -def initReport(): - reports = [] - for f in listdir("."): - if f.startswith("debug_"): - reports.append(f) - - for i, f in enumerate(reports): - print "%s. %s" % (i,f) - - choice = raw_input("Choose Report: ") - - report = reports[int(choice)] - - f = open(report, "rb") - - content = f.readlines() - content = [x.strip() for x in content if x.strip()] - - frame = Wrapper() - plugin = Wrapper() - pyfile = Wrapper() - - frame_c = [] - plugin_c = [] - pyfile_c = [] - - dest = None - - for line in content: - if line == "FRAMESTACK:": - dest = frame_c - continue - elif line == "PLUGIN OBJECT DUMP:": - dest = plugin_c - continue - elif line == "PYFILE OBJECT DUMP:": - dest = pyfile_c - continue - elif line == "CONFIG:": - dest = None - - if dest is not None: - dest.append(line) - - - frame_c = filter(filter_info, frame_c) - plugin_c = filter(filter_info, plugin_c) - pyfile_c = filter(filter_info, pyfile_c) - - frame_c = appendName(frame_c, "frame") - plugin_c = appendName(plugin_c, "plugin") - pyfile_c = appendName(pyfile_c, "pyfile") - - exec("\n".join(frame_c+plugin_c+pyfile_c) ) - - return frame, plugin, pyfile - -if __name__=='__main__': - print "No main method, use this module with your python shell"
\ No newline at end of file diff --git a/module/gui/AccountEdit.py b/module/gui/AccountEdit.py deleted file mode 100644 index b22cfc49f..000000000 --- a/module/gui/AccountEdit.py +++ /dev/null @@ -1,104 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from os.path import join - -class AccountEdit(QWidget): - """ - account editor widget - """ - - def __init__(self): - QMainWindow.__init__(self) - - self.setWindowTitle(_("Edit account")) - self.setWindowIcon(QIcon(join(pypath, "icons","logo.png"))) - - self.setLayout(QGridLayout()) - l = self.layout() - - typeLabel = QLabel(_("Type")) - loginLabel = QLabel(_("Login")) - passwordLabel = QLabel(_("New password")) - changePw = QCheckBox() - changePw.setChecked(False) - self.changePw = changePw - password = QLineEdit() - password.setEnabled(False) - password.setEchoMode(QLineEdit.Password) - self.password = password - login = QLineEdit() - self.login = login - acctype = QComboBox() - self.acctype = acctype - - save = QPushButton(_("Save")) - - self.connect(changePw, SIGNAL("toggled(bool)"), password, SLOT("setEnabled(bool)")) - - l.addWidget(save, 3, 0, 1, 3) - l.addWidget(acctype, 0, 1, 1, 2) - l.addWidget(login, 1, 1, 1, 2) - l.addWidget(password, 2, 2) - l.addWidget(changePw, 2, 1) - l.addWidget(passwordLabel, 2, 0) - l.addWidget(loginLabel, 1, 0) - l.addWidget(typeLabel, 0, 0) - - self.connect(save, SIGNAL("clicked()"), self.slotSave) - - def slotSave(self): - """ - save entered data - """ - data = {"login": str(self.login.text()), "acctype": str(self.acctype.currentText()), "password": False} - if self.changePw.isChecked(): - data["password"] = str(self.password.text()) - self.emit(SIGNAL("done"), data) - - @staticmethod - def newAccount(types): - """ - create empty editor instance - """ - w = AccountEdit() - w.setWindowTitle(_("Create account")) - - w.changePw.setChecked(True) - w.password.setEnabled(True) - - w.acctype.addItems(types) - - return w - - @staticmethod - def editAccount(types, base): - """ - create editor instance with given data - """ - w = AccountEdit() - - w.acctype.addItems(types) - w.acctype.setCurrentIndex(types.index(base["type"])) - - w.login.setText(base["login"]) - - return w diff --git a/module/gui/Accounts.py b/module/gui/Accounts.py deleted file mode 100644 index 8db04dfa9..000000000 --- a/module/gui/Accounts.py +++ /dev/null @@ -1,213 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from time import strftime, gmtime - -class AccountModel(QAbstractItemModel): - """ - model for account view - """ - - def __init__(self, view, connector): - QAbstractItemModel.__init__(self) - self.connector = connector - self.view = view - self._data = [] - self.cols = 4 - self.mutex = QMutex() - - def reloadData(self, force=False): - """ - reload account list - """ - accounts = self.connector.proxy.getAccounts(False) - - if self._data == accounts: - return - - if len(self._data) > 0: - self.beginRemoveRows(QModelIndex(), 0, len(self._data)-1) - self._data = [] - self.endRemoveRows() - - if len(accounts) > 0: - self.beginInsertRows(QModelIndex(), 0, len(accounts)-1) - self._data = accounts - self.endInsertRows() - - def toData(self, index): - """ - return index pointer - """ - return index.internalPointer() - - def data(self, index, role=Qt.DisplayRole): - """ - return cell data - """ - if not index.isValid(): - return QVariant() - if role == Qt.DisplayRole: - if index.column() == 0: - return QVariant(self.toData(index).type) - elif index.column() == 1: - return QVariant(self.toData(index).login) - elif index.column() == 2: - if not self.toData(index).valid: - return QVariant(_("not valid")) - if not self.toData(index).validuntil: - return QVariant(_("n/a")) - until = int(self.toData(index).validuntil) - if until > 0: - fmtime = strftime(_("%a, %d %b %Y %H:%M"), gmtime(until)) - return QVariant(fmtime) - else: - return QVariant(_("unlimited")) - #elif role == Qt.EditRole: - # if index.column() == 0: - # return QVariant(index.internalPointer().data["name"]) - return QVariant() - - def index(self, row, column, parent=QModelIndex()): - """ - create index with data pointer - """ - if parent == QModelIndex() and len(self._data) > row: - pointer = self._data[row] - index = self.createIndex(row, column, pointer) - elif parent.isValid(): - pointer = parent.internalPointer().children[row] - index = self.createIndex(row, column, pointer) - else: - index = QModelIndex() - return index - - def parent(self, index): - """ - no parents, everything on top level - """ - return QModelIndex() - - def rowCount(self, parent=QModelIndex()): - """ - account count - """ - if parent == QModelIndex(): - return len(self._data) - return 0 - - def columnCount(self, parent=QModelIndex()): - return self.cols - - def hasChildren(self, parent=QModelIndex()): - """ - everything on top level - """ - if parent == QModelIndex(): - return True - else: - return False - - def canFetchMore(self, parent): - return False - - def headerData(self, section, orientation, role=Qt.DisplayRole): - """ - returns column heading - """ - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - if section == 0: - return QVariant(_("Type")) - elif section == 1: - return QVariant(_("Login")) - elif section == 2: - return QVariant(_("Valid until")) - elif section == 3: - return QVariant(_("Traffic left")) - return QVariant() - - def flags(self, index): - """ - cell flags - """ - return Qt.ItemIsSelectable | Qt.ItemIsEnabled - -class AccountView(QTreeView): - """ - view component for accounts - """ - - def __init__(self, connector): - QTreeView.__init__(self) - self.setModel(AccountModel(self, connector)) - - self.setColumnWidth(0, 150) - self.setColumnWidth(1, 150) - self.setColumnWidth(2, 150) - self.setColumnWidth(3, 150) - - self.setEditTriggers(QAbstractItemView.NoEditTriggers) - - self.delegate = AccountDelegate(self, self.model()) - self.setItemDelegateForColumn(3, self.delegate) - -class AccountDelegate(QItemDelegate): - """ - used to display a progressbar for the traffic in the traffic cell - """ - - def __init__(self, parent, model): - QItemDelegate.__init__(self, parent) - self.model = model - - def paint(self, painter, option, index): - """ - paint the progressbar - """ - if not index.isValid(): - return - if index.column() == 3: - data = self.model.toData(index) - opts = QStyleOptionProgressBarV2() - opts.minimum = 0 - if data.trafficleft: - if data.trafficleft == -1 or data.trafficleft is None: - opts.maximum = opts.progress = 1 - else: - opts.maximum = opts.progress = data.trafficleft - if data.maxtraffic: - opts.maximum = data.maxtraffic - - opts.rect = option.rect - opts.rect.setRight(option.rect.right()-1) - opts.rect.setHeight(option.rect.height()-1) - opts.textVisible = True - opts.textAlignment = Qt.AlignCenter - if data.trafficleft and data.trafficleft == -1: - opts.text = QString(_("unlimited")) - elif data.trafficleft is None: - opts.text = QString(_("n/a")) - else: - opts.text = QString.number(round(float(opts.progress)/1024/1024, 2)) + " GB" - QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter) - return - QItemDelegate.paint(self, painter, option, index) - diff --git a/module/gui/CNLServer.py b/module/gui/CNLServer.py deleted file mode 100644 index 5ecac8277..000000000 --- a/module/gui/CNLServer.py +++ /dev/null @@ -1,226 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" -import re -from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler -from threading import Thread -from cgi import FieldStorage -from os.path import abspath, dirname, join -from urllib import unquote -from base64 import standard_b64decode -from binascii import unhexlify - -try: - from Crypto.Cipher import AES -except: - pass - -try: - from module.common import JsEngine -except ImportError: - import sys - sys.path.append(join(abspath(dirname(__file__)), "..", "..")) - from module.common import JsEngine - -js = JsEngine.JsEngine() -core = None - -class CNLServer(Thread): - def __init__(self): - Thread.__init__(self) - self.setDaemon(True) - - self.stop = False - self.stopped = False - - def run(self): - server_address = ('127.0.0.1', 9666) - try: - httpd = HTTPServer(server_address, CNLHandler) - except: - self.stopped = True - return - - self.stopped = False - while self.keep_running(): - httpd.handle_request() - self.stopped = True - - def keep_running(self): - return not self.stop - - -class CNLHandler(BaseHTTPRequestHandler): - - #def log_message(self, *args): - # pass - - def add_package(self, name, urls, queue=0): - print "name", name - print "urls", urls - print "queue", queue - - def get_post(self, name, default=""): - if name in self.post: - return self.post[name] - else: - return default - - def start_response(self, string): - - self.send_response(200) - - self.send_header("Content-Length", len(string)) - self.send_header("Content-Language", "de") - self.send_header("Vary", "Accept-Language, Cookie") - self.send_header("Cache-Control", "no-cache, must-revalidate") - self.send_header("Content-type", "text/html") - self.end_headers() - - def do_GET(self): - path = self.path.strip("/").lower() - #self.wfile.write(path+"\n") - - self.map = [ (r"add$", self.add), - (r"addcrypted$", self.addcrypted), - (r"addcrypted2$", self.addcrypted2), - (r"flashgot", self.flashgot), - (r"crossdomain\.xml", self.crossdomain), - (r"checkSupportForUrl", self.checksupport), - (r"jdcheck.js", self.jdcheck), - (r"", self.flash) ] - - func = None - for r, f in self.map: - if re.match(r"(flash(got)?/?)?"+r, path): - func = f - break - - if func: - try: - resp = func() - if not resp: resp = "success" - resp += "\r\n" - self.start_response(resp) - self.wfile.write(resp) - except Exception,e : - self.send_error(500, str(e)) - else: - self.send_error(404, "Not Found") - - def do_POST(self): - form = FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD':'POST', - 'CONTENT_TYPE':self.headers['Content-Type'], - }) - - self.post = {} - for name in form.keys(): - self.post[name] = form[name].value - - return self.do_GET() - - def flash(self): - return "JDownloader" - - def add(self): - package = self.get_post('referer', 'ClickAndLoad Package') - urls = filter(lambda x: x != "", self.get_post('urls').split("\n")) - - self.add_package(package, urls, 0) - - def addcrypted(self): - package = self.get_post('referer', 'ClickAndLoad Package') - dlc = self.get_post('crypted').replace(" ", "+") - - core.upload_container(package, dlc) - - def addcrypted2(self): - package = self.get_post("source", "ClickAndLoad Package") - crypted = self.get_post("crypted") - jk = self.get_post("jk") - - crypted = standard_b64decode(unquote(crypted.replace(" ", "+"))) - jk = "%s f()" % jk - jk = js.eval(jk) - Key = unhexlify(jk) - IV = Key - - obj = AES.new(Key, AES.MODE_CBC, IV) - result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n") - - result = filter(lambda x: x != "", result) - - self.add_package(package, result, 0) - - - def flashgot(self): - autostart = int(self.get_post('autostart', 0)) - package = self.get_post('package', "FlashGot") - urls = filter(lambda x: x != "", self.get_post('urls').split("\n")) - - self.add_package(package, urls, autostart) - - def crossdomain(self): - rep = "<?xml version=\"1.0\"?>\n" - rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" - rep += "<cross-domain-policy>\n" - rep += "<allow-access-from domain=\"*\" />\n" - rep += "</cross-domain-policy>" - return rep - - def checksupport(self): - pass - - def jdcheck(self): - rep = "jdownloader=true;\n" - rep += "var version='10629';\n" - return rep - - -if __name__ == "__main__": - import xmlrpclib - from module import InitHomeDir - from module.ConfigParser import ConfigParser - - config = ConfigParser() - - ssl = "" - if config.get("ssl", "activated"): - ssl = "s" - - server_url = "http%s://%s:%s@%s:%s/" % ( - ssl, - config.username, - config.password, - config.get("remote", "listenaddr"), - config.get("remote", "port") - ) - - core = xmlrpclib.ServerProxy(server_url, allow_none=True) - - s = CNLServer() - s.start() - while not s.stopped: - try: - s.join(1) - except KeyboardInterrupt: - s.stop = True - s.stopped = True - print "quiting.." diff --git a/module/gui/CaptchaDock.py b/module/gui/CaptchaDock.py deleted file mode 100644 index b88cb53ca..000000000 --- a/module/gui/CaptchaDock.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -class CaptchaDock(QDockWidget): - """ - dock widget for captcha input - """ - - def __init__(self): - QDockWidget.__init__(self, _("Captcha")) - self.setObjectName("Captcha Dock") - self.widget = CaptchaDockWidget(self) - self.setWidget(self.widget) - self.setAllowedAreas(Qt.BottomDockWidgetArea) - self.setFeatures(QDockWidget.NoDockWidgetFeatures) - self.hide() - self.processing = False - self.currentID = None - self.connect(self, SIGNAL("setTask"), self.setTask) - - def isFree(self): - return not self.processing - - def setTask(self, tid, img, imgType): - self.processing = True - data = QByteArray(img) - self.currentID = tid - self.widget.emit(SIGNAL("setImage"), data) - self.widget.input.setText("") - self.show() - -class CaptchaDockWidget(QWidget): - """ - widget for the input widgets - """ - - def __init__(self, dock): - QWidget.__init__(self) - self.dock = dock - self.setLayout(QHBoxLayout()) - layout = self.layout() - - imgLabel = QLabel() - captchaInput = QLineEdit() - okayButton = QPushButton(_("OK")) - cancelButton = QPushButton(_("Cancel")) - - layout.addStretch() - layout.addWidget(imgLabel) - layout.addWidget(captchaInput) - layout.addWidget(okayButton) - layout.addWidget(cancelButton) - layout.addStretch() - - self.input = captchaInput - - self.connect(okayButton, SIGNAL("clicked()"), self.slotSubmit) - self.connect(captchaInput, SIGNAL("returnPressed()"), self.slotSubmit) - self.connect(self, SIGNAL("setImage"), self.setImg) - self.connect(self, SIGNAL("setPixmap(const QPixmap &)"), imgLabel, SLOT("setPixmap(const QPixmap &)")) - - def setImg(self, data): - pixmap = QPixmap() - pixmap.loadFromData(data) - self.emit(SIGNAL("setPixmap(const QPixmap &)"), pixmap) - self.input.setFocus(Qt.OtherFocusReason) - - def slotSubmit(self): - text = self.input.text() - tid = self.dock.currentID - self.dock.currentID = None - self.dock.emit(SIGNAL("done"), tid, str(text)) - self.dock.hide() - self.dock.processing = False - diff --git a/module/gui/Collector.py b/module/gui/Collector.py deleted file mode 100644 index 3ec4262f1..000000000 --- a/module/gui/Collector.py +++ /dev/null @@ -1,407 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from module.PyFile import statusMap -from module.utils import formatSize - -from module.remote.thriftbackend.ThriftClient import Destination, FileDoesNotExists, ElementType - -statusMapReverse = dict((v,k) for k, v in statusMap.iteritems()) - -translatedStatusMap = {} # -> CollectorModel.__init__ - -class CollectorModel(QAbstractItemModel): - """ - model for the collector view - """ - - def __init__(self, view, connector): - QAbstractItemModel.__init__(self) - self.connector = connector - self.view = view - self._data = [] - self.cols = 4 - self.interval = 1 - self.mutex = QMutex() - - global translatedStatusMap # workaround because i18n is not running at import time - translatedStatusMap = { - "finished": _("finished"), - "offline": _("offline"), - "online": _("online"), - "queued": _("queued"), - "skipped": _("skipped"), - "waiting": _("waiting"), - "temp. offline": _("temp. offline"), - "starting": _("starting"), - "failed": _("failed"), - "aborted": _("aborted"), - "decrypting": _("decrypting"), - "custom": _("custom"), - "downloading": _("downloading"), - "processing": _("processing") - } - - def translateStatus(self, string): - """ - used to convert to locale specific status - """ - return translatedStatusMap[string] - - def addEvent(self, event): - """ - called from main loop, pass events to the correct methods - """ - QMutexLocker(self.mutex) - if event.eventname == "reload": - self.fullReload() - elif event.eventname == "remove": - self.removeEvent(event) - elif event.eventname == "insert": - self.insertEvent(event) - elif event.eventname == "update": - self.updateEvent(event) - - def fullReload(self): - """ - reload whole model, used at startup to load initial data - """ - self._data = [] - order = self.connector.getPackageOrder(Destination.Collector) - self.beginInsertRows(QModelIndex(), 0, len(order.values())) - for position, pid in order.iteritems(): - pack = self.connector.getPackageData(pid) - package = Package(pack) - self._data.append(package) - self._data = sorted(self._data, key=lambda p: p.data["order"]) - self.endInsertRows() - - def removeEvent(self, event): - """ - remove an element from model - """ - if event.type == ElementType.File: - for p, package in enumerate(self._data): - for k, child in enumerate(package.children): - if child.id == event.id: - self.beginRemoveRows(self.index(p, 0), k, k) - del package.children[k] - self.endRemoveRows() - break - else: - for k, package in enumerate(self._data): - if package.id == event.id: - self.beginRemoveRows(QModelIndex(), k, k) - del self._data[k] - self.endRemoveRows() - break - - def insertEvent(self, event): - """ - inserts a new element in the model - """ - if event.type == ElementType.File: - try: - info = self.connector.getFileData(event.id) - except FileDoesNotExists: - return - - for k, package in enumerate(self._data): - if package.id == info.package: - if package.getChild(info.fid): - self.updateEvent(event) - break - self.beginInsertRows(self.index(k, 0), info.order, info.order) - package.addChild(info) - self.endInsertRows() - break - else: - data = self.connector.getPackageData(event.id) - package = Package(data) - self.beginInsertRows(QModelIndex(), data.order, data.order) - self._data.insert(data.order, package) - self.endInsertRows() - - def updateEvent(self, event): - """ - update an element in the model - """ - if event.type == ElementType.File: - try: - info = self.connector.proxy.getFileData(event.id) - except FileDoesNotExists: - return - for p, package in enumerate(self._data): - if package.id == info.packageID: - for k, child in enumerate(package.children): - if child.id == event.id: - child.update(info) - if not info.status == 12: - child.data["downloading"] = None - self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols))) - break - else: - data = self.connector.getPackageData(event.id) - if not data: - return - for p, package in enumerate(self._data): - if package.id == event.id: - package.update(data) - self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(p, 0), self.index(p, self.cols)) - break - - def data(self, index, role=Qt.DisplayRole): - """ - return cell data - """ - if not index.isValid(): - return QVariant() - if role == Qt.DisplayRole: - if index.column() == 0: - return QVariant(index.internalPointer().data["name"]) - elif index.column() == 1: - item = index.internalPointer() - plugins = [] - if isinstance(item, Package): - for child in item.children: - if not child.data["plugin"] in plugins: - plugins.append(child.data["plugin"]) - else: - plugins.append(item.data["plugin"]) - return QVariant(", ".join(plugins)) - elif index.column() == 2: - item = index.internalPointer() - status = 0 - if isinstance(item, Package): - for child in item.children: - if child.data["status"] > status: - status = child.data["status"] - else: - status = item.data["status"] - return QVariant(self.translateStatus(statusMapReverse[status])) - elif index.column() == 3: - item = index.internalPointer() - if isinstance(item, Link): - return QVariant(formatSize(item.data["size"])) - else: - ms = 0 - for c in item.children: - ms += c.data["size"] - return QVariant(formatSize(ms)) - elif role == Qt.EditRole: - if index.column() == 0: - return QVariant(index.internalPointer().data["name"]) - return QVariant() - - def index(self, row, column, parent=QModelIndex()): - """ - creates a cell index with pointer to the data - """ - if parent == QModelIndex() and len(self._data) > row: - pointer = self._data[row] - index = self.createIndex(row, column, pointer) - elif parent.isValid(): - try: - pointer = parent.internalPointer().children[row] - except: - return QModelIndex() - index = self.createIndex(row, column, pointer) - else: - index = QModelIndex() - return index - - def parent(self, index): - """ - return index of parent element - only valid for links - """ - if index == QModelIndex(): - return QModelIndex() - if index.isValid(): - link = index.internalPointer() - if isinstance(link, Link): - for k, pack in enumerate(self._data): - if pack == link.package: - return self.createIndex(k, 0, link.package) - return QModelIndex() - - def rowCount(self, parent=QModelIndex()): - """ - returns row count for the element - """ - if parent == QModelIndex(): - #return package count - return len(self._data) - else: - if parent.isValid(): - #index is valid - pack = parent.internalPointer() - if isinstance(pack, Package): - #index points to a package - #return len of children - return len(pack.children) - else: - #index is invalid - return False - #files have no children - return 0 - - def columnCount(self, parent=QModelIndex()): - return self.cols - - def hasChildren(self, parent=QModelIndex()): - if not parent.isValid(): - return True - return self.rowCount(parent) > 0 - - def canFetchMore(self, parent): - return False - - def headerData(self, section, orientation, role=Qt.DisplayRole): - """ - returns column heading - """ - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - if section == 0: - return QVariant(_("Name")) - elif section == 1: - return QVariant(_("Plugin")) - elif section == 2: - return QVariant(_("Status")) - elif section == 3: - return QVariant(_("Size")) - return QVariant() - - def flags(self, index): - """ - cell flags - """ - if index.column() == 0 and self.parent(index) == QModelIndex(): - return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled - return Qt.ItemIsSelectable | Qt.ItemIsEnabled - - def setData(self, index, value, role=Qt.EditRole): - """ - called if package name editing is finished, sets new name - """ - if index.column() == 0 and self.parent(index) == QModelIndex() and role == Qt.EditRole: - self.connector.setPackageName(index.internalPointer().id, str(value.toString())) - return True - -class Package(object): - """ - package object in the model - """ - - def __init__(self, pack): - self.id = pack.pid - self.children = [] - for f in pack.links: - self.addChild(f) - self.data = {} - self.update(pack) - - def update(self, pack): - """ - update data dict from thift object - """ - data = { - "name": pack.name, - "folder": pack.folder, - "site": pack.site, - "password": pack.password, - "order": pack.order, - } - self.data.update(data) - - def addChild(self, f): - """ - add child (Link) to package - """ - self.children.insert(f.order, Link(f, self)) - self.children = sorted(self.children, key=lambda l: l.data["order"]) - - def getChild(self, fid): - """ - get child from package - """ - for child in self.children: - if child.id == int(fid): - return child - return None - - def getChildKey(self, fid): - """ - get child index - """ - for k, child in enumerate(self.children): - if child.id == int(fid): - return k - return None - - def removeChild(self, fid): - """ - remove child - """ - for k, child in enumerate(self.children): - if child.id == int(fid): - del self.children[k] - -class Link(object): - def __init__(self, f, pack): - self.data = {"downloading": None} - self.update(f) - self.id = f.fid - self.package = pack - - def update(self, f): - """ - update data dict from thift object - """ - data = { - "url": f.url, - "name": f.name, - "plugin": f.plugin, - "size": f.size, - "format_size": f.format_size, - "status": f.status, - "statusmsg": f.statusmsg, - "package": f.packageID, - "error": f.error, - "order": f.order, - } - self.data.update(data) - -class CollectorView(QTreeView): - """ - view component for collector - """ - - def __init__(self, connector): - QTreeView.__init__(self) - self.setModel(CollectorModel(self, connector)) - self.setColumnWidth(0, 500) - self.setColumnWidth(1, 100) - self.setColumnWidth(2, 200) - self.setColumnWidth(3, 100) - - self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed) - diff --git a/module/gui/ConnectionManager.py b/module/gui/ConnectionManager.py deleted file mode 100644 index def575abc..000000000 --- a/module/gui/ConnectionManager.py +++ /dev/null @@ -1,302 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from os.path import join - -from uuid import uuid4 as uuid - -class ConnectionManager(QWidget): - - - warningShown = False - - def __init__(self): - QWidget.__init__(self) - - if not self.warningShown: - QMessageBox.warning(self, 'Warning', - "We are sorry but the GUI is not stable yet. Please use the webinterface for much better experience. \n", QMessageBox.Ok) - ConnectionManager.warningShown = True - - mainLayout = QHBoxLayout() - buttonLayout = QVBoxLayout() - - self.setWindowTitle(_("pyLoad ConnectionManager")) - self.setWindowIcon(QIcon(join(pypath, "icons","logo.png"))) - - connList = QListWidget() - - new = QPushButton(_("New")) - edit = QPushButton(_("Edit")) - remove = QPushButton(_("Remove")) - connect = QPushButton(_("Connect")) - - #box = QFrame() - boxLayout = QVBoxLayout() - #box.setLayout(boxLayout) - - boxLayout.addWidget(QLabel(_("Connect:"))) - boxLayout.addWidget(connList) - - line = QFrame() - #line.setFixedWidth(100) - line.setFrameShape(line.HLine) - line.setFrameShadow(line.Sunken) - line.setFixedHeight(10) - - boxLayout.addWidget(line) - - form = QFormLayout() - form.setMargin(5) - form.setSpacing(20) - - form.setAlignment(Qt.AlignRight) - checkbox = QCheckBox() - form.addRow(_("Use internal Core:"), checkbox) - - boxLayout.addLayout(form) - - mainLayout.addLayout(boxLayout) - mainLayout.addLayout(buttonLayout) - - buttonLayout.addWidget(new) - buttonLayout.addWidget(edit) - buttonLayout.addWidget(remove) - buttonLayout.addStretch() - buttonLayout.addWidget(connect) - - self.setLayout(mainLayout) - - self.internal = checkbox - self.new = new - self.connectb = connect - self.remove = remove - self.editb = edit - self.connList = connList - self.edit = self.EditWindow() - self.connectSignals() - - self.defaultStates = {} - - def connectSignals(self): - self.connect(self, SIGNAL("setConnections"), self.setConnections) - self.connect(self.new, SIGNAL("clicked()"), self.slotNew) - self.connect(self.editb, SIGNAL("clicked()"), self.slotEdit) - self.connect(self.remove, SIGNAL("clicked()"), self.slotRemove) - self.connect(self.connectb, SIGNAL("clicked()"), self.slotConnect) - self.connect(self.edit, SIGNAL("save"), self.slotSave) - self.connect(self.connList, SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.slotItemDoubleClicked) - self.connect(self.internal, SIGNAL("clicked()"), self.slotInternal) - - def setConnections(self, connections): - self.connList.clear() - for conn in connections: - item = QListWidgetItem() - item.setData(Qt.DisplayRole, QVariant(conn["name"])) - item.setData(Qt.UserRole, QVariant(conn)) - self.connList.addItem(item) - if conn["default"]: - item.setData(Qt.DisplayRole, QVariant(_("%s (Default)") % conn["name"])) - self.connList.setCurrentItem(item) - - def slotNew(self): - data = {"id":uuid().hex, "type":"remote", "default":False, "name":"", "host":"", "port":"7228", "user":"admin", "password":""} - self.edit.setData(data) - self.edit.show() - - def slotEdit(self): - item = self.connList.currentItem() - data = item.data(Qt.UserRole).toPyObject() - data = self.cleanDict(data) - self.edit.setData(data) - self.edit.show() - - def slotRemove(self): - item = self.connList.currentItem() - data = item.data(Qt.UserRole).toPyObject() - data = self.cleanDict(data) - self.emit(SIGNAL("removeConnection"), data) - - def slotConnect(self): - if self.internal.checkState() == 2: - data = {"type": "internal"} - self.emit(SIGNAL("connect"), data) - else: - item = self.connList.currentItem() - data = item.data(Qt.UserRole).toPyObject() - data = self.cleanDict(data) - self.emit(SIGNAL("connect"), data) - - def cleanDict(self, data): - tmp = {} - for k, d in data.items(): - tmp[str(k)] = d - return tmp - - def slotSave(self, data): - self.emit(SIGNAL("saveConnection"), data) - - def slotItemDoubleClicked(self, defaultItem): - data = defaultItem.data(Qt.UserRole).toPyObject() - self.setDefault(data, True) - did = self.cleanDict(data)["id"] - #allItems = self.connList.findItems("*", Qt.MatchWildcard) - count = self.connList.count() - for i in range(count): - item = self.connList.item(i) - data = item.data(Qt.UserRole).toPyObject() - if self.cleanDict(data)["id"] == did: - continue - self.setDefault(data, False) - - def slotInternal(self): - if self.internal.checkState() == 2: - self.connList.clearSelection() - - def setDefault(self, data, state): - data = self.cleanDict(data) - self.edit.setData(data) - data = self.edit.getData() - data["default"] = state - self.edit.emit(SIGNAL("save"), data) - - class EditWindow(QWidget): - def __init__(self): - QWidget.__init__(self) - - self.setWindowTitle(_("pyLoad ConnectionManager")) - self.setWindowIcon(QIcon(join(pypath, "icons","logo.png"))) - - grid = QGridLayout() - - nameLabel = QLabel(_("Name:")) - hostLabel = QLabel(_("Host:")) - localLabel = QLabel(_("Local:")) - userLabel = QLabel(_("User:")) - pwLabel = QLabel(_("Password:")) - portLabel = QLabel(_("Port:")) - - name = QLineEdit() - host = QLineEdit() - local = QCheckBox() - user = QLineEdit() - password = QLineEdit() - password.setEchoMode(QLineEdit.Password) - port = QSpinBox() - port.setRange(1,10000) - - save = QPushButton(_("Save")) - cancel = QPushButton(_("Cancel")) - - grid.addWidget(nameLabel, 0, 0) - grid.addWidget(name, 0, 1) - grid.addWidget(localLabel, 1, 0) - grid.addWidget(local, 1, 1) - grid.addWidget(hostLabel, 2, 0) - grid.addWidget(host, 2, 1) - grid.addWidget(portLabel, 3, 0) - grid.addWidget(port, 3, 1) - grid.addWidget(userLabel, 4, 0) - grid.addWidget(user, 4, 1) - grid.addWidget(pwLabel, 5, 0) - grid.addWidget(password, 5, 1) - grid.addWidget(cancel, 6, 0) - grid.addWidget(save, 6, 1) - - self.setLayout(grid) - self.controls = {"name": name, - "host": host, - "local": local, - "user": user, - "password": password, - "port": port, - "save": save, - "cancel": cancel} - - self.connect(cancel, SIGNAL("clicked()"), self.hide) - self.connect(save, SIGNAL("clicked()"), self.slotDone) - self.connect(local, SIGNAL("stateChanged(int)"), self.slotLocalChanged) - - self.id = None - self.default = None - - def setData(self, data): - if not data: return - - self.id = data["id"] - self.default = data["default"] - self.controls["name"].setText(data["name"]) - if data["type"] == "local": - data["local"] = True - else: - data["local"] = False - self.controls["local"].setChecked(data["local"]) - if not data["local"]: - self.controls["user"].setText(data["user"]) - self.controls["password"].setText(data["password"]) - self.controls["port"].setValue(int(data["port"])) - self.controls["host"].setText(data["host"]) - self.controls["user"].setDisabled(False) - self.controls["password"].setDisabled(False) - self.controls["port"].setDisabled(False) - self.controls["host"].setDisabled(False) - else: - self.controls["user"].setText("") - self.controls["port"].setValue(1) - self.controls["host"].setText("") - self.controls["user"].setDisabled(True) - self.controls["password"].setDisabled(True) - self.controls["port"].setDisabled(True) - self.controls["host"].setDisabled(True) - - def slotLocalChanged(self, val): - if val == 2: - self.controls["user"].setDisabled(True) - self.controls["password"].setDisabled(True) - self.controls["port"].setDisabled(True) - self.controls["host"].setDisabled(True) - elif val == 0: - self.controls["user"].setDisabled(False) - self.controls["password"].setDisabled(False) - self.controls["port"].setDisabled(False) - self.controls["host"].setDisabled(False) - - def getData(self): - d = {} - d["id"] = self.id - d["default"] = self.default - d["name"] = self.controls["name"].text() - d["local"] = self.controls["local"].isChecked() - d["user"] = self.controls["user"].text() - d["password"] = self.controls["password"].text() - d["host"] = self.controls["host"].text() - d["port"] = self.controls["port"].value() - if d["local"]: - d["type"] = "local" - else: - d["type"] = "remote" - return d - - def slotDone(self): - data = self.getData() - self.hide() - self.emit(SIGNAL("save"), data) - diff --git a/module/gui/CoreConfigParser.py b/module/gui/CoreConfigParser.py deleted file mode 100644 index 0d1d298c6..000000000 --- a/module/gui/CoreConfigParser.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import with_statement -from os.path import exists -from os.path import join - - -CONF_VERSION = 1 - -######################################################################## -class ConfigParser: - - #---------------------------------------------------------------------- - def __init__(self, configdir): - """Constructor""" - self.configdir = configdir - self.config = {} - - if self.checkVersion(): - self.readConfig() - - #---------------------------------------------------------------------- - def checkVersion(self): - - if not exists(join(self.configdir, "pyload.conf")): - return False - f = open(join(self.configdir, "pyload.conf"), "rb") - v = f.readline() - f.close() - v = v[v.find(":")+1:].strip() - - if int(v) < CONF_VERSION: - return False - - return True - - #---------------------------------------------------------------------- - def readConfig(self): - """reads the config file""" - - self.config = self.parseConfig(join(self.configdir, "pyload.conf")) - - - #---------------------------------------------------------------------- - def parseConfig(self, config): - """parses a given configfile""" - - f = open(config) - - config = f.read() - - config = config.split("\n")[1:] - - conf = {} - - section, option, value, typ, desc = "","","","","" - - listmode = False - - for line in config: - - line = line.rpartition("#") # removes comments - - if line[1]: - line = line[0] - else: - line = line[2] - - line = line.strip() - - try: - - if line == "": - continue - elif line.endswith(":"): - section, none, desc = line[:-1].partition('-') - section = section.strip() - desc = desc.replace('"', "").strip() - conf[section] = { "desc" : desc } - else: - if listmode: - - if line.endswith("]"): - listmode = False - line = line.replace("]","") - - value += [self.cast(typ, x.strip()) for x in line.split(",") if x] - - if not listmode: - conf[section][option] = { "desc" : desc, - "type" : typ, - "value" : value} - - - else: - content, none, value = line.partition("=") - - content, none, desc = content.partition(":") - - desc = desc.replace('"', "").strip() - - typ, option = content.split() - - value = value.strip() - - if value.startswith("["): - if value.endswith("]"): - listmode = False - value = value[:-1] - else: - listmode = True - - value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x] - else: - value = self.cast(typ, value) - - if not listmode: - conf[section][option] = { "desc" : desc, - "type" : typ, - "value" : value} - - except: - pass - - - f.close() - return conf - - #---------------------------------------------------------------------- - def cast(self, typ, value): - """cast value to given format""" - if type(value) not in (str, unicode): - return value - - if typ == "int": - return int(value) - elif typ == "bool": - return True if value.lower() in ("1","true", "on", "an","yes") else False - else: - return value - - #---------------------------------------------------------------------- - def get(self, section, option): - """get value""" - return self.config[section][option]["value"] - - #---------------------------------------------------------------------- - def __getitem__(self, section): - """provides dictonary like access: c['section']['option']""" - return Section(self, section) - -######################################################################## -class Section: - """provides dictionary like access for configparser""" - - #---------------------------------------------------------------------- - def __init__(self, parser, section): - """Constructor""" - self.parser = parser - self.section = section - - #---------------------------------------------------------------------- - def __getitem__(self, item): - """getitem""" - return self.parser.get(self.section, item) diff --git a/module/gui/LinkDock.py b/module/gui/LinkDock.py deleted file mode 100644 index ac2d4aae5..000000000 --- a/module/gui/LinkDock.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -class NewLinkDock(QDockWidget): - def __init__(self): - QDockWidget.__init__(self, "New Links") - self.setObjectName("New Links Dock") - self.widget = NewLinkWindow(self) - self.setWidget(self.widget) - self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea) - self.hide() - - def slotDone(self): - text = str(self.widget.box.toPlainText()) - lines = text.splitlines() - self.emit(SIGNAL("done"), lines) - self.widget.box.clear() - self.hide() - -class NewLinkWindow(QWidget): - def __init__(self, dock): - QWidget.__init__(self) - self.dock = dock - self.setLayout(QVBoxLayout()) - layout = self.layout() - - explanationLabel = QLabel("Select a package and then click Add button.") - boxLabel = QLabel("Paste URLs here:") - self.box = QTextEdit() - - save = QPushButton("Add") - - layout.addWidget(explanationLabel) - layout.addWidget(boxLabel) - layout.addWidget(self.box) - layout.addWidget(save) - - self.connect(save, SIGNAL("clicked()"), self.dock.slotDone) diff --git a/module/gui/MainWindow.py b/module/gui/MainWindow.py deleted file mode 100644 index c71112e9b..000000000 --- a/module/gui/MainWindow.py +++ /dev/null @@ -1,697 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from os.path import join - -from module.gui.PackageDock import * -from module.gui.LinkDock import * -from module.gui.CaptchaDock import CaptchaDock -from module.gui.SettingsWidget import SettingsWidget - -from module.gui.Collector import CollectorView, Package, Link -from module.gui.Queue import QueueView -from module.gui.Overview import OverviewView -from module.gui.Accounts import AccountView -from module.gui.AccountEdit import AccountEdit - -from module.remote.thriftbackend.ThriftClient import AccountInfo - -class MainWindow(QMainWindow): - def __init__(self, connector): - """ - set up main window - """ - QMainWindow.__init__(self) - #window stuff - self.setWindowTitle(_("pyLoad Client")) - self.setWindowIcon(QIcon(join(pypath, "icons","logo.png"))) - self.resize(1000,600) - - #layout version - self.version = 3 - - #init docks - self.newPackDock = NewPackageDock() - self.addDockWidget(Qt.RightDockWidgetArea, self.newPackDock) - self.connect(self.newPackDock, SIGNAL("done"), self.slotAddPackage) - self.captchaDock = CaptchaDock() - self.addDockWidget(Qt.BottomDockWidgetArea, self.captchaDock) - self.newLinkDock = NewLinkDock() - self.addDockWidget(Qt.RightDockWidgetArea, self.newLinkDock) - self.connect(self.newLinkDock, SIGNAL("done"), self.slotAddLinksToPackage) - - #central widget, layout - self.masterlayout = QVBoxLayout() - lw = QWidget() - lw.setLayout(self.masterlayout) - self.setCentralWidget(lw) - - #status - self.statusw = QFrame() - self.statusw.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) - self.statusw.setLineWidth(2) - self.statusw.setLayout(QGridLayout()) - #palette = self.statusw.palette() - #palette.setColor(QPalette.Window, QColor(255, 255, 255)) - #self.statusw.setPalette(palette) - #self.statusw.setAutoFillBackground(True) - l = self.statusw.layout() - - class BoldLabel(QLabel): - def __init__(self, text): - QLabel.__init__(self, text) - f = self.font() - f.setBold(True) - self.setFont(f) - self.setAlignment(Qt.AlignRight) - - class Seperator(QFrame): - def __init__(self): - QFrame.__init__(self) - self.setFrameShape(QFrame.VLine) - self.setFrameShadow(QFrame.Sunken) - - l.addWidget(BoldLabel(_("Packages:")), 0, 0) - self.packageCount = QLabel("0") - l.addWidget(self.packageCount, 0, 1) - - l.addWidget(BoldLabel(_("Files:")), 0, 2) - self.fileCount = QLabel("0") - l.addWidget(self.fileCount, 0, 3) - - l.addWidget(BoldLabel(_("Status:")), 0, 4) - self.status = QLabel("running") - l.addWidget(self.status, 0, 5) - - l.addWidget(BoldLabel(_("Space:")), 0, 6) - self.space = QLabel("") - l.addWidget(self.space, 0, 7) - - l.addWidget(BoldLabel(_("Speed:")), 0, 8) - self.speed = QLabel("") - l.addWidget(self.speed, 0, 9) - - #l.addWidget(BoldLabel(_("Max. downloads:")), 0, 9) - #l.addWidget(BoldLabel(_("Max. chunks:")), 1, 9) - #self.maxDownloads = QSpinBox() - #self.maxDownloads.setEnabled(False) - #self.maxChunks = QSpinBox() - #self.maxChunks.setEnabled(False) - #l.addWidget(self.maxDownloads, 0, 10) - #l.addWidget(self.maxChunks, 1, 10) - - #set menubar and statusbar - self.menubar = self.menuBar() - #self.statusbar = self.statusBar() - #self.connect(self.statusbar, SIGNAL("showMsg"), self.statusbar.showMessage) - #self.serverStatus = QLabel(_("Status: Not Connected")) - #self.statusbar.addPermanentWidget(self.serverStatus) - - #menu - self.menus = {"file": self.menubar.addMenu(_("File")), - "connections": self.menubar.addMenu(_("Connections"))} - - #menu actions - self.mactions = {"exit": QAction(_("Exit"), self.menus["file"]), - "manager": QAction(_("Connection manager"), self.menus["connections"])} - - #add menu actions - self.menus["file"].addAction(self.mactions["exit"]) - self.menus["connections"].addAction(self.mactions["manager"]) - - #toolbar - self.actions = {} - self.init_toolbar() - - #tabs - self.tabw = QTabWidget() - self.tabs = {"overview": {"w": QWidget()}, - "queue": {"w": QWidget()}, - "collector": {"w": QWidget()}, - "accounts": {"w": QWidget()}, - "settings": {}} - #self.tabs["settings"]["s"] = QScrollArea() - self.tabs["settings"]["w"] = SettingsWidget() - #self.tabs["settings"]["s"].setWidgetResizable(True) - #self.tabs["settings"]["s"].setWidget(self.tabs["settings"]["w"]) - self.tabs["log"] = {"w":QWidget()} - self.tabw.addTab(self.tabs["overview"]["w"], _("Overview")) - self.tabw.addTab(self.tabs["queue"]["w"], _("Queue")) - self.tabw.addTab(self.tabs["collector"]["w"], _("Collector")) - self.tabw.addTab(self.tabs["accounts"]["w"], _("Accounts")) - self.tabw.addTab(self.tabs["settings"]["w"], _("Settings")) - self.tabw.addTab(self.tabs["log"]["w"], _("Log")) - - #init tabs - self.init_tabs(connector) - - #context menus - self.init_context() - - #layout - self.masterlayout.addWidget(self.tabw) - self.masterlayout.addWidget(self.statusw) - - #signals.. - self.connect(self.mactions["manager"], SIGNAL("triggered()"), self.slotShowConnector) - - self.connect(self.tabs["queue"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotQueueContextMenu) - self.connect(self.tabs["collector"]["package_view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotCollectorContextMenu) - self.connect(self.tabs["accounts"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotAccountContextMenu) - - self.connect(self.tabw, SIGNAL("currentChanged(int)"), self.slotTabChanged) - - self.lastAddedID = None - - self.connector = connector - - def init_toolbar(self): - """ - create toolbar - """ - self.toolbar = self.addToolBar(_("Hide Toolbar")) - self.toolbar.setObjectName("Main Toolbar") - self.toolbar.setIconSize(QSize(30,30)) - self.toolbar.setMovable(False) - self.actions["toggle_status"] = self.toolbar.addAction(_("Toggle Pause/Resume")) - pricon = QIcon() - pricon.addFile(join(pypath, "icons","toolbar_start.png"), QSize(), QIcon.Normal, QIcon.Off) - pricon.addFile(join(pypath, "icons","toolbar_pause.png"), QSize(), QIcon.Normal, QIcon.On) - self.actions["toggle_status"].setIcon(pricon) - self.actions["toggle_status"].setCheckable(True) - self.actions["status_stop"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_stop.png")), _("Stop")) - self.toolbar.addSeparator() - self.actions["add"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_add.png")), _("Add")) - self.toolbar.addSeparator() - self.actions["clipboard"] = self.toolbar.addAction(QIcon(join(pypath, "icons","clipboard.png")), _("Check Clipboard")) - self.actions["clipboard"].setCheckable(True) - - self.connect(self.actions["toggle_status"], SIGNAL("toggled(bool)"), self.slotToggleStatus) - self.connect(self.actions["clipboard"], SIGNAL("toggled(bool)"), self.slotToggleClipboard) - self.connect(self.actions["status_stop"], SIGNAL("triggered()"), self.slotStatusStop) - self.addMenu = QMenu() - packageAction = self.addMenu.addAction(_("Package")) - containerAction = self.addMenu.addAction(_("Container")) - accountAction = self.addMenu.addAction(_("Account")) - linksAction = self.addMenu.addAction(_("Links")) - self.connect(self.actions["add"], SIGNAL("triggered()"), self.slotAdd) - self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage) - self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer) - self.connect(accountAction, SIGNAL("triggered()"), self.slotNewAccount) - self.connect(linksAction, SIGNAL("triggered()"), self.slotShowAddLinks) - - def init_tabs(self, connector): - """ - create tabs - """ - #overview - self.tabs["overview"]["l"] = QGridLayout() - self.tabs["overview"]["w"].setLayout(self.tabs["overview"]["l"]) - self.tabs["overview"]["view"] = OverviewView(connector) - self.tabs["overview"]["l"].addWidget(self.tabs["overview"]["view"]) - - #queue - self.tabs["queue"]["l"] = QGridLayout() - self.tabs["queue"]["w"].setLayout(self.tabs["queue"]["l"]) - self.tabs["queue"]["view"] = QueueView(connector) - self.tabs["queue"]["l"].addWidget(self.tabs["queue"]["view"]) - - #collector - toQueue = QPushButton(_("Push selected packages to queue")) - self.tabs["collector"]["l"] = QGridLayout() - self.tabs["collector"]["w"].setLayout(self.tabs["collector"]["l"]) - self.tabs["collector"]["package_view"] = CollectorView(connector) - self.tabs["collector"]["l"].addWidget(self.tabs["collector"]["package_view"], 0, 0) - self.tabs["collector"]["l"].addWidget(toQueue, 1, 0) - self.connect(toQueue, SIGNAL("clicked()"), self.slotPushPackageToQueue) - self.tabs["collector"]["package_view"].setContextMenuPolicy(Qt.CustomContextMenu) - self.tabs["queue"]["view"].setContextMenuPolicy(Qt.CustomContextMenu) - - #log - self.tabs["log"]["l"] = QGridLayout() - self.tabs["log"]["w"].setLayout(self.tabs["log"]["l"]) - self.tabs["log"]["text"] = QTextEdit() - self.tabs["log"]["text"].logOffset = 0 - self.tabs["log"]["text"].setReadOnly(True) - self.connect(self.tabs["log"]["text"], SIGNAL("append(QString)"), self.tabs["log"]["text"].append) - self.tabs["log"]["l"].addWidget(self.tabs["log"]["text"]) - - #accounts - self.tabs["accounts"]["view"] = AccountView(connector) - self.tabs["accounts"]["w"].setLayout(QVBoxLayout()) - self.tabs["accounts"]["w"].layout().addWidget(self.tabs["accounts"]["view"]) - newbutton = QPushButton(_("New Account")) - self.tabs["accounts"]["w"].layout().addWidget(newbutton) - self.connect(newbutton, SIGNAL("clicked()"), self.slotNewAccount) - self.tabs["accounts"]["view"].setContextMenuPolicy(Qt.CustomContextMenu) - - def init_context(self): - """ - create context menus - """ - self.activeMenu = None - #queue - self.queueContext = QMenu() - self.queueContext.buttons = {} - self.queueContext.item = (None, None) - self.queueContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.queueContext) - self.queueContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.queueContext) - self.queueContext.buttons["pull"] = QAction(QIcon(join(pypath, "icons","pull_small.png")), _("Pull out"), self.queueContext) - self.queueContext.buttons["abort"] = QAction(QIcon(join(pypath, "icons","abort.png")), _("Abort"), self.queueContext) - self.queueContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.queueContext) - self.queueContext.addAction(self.queueContext.buttons["pull"]) - self.queueContext.addAction(self.queueContext.buttons["edit"]) - self.queueContext.addAction(self.queueContext.buttons["remove"]) - self.queueContext.addAction(self.queueContext.buttons["restart"]) - self.queueContext.addAction(self.queueContext.buttons["abort"]) - self.connect(self.queueContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload) - self.connect(self.queueContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload) - self.connect(self.queueContext.buttons["pull"], SIGNAL("triggered()"), self.slotPullOutPackage) - self.connect(self.queueContext.buttons["abort"], SIGNAL("triggered()"), self.slotAbortDownload) - self.connect(self.queueContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage) - - #collector - self.collectorContext = QMenu() - self.collectorContext.buttons = {} - self.collectorContext.item = (None, None) - self.collectorContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.collectorContext) - self.collectorContext.buttons["push"] = QAction(QIcon(join(pypath, "icons","push_small.png")), _("Push to queue"), self.collectorContext) - self.collectorContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.collectorContext) - self.collectorContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.collectorContext) - self.collectorContext.buttons["refresh"] = QAction(QIcon(join(pypath, "icons","refresh1_small.png")),_("Refresh Status"), self.collectorContext) - self.collectorContext.addAction(self.collectorContext.buttons["push"]) - self.collectorContext.addSeparator() - self.collectorContext.buttons["add"] = self.collectorContext.addMenu(QIcon(join(pypath, "icons","add_small.png")), _("Add")) - self.collectorContext.addAction(self.collectorContext.buttons["edit"]) - self.collectorContext.addAction(self.collectorContext.buttons["remove"]) - self.collectorContext.addAction(self.collectorContext.buttons["restart"]) - self.collectorContext.addSeparator() - self.collectorContext.addAction(self.collectorContext.buttons["refresh"]) - packageAction = self.collectorContext.buttons["add"].addAction(_("Package")) - containerAction = self.collectorContext.buttons["add"].addAction(_("Container")) - linkAction = self.collectorContext.buttons["add"].addAction(_("Links")) - self.connect(self.collectorContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload) - self.connect(self.collectorContext.buttons["push"], SIGNAL("triggered()"), self.slotPushPackageToQueue) - self.connect(self.collectorContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage) - self.connect(self.collectorContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload) - self.connect(self.collectorContext.buttons["refresh"], SIGNAL("triggered()"), self.slotRefreshPackage) - self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage) - self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer) - self.connect(linkAction, SIGNAL("triggered()"), self.slotShowAddLinks) - - self.accountContext = QMenu() - self.accountContext.buttons = {} - self.accountContext.buttons["add"] = QAction(QIcon(join(pypath, "icons","add_small.png")), _("Add"), self.accountContext) - self.accountContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.accountContext) - self.accountContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit"), self.accountContext) - self.accountContext.addAction(self.accountContext.buttons["add"]) - self.accountContext.addAction(self.accountContext.buttons["edit"]) - self.accountContext.addAction(self.accountContext.buttons["remove"]) - self.connect(self.accountContext.buttons["add"], SIGNAL("triggered()"), self.slotNewAccount) - self.connect(self.accountContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditAccount) - self.connect(self.accountContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveAccount) - - def slotToggleStatus(self, status): - """ - pause/start toggle (toolbar) - """ - self.emit(SIGNAL("setDownloadStatus"), status) - - def slotStatusStop(self): - """ - stop button (toolbar) - """ - self.emit(SIGNAL("stopAllDownloads")) - - def slotAdd(self): - """ - add button (toolbar) - show context menu (choice: links/package) - """ - self.addMenu.exec_(QCursor.pos()) - - def slotShowAddPackage(self): - """ - action from add-menu - show new-package dock - """ - self.tabw.setCurrentIndex(1) - self.newPackDock.show() - - def slotShowAddLinks(self): - """ - action from add-menu - show new-links dock - """ - self.tabw.setCurrentIndex(1) - self.newLinkDock.show() - - def slotShowConnector(self): - """ - connectionmanager action triggered - let main to the stuff - """ - self.emit(SIGNAL("connector")) - - def slotAddPackage(self, name, links, password=None): - """ - new package - let main to the stuff - """ - self.emit(SIGNAL("addPackage"), name, links, password) - - def slotAddLinksToPackage(self, links): - """ - adds links to currently selected package - only in collector - """ - if self.tabw.currentIndex() != 1: - return - - smodel = self.tabs["collector"]["package_view"].selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - if isinstance(item, Package): - self.connector.proxy.addFiles(item.id, links) - break - - def slotShowAddContainer(self): - """ - action from add-menu - show file selector, emit upload - """ - typeStr = ";;".join([ - _("All Container Types (%s)") % "*.dlc *.ccf *.rsdf *.txt", - _("DLC (%s)") % "*.dlc", - _("CCF (%s)") % "*.ccf", - _("RSDF (%s)") % "*.rsdf", - _("Text Files (%s)") % "*.txt" - ]) - fileNames = QFileDialog.getOpenFileNames(self, _("Open container"), "", typeStr) - for name in fileNames: - self.emit(SIGNAL("addContainer"), str(name)) - - def slotPushPackageToQueue(self): - """ - push collector pack to queue - get child ids - let main to the rest - """ - smodel = self.tabs["collector"]["package_view"].selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - if isinstance(item, Package): - self.emit(SIGNAL("pushPackageToQueue"), item.id) - else: - self.emit(SIGNAL("pushPackageToQueue"), item.package.id) - - def saveWindow(self): - """ - get window state/geometry - pass data to main - """ - state_raw = self.saveState(self.version) - geo_raw = self.saveGeometry() - - state = str(state_raw.toBase64()) - geo = str(geo_raw.toBase64()) - - self.emit(SIGNAL("saveMainWindow"), state, geo) - - def closeEvent(self, event): - """ - somebody wants to close me! - let me first save my state - """ - self.saveWindow() - event.ignore() - self.hide() - self.emit(SIGNAL("hidden")) - - # quit when no tray is available - if not QSystemTrayIcon.isSystemTrayAvailable(): - self.emit(SIGNAL("Quit")) - - def restoreWindow(self, state, geo): - """ - restore window state/geometry - """ - state = QByteArray(state) - geo = QByteArray(geo) - - state_raw = QByteArray.fromBase64(state) - geo_raw = QByteArray.fromBase64(geo) - - self.restoreState(state_raw, self.version) - self.restoreGeometry(geo_raw) - - def slotQueueContextMenu(self, pos): - """ - custom context menu in queue view requested - """ - globalPos = self.tabs["queue"]["view"].mapToGlobal(pos) - i = self.tabs["queue"]["view"].indexAt(pos) - if not i: - return - item = i.internalPointer() - menuPos = QCursor.pos() - menuPos.setX(menuPos.x()+2) - self.activeMenu = self.queueContext - showAbort = False - if isinstance(item, Link) and item.data["downloading"]: - showAbort = True - elif isinstance(item, Package): - for child in item.children: - if child.data["downloading"]: - showAbort = True - break - if showAbort: - self.queueContext.buttons["abort"].setEnabled(True) - else: - self.queueContext.buttons["abort"].setEnabled(False) - if isinstance(item, Package): - self.queueContext.index = i - #self.queueContext.buttons["remove"].setEnabled(True) - #self.queueContext.buttons["restart"].setEnabled(True) - self.queueContext.buttons["pull"].setEnabled(True) - self.queueContext.buttons["edit"].setEnabled(True) - elif isinstance(item, Link): - self.collectorContext.index = i - self.collectorContext.buttons["edit"].setEnabled(False) - self.collectorContext.buttons["remove"].setEnabled(True) - self.collectorContext.buttons["push"].setEnabled(False) - self.collectorContext.buttons["restart"].setEnabled(True) - else: - self.queueContext.index = None - #self.queueContext.buttons["remove"].setEnabled(False) - #self.queueContext.buttons["restart"].setEnabled(False) - self.queueContext.buttons["pull"].setEnabled(False) - self.queueContext.buttons["edit"].setEnabled(False) - self.queueContext.exec_(menuPos) - - def slotCollectorContextMenu(self, pos): - """ - custom context menu in package collector view requested - """ - globalPos = self.tabs["collector"]["package_view"].mapToGlobal(pos) - i = self.tabs["collector"]["package_view"].indexAt(pos) - if not i: - return - item = i.internalPointer() - menuPos = QCursor.pos() - menuPos.setX(menuPos.x()+2) - self.activeMenu = self.collectorContext - if isinstance(item, Package): - self.collectorContext.index = i - self.collectorContext.buttons["edit"].setEnabled(True) - self.collectorContext.buttons["remove"].setEnabled(True) - self.collectorContext.buttons["push"].setEnabled(True) - self.collectorContext.buttons["restart"].setEnabled(True) - elif isinstance(item, Link): - self.collectorContext.index = i - self.collectorContext.buttons["edit"].setEnabled(False) - self.collectorContext.buttons["remove"].setEnabled(True) - self.collectorContext.buttons["push"].setEnabled(False) - self.collectorContext.buttons["restart"].setEnabled(True) - else: - self.collectorContext.index = None - self.collectorContext.buttons["edit"].setEnabled(False) - self.collectorContext.buttons["remove"].setEnabled(False) - self.collectorContext.buttons["push"].setEnabled(False) - self.collectorContext.buttons["restart"].setEnabled(False) - self.collectorContext.exec_(menuPos) - - def slotLinkCollectorContextMenu(self, pos): - """ - custom context menu in link collector view requested - """ - pass - - def slotRestartDownload(self): - """ - restart download action is triggered - """ - smodel = self.tabs["queue"]["view"].selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - self.emit(SIGNAL("restartDownload"), item.id, isinstance(item, Package)) - - def slotRemoveDownload(self): - """ - remove download action is triggered - """ - if self.activeMenu == self.queueContext: - view = self.tabs["queue"]["view"] - else: - view = self.tabs["collector"]["package_view"] - smodel = view.selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - self.emit(SIGNAL("removeDownload"), item.id, isinstance(item, Package)) - - def slotToggleClipboard(self, status): - """ - check clipboard (toolbar) - """ - self.emit(SIGNAL("setClipboardStatus"), status) - - def slotEditPackage(self): - # in Queue, only edit name - if self.activeMenu == self.queueContext: - view = self.tabs["queue"]["view"] - else: - view = self.tabs["collector"]["package_view"] - view.edit(self.activeMenu.index) - - def slotEditCommit(self, editor): - self.emit(SIGNAL("changePackageName"), self.activeMenu.index.internalPointer().id, editor.text()) - - def slotPullOutPackage(self): - """ - pull package out of the queue - """ - smodel = self.tabs["queue"]["view"].selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - if isinstance(item, Package): - self.emit(SIGNAL("pullOutPackage"), item.id) - else: - self.emit(SIGNAL("pullOutPackage"), item.package.id) - - def slotAbortDownload(self): - view = self.tabs["queue"]["view"] - smodel = view.selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - self.emit(SIGNAL("abortDownload"), item.id, isinstance(item, Package)) - - # TODO disabled because changing desktop on linux, main window disappears - #def changeEvent(self, e): - # if e.type() == QEvent.WindowStateChange and self.isMinimized(): - # e.ignore() - # self.hide() - # self.emit(SIGNAL("hidden")) - # else: - # super(MainWindow, self).changeEvent(e) - - def slotTabChanged(self, index): - if index == 2: - self.emit(SIGNAL("reloadAccounts")) - elif index == 3: - self.tabs["settings"]["w"].loadConfig() - - def slotRefreshPackage(self): - smodel = self.tabs["collector"]["package_view"].selectionModel() - for index in smodel.selectedRows(0): - item = index.internalPointer() - pid = item.id - if isinstance(item, Link): - pid = item.package.id - self.emit(SIGNAL("refreshStatus"), pid) - - def slotNewAccount(self): - types = self.connector.proxy.getAccountTypes() - self.accountEdit = AccountEdit.newAccount(types) - - #TODO make more easy n1, n2, n3 - def save(data): - if data["password"]: - self.accountEdit.close() - n1 = data["acctype"] - n2 = data["login"] - n3 = data["password"] - self.connector.updateAccount(n1, n2, n3, None) - - self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save) - self.accountEdit.show() - - def slotEditAccount(self): - types = self.connector.getAccountTypes() - - data = self.tabs["accounts"]["view"].selectedIndexes() - if len(data) < 1: - return - - data = data[0].internalPointer() - - self.accountEdit = AccountEdit.editAccount(types, data) - - #TODO make more easy n1, n2, n3 - #TODO reload accounts tab after insert of edit account - #TODO if account does not exist give error - def save(data): - self.accountEdit.close() - n1 = data["acctype"] - n2 = data["login"] - if data["password"]: - n3 = data["password"] - self.connector.updateAccount(n1, n2, n3, None) - - self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save) - self.accountEdit.show() - - def slotRemoveAccount(self): - data = self.tabs["accounts"]["view"].selectedIndexes() - if len(data) < 1: - return - - data = data[0].internalPointer() - - self.connector.removeAccount(data.type, data.login) - - def slotAccountContextMenu(self, pos): - globalPos = self.tabs["accounts"]["view"].mapToGlobal(pos) - i = self.tabs["accounts"]["view"].indexAt(pos) - if not i: - return - - data = i.internalPointer() - - if data is None: - self.accountContext.buttons["edit"].setEnabled(False) - self.accountContext.buttons["remove"].setEnabled(False) - else: - self.accountContext.buttons["edit"].setEnabled(True) - self.accountContext.buttons["remove"].setEnabled(True) - - menuPos = QCursor.pos() - menuPos.setX(menuPos.x()+2) - self.accountContext.exec_(menuPos) diff --git a/module/gui/Overview.py b/module/gui/Overview.py deleted file mode 100644 index 183383b5e..000000000 --- a/module/gui/Overview.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from module.utils import formatSpeed, formatSize - -class OverviewModel(QAbstractListModel): - PackageName = 10 - Progress = 11 - PartsFinished = 12 - Parts = 13 - ETA = 14 - Speed = 15 - CurrentSize = 16 - MaxSize = 17 - Status = 18 - - def __init__(self, view, connector): - QAbstractListModel.__init__(self) - - self.packages = [] - - def queueChanged(self): #dirty.. - self.beginResetModel() - - self.packages = [] - - def partsFinished(p): - f = 0 - for c in p.children: - if c.data["status"] == 0: - f += 1 - return f - - def maxSize(p): - ms = 0 - cs = 0 - for c in p.children: - try: - s = c.data["downloading"]["size"] - except: - s = c.data["size"] - if c.data["downloading"]: - cs += s - c.data["downloading"]["bleft"] - elif self.queue.getProgress(c, False) == 100: - cs += s - ms += s - return ms, cs - - def getProgress(p): - for c in p.children: - if c.data["status"] == 13: - pass # TODO return _("Unpacking"), int(c.data["progress"]) - return _("Downloading"), self.queue.getProgress(p) - - d = self.queue._data - for p in d: - status, progress = getProgress(p) - maxsize, currentsize = maxSize(p) - speed = self.queue.getSpeed(p) - if speed: - eta = (maxsize - (maxsize * (progress/100.0)))/speed - else: - eta = 0 - if not speed and not progress: - status = _("Queued") - info = { - OverviewModel.PackageName: p.data["name"], - OverviewModel.Progress: progress, - OverviewModel.PartsFinished: partsFinished(p), - OverviewModel.Parts: len(p.children), - OverviewModel.ETA: int(eta), - OverviewModel.Speed: speed, - OverviewModel.CurrentSize: currentsize, - OverviewModel.MaxSize: maxsize, - OverviewModel.Status: status, - } - - self.packages.append(info) - - self.endResetModel() - - def headerData(self, section, orientation, role=Qt.DisplayRole): - return QVariant(_("Package")) - - def rowCount(self, parent=QModelIndex()): - return len(self.packages) - - def data(self, index, role=Qt.DisplayRole): - if role in [OverviewModel.PackageName, OverviewModel.Progress, OverviewModel.PartsFinished, OverviewModel.Parts, OverviewModel.ETA, OverviewModel.Speed, OverviewModel.CurrentSize, OverviewModel.MaxSize, OverviewModel.Status]: - return QVariant(self.packages[index.row()][role]) - return QVariant() - -class OverviewView(QListView): - def __init__(self, connector): - QListView.__init__(self) - self.setModel(OverviewModel(self, connector)) - - self.setAlternatingRowColors(True) - self.delegate = OverviewDelegate(self) - self.setItemDelegate(self.delegate) - -class OverviewDelegate(QItemDelegate): - def __init__(self, parent): - QItemDelegate.__init__(self, parent) - self.parent = parent - self.model = parent.model() - - def paint(self, painter, option, index): - option.rect.setHeight(59+16) - option.rect.setWidth(self.parent.width()-20) - - #if option.state & QStyle.State_Selected: - # painter.fillRect(option.rect, option.palette.color(QPalette.Highlight)) - - packagename = index.data(OverviewModel.PackageName).toString() - partsf = index.data(OverviewModel.PartsFinished).toString() - parts = index.data(OverviewModel.Parts).toString() - eta = int(index.data(OverviewModel.ETA).toString()) - speed = index.data(OverviewModel.Speed).toString() or 0 - progress = int(index.data(OverviewModel.Progress).toString()) - currentSize = int(index.data(OverviewModel.CurrentSize).toString()) - maxSize = int(index.data(OverviewModel.MaxSize).toString()) - status = index.data(OverviewModel.Status).toString() - - def formatEta(seconds): #TODO add to utils - if seconds <= 0: return "" - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - return _("ETA: ") + "%.2i:%.2i:%.2i" % (hours, minutes, seconds) - - statusline = QString(_("Parts: ") + "%s/%s" % (partsf, parts)) - if partsf == parts: - speedline = _("Finished") - elif not status == _("Downloading"): - speedline = QString(status) - else: - speedline = QString(formatEta(eta) + " " + _("Speed: %s") % formatSpeed(speed)) - - if progress in (0,100): - sizeline = QString(_("Size:") + "%s" % formatSize(maxSize)) - else: - sizeline = QString(_("Size:") + "%s / %s" % (formatSize(currentSize), formatSize(maxSize))) - - f = painter.font() - f.setPointSize(12) - f.setBold(True) - painter.setFont(f) - - r = option.rect.adjusted(4, 4, -4, -4) - painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename) - newr = painter.boundingRect(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename) - - f.setPointSize(10) - f.setBold(False) - painter.setFont(f) - - painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline) - painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignHCenter, sizeline) - painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignRight, speedline) - newr = painter.boundingRect(r.left(), newr.bottom()+2, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline) - newr.setTop(newr.bottom()+8) - newr.setBottom(newr.top()+20) - newr.setRight(self.parent.width()-25) - - f.setPointSize(10) - painter.setFont(f) - - opts = QStyleOptionProgressBarV2() - opts.maximum = 100 - opts.minimum = 0 - opts.progress = progress - opts.rect = newr - opts.textVisible = True - opts.textAlignment = Qt.AlignCenter - opts.text = QString.number(opts.progress) + "%" - QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter) - - def sizeHint(self, option, index): - return QSize(self.parent.width()-22, 59+16) diff --git a/module/gui/PackageDock.py b/module/gui/PackageDock.py deleted file mode 100644 index 73db8f177..000000000 --- a/module/gui/PackageDock.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" -import re - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -class NewPackageDock(QDockWidget): - def __init__(self): - QDockWidget.__init__(self, _("New Package")) - self.setObjectName("New Package Dock") - self.widget = NewPackageWindow(self) - self.setWidget(self.widget) - self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea) - self.hide() - - def slotDone(self): - text = str(self.widget.box.toPlainText()) - pw = str(self.widget.passwordInput.text()) - if not pw: - pw = None - lines = [] - for line in text.splitlines(): - line = line.strip() - if not line: - continue - lines.append(line) - self.emit(SIGNAL("done"), str(self.widget.nameInput.text()), lines, pw) - self.widget.nameInput.setText("") - self.widget.passwordInput.setText("") - self.widget.box.clear() - self.hide() - - def parseUri(self): - - text=str(self.widget.box.toPlainText()) - self.widget.box.setText("") - result = re.findall(r"(?:ht|f)tps?:\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}", text) - for url in result: - if "\n" or "\t" or "\r" or "\"" or "<" or "'" in url: - url = url[:-1] - self.widget.box.append("%s " % url) - -class NewPackageWindow(QWidget): - def __init__(self, dock): - QWidget.__init__(self) - self.dock = dock - self.setLayout(QGridLayout()) - layout = self.layout() - - nameLabel = QLabel(_("Name")) - nameInput = QLineEdit() - passwordLabel = QLabel(_("Password")) - passwordInput = QLineEdit() - - linksLabel = QLabel(_("Links in this Package")) - - self.box = QTextEdit() - self.nameInput = nameInput - self.passwordInput = passwordInput - - save = QPushButton(_("Create")) - parseUri = QPushButton(_("Filter URLs")) - - layout.addWidget(nameLabel, 0, 0) - layout.addWidget(nameInput, 0, 1) - layout.addWidget(passwordLabel, 1, 0) - layout.addWidget(passwordInput, 1, 1) - layout.addWidget(linksLabel, 2, 0, 1, 2) - layout.addWidget(self.box, 3, 0, 1, 2) - layout.addWidget(parseUri, 4, 0, 1, 2) - layout.addWidget(save, 5, 0, 1, 2) - - self.connect(save, SIGNAL("clicked()"), self.dock.slotDone) - self.connect(parseUri, SIGNAL("clicked()"), self.dock.parseUri)
\ No newline at end of file diff --git a/module/gui/Queue.py b/module/gui/Queue.py deleted file mode 100644 index 0a0cbb810..000000000 --- a/module/gui/Queue.py +++ /dev/null @@ -1,390 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from time import time - -from module.remote.thriftbackend.ThriftClient import Destination -from module.gui.Collector import CollectorModel, Package, Link, CollectorView, statusMapReverse -from module.utils import formatSize, formatSpeed - -class QueueModel(CollectorModel): - """ - model for the queue view, inherits from CollectorModel - """ - - def __init__(self, view, connector): - CollectorModel.__init__(self, view, connector) - self.cols = 6 - self.wait_dict = {} - - self.updater = self.QueueUpdater(self.interval) - self.connect(self.updater, SIGNAL("update()"), self.update) - - class QueueUpdater(QObject): - """ - timer which emits signal for a download status reload - @TODO: make intervall configurable - """ - - def __init__(self, interval): - QObject.__init__(self) - - self.interval = interval - self.timer = QTimer() - self.timer.connect(self.timer, SIGNAL("timeout()"), self, SIGNAL("update()")) - - def start(self): - self.timer.start(1000) - - def stop(self): - self.timer.stop() - - def start(self): - self.updater.start() - - def stop(self): - self.updater.stop() - - def fullReload(self): - """ - reimplements CollectorModel.fullReload, because we want the Queue data - """ - self._data = [] - order = self.connector.getPackageOrder(Destination.Queue) - self.beginInsertRows(QModelIndex(), 0, len(order.values())) - for position, pid in order.iteritems(): - pack = self.connector.getPackageData(pid) - package = Package(pack) - self._data.append(package) - self._data = sorted(self._data, key=lambda p: p.data["order"]) - self.endInsertRows() - self.updateCount() - - def insertEvent(self, event): - """ - wrap CollectorModel.insertEvent to update the element count - """ - CollectorModel.insertEvent(self, event) - self.updateCount() - - def removeEvent(self, event): - """ - wrap CollectorModel.removeEvent to update the element count - """ - CollectorModel.removeEvent(self, event) - self.updateCount() - - def updateEvent(self, event): - """ - wrap CollectorModel.updateEvent to update the element count - """ - CollectorModel.updateEvent(self, event) - self.updateCount() - - def updateCount(self): - """ - calculate package- and filecount for statusbar, - ugly?: Overview connects to this signal for updating - """ - packageCount = len(self._data) - fileCount = 0 - for p in self._data: - fileCount += len(p.children) - self.mutex.unlock() - self.emit(SIGNAL("updateCount"), packageCount, fileCount) - self.mutex.lock() - - def update(self): - """ - update slot for download status updating - """ - locker = QMutexLocker(self.mutex) - downloading = self.connector.statusDownloads() - if not downloading: - return - for p, pack in enumerate(self._data): - for d in downloading: - child = pack.getChild(d.fid) - if child: - dd = { - "name": d.name, - "speed": d.speed, - "eta": d.eta, - "format_eta": d.format_eta, - "bleft": d.bleft, - "size": d.size, - "format_size": d.format_size, - "percent": d.percent, - "status": d.status, - "statusmsg": d.statusmsg, - "format_wait": d.format_wait, - "wait_until": d.wait_until - } - child.data["downloading"] = dd - k = pack.getChildKey(d.fid) - self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols))) - self.updateCount() - - def headerData(self, section, orientation, role=Qt.DisplayRole): - """ - returns column heading - """ - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - if section == 0: - return QVariant(_("Name")) - elif section == 2: - return QVariant(_("Status")) - elif section == 1: - return QVariant(_("Plugin")) - elif section == 3: - return QVariant(_("Size")) - elif section == 4: - return QVariant(_("ETA")) - elif section == 5: - return QVariant(_("Progress")) - return QVariant() - - def getWaitingProgress(self, item): - """ - returns time to wait, caches startingtime to provide progress - """ - locker = QMutexLocker(self.mutex) - if isinstance(item, Link): - if item.data["status"] == 5 and item.data["downloading"]: - until = float(item.data["downloading"]["wait_until"]) - try: - since, until_old = self.wait_dict[item.id] - if not until == until_old: - raise Exception - except: - since = time() - self.wait_dict[item.id] = since, until - since = float(since) - max_wait = float(until-since) - rest = int(until-time()) - if rest < 0: - return 0, None - res = 100/max_wait - perc = rest*res - return perc, rest - return None - - def getProgress(self, item, locked=True): - """ - return download progress, locks by default - since it's used in already locked calls, - it provides an option to not lock - """ - if locked: - locker = QMutexLocker(self.mutex) - if isinstance(item, Link): - try: - if item.data["status"] == 0: - return 100 - return int(item.data["downloading"]["percent"]) - except: - return 0 - elif isinstance(item, Package): - count = len(item.children) - perc_sum = 0 - for child in item.children: - try: - if child.data["status"] == 0: #completed - perc_sum += 100 - perc_sum += int(child.data["downloading"]["percent"]) - except: - pass - if count == 0: - return 0 - return perc_sum/count - return 0 - - def getSpeed(self, item): - """ - calculate download speed - """ - if isinstance(item, Link): - if item.data["downloading"]: - return int(item.data["downloading"]["speed"]) - elif isinstance(item, Package): - count = len(item.children) - speed_sum = 0 - all_waiting = True - running = False - for child in item.children: - val = 0 - if child.data["downloading"]: - if not child.data["statusmsg"] == "waiting": - all_waiting = False - val = int(child.data["downloading"]["speed"]) - running = True - speed_sum += val - if count == 0 or not running or all_waiting: - return None - return speed_sum - return None - - def data(self, index, role=Qt.DisplayRole): - """ - return cell data - """ - if not index.isValid(): - return QVariant() - if role == Qt.DisplayRole: - if index.column() == 0: - return QVariant(index.internalPointer().data["name"]) - elif index.column() == 1: - item = index.internalPointer() - plugins = [] - if isinstance(item, Package): - for child in item.children: - if not child.data["plugin"] in plugins: - plugins.append(child.data["plugin"]) - else: - plugins.append(item.data["plugin"]) - return QVariant(", ".join(plugins)) - elif index.column() == 2: - item = index.internalPointer() - status = 0 - speed = self.getSpeed(item) - if isinstance(item, Package): - for child in item.children: - if child.data["status"] > status: - status = child.data["status"] - else: - status = item.data["status"] - - if speed is None or status == 7 or status == 10 or status == 5: - return QVariant(self.translateStatus(statusMapReverse[status])) - else: - return QVariant("%s (%s)" % (self.translateStatus(statusMapReverse[status]), formatSpeed(speed))) - elif index.column() == 3: - item = index.internalPointer() - if isinstance(item, Link): - if item.data["status"] == 0: #TODO needs change?? - #self.getProgress(item, False) == 100: - return QVariant(formatSize(item.data["size"])) - elif self.getProgress(item, False) == 0: - try: - return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"]))) - except: - return QVariant("0 B / %s" % formatSize(item.data["size"])) - else: - try: - return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"]))) - except: - return QVariant("? / %s" % formatSize(item.data["size"])) - else: - ms = 0 - cs = 0 - for c in item.children: - try: - s = c.data["downloading"]["size"] - except: - s = c.data["size"] - if c.data["downloading"]: - cs += s - c.data["downloading"]["bleft"] - elif self.getProgress(c, False) == 100: - cs += s - ms += s - if cs == 0 or cs == ms: - return QVariant(formatSize(ms)) - else: - return QVariant("%s / %s" % (formatSize(cs), formatSize(ms))) - elif index.column() == 4: - item = index.internalPointer() - if isinstance(item, Link): - if item.data["downloading"]: - return QVariant(item.data["downloading"]["format_eta"]) - elif role == Qt.EditRole: - if index.column() == 0: - return QVariant(index.internalPointer().data["name"]) - return QVariant() - - def flags(self, index): - """ - cell flags - """ - if index.column() == 0 and self.parent(index) == QModelIndex(): - return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled - return Qt.ItemIsSelectable | Qt.ItemIsEnabled - -class QueueView(CollectorView): - """ - view component for queue - """ - - def __init__(self, connector): - CollectorView.__init__(self, connector) - self.setModel(QueueModel(self, connector)) - - self.setColumnWidth(0, 300) - self.setColumnWidth(1, 100) - self.setColumnWidth(2, 140) - self.setColumnWidth(3, 180) - self.setColumnWidth(4, 70) - - self.setEditTriggers(QAbstractItemView.NoEditTriggers) - - self.delegate = QueueProgressBarDelegate(self, self.model()) - self.setItemDelegateForColumn(5, self.delegate) - -class QueueProgressBarDelegate(QItemDelegate): - """ - used to display a progressbar in the progress cell - """ - - def __init__(self, parent, queue): - QItemDelegate.__init__(self, parent) - self.queue = queue - - def paint(self, painter, option, index): - """ - paint the progressbar - """ - if not index.isValid(): - return - if index.column() == 5: - item = index.internalPointer() - w = self.queue.getWaitingProgress(item) - wait = None - if w: - progress = w[0] - wait = w[1] - else: - progress = self.queue.getProgress(item) - opts = QStyleOptionProgressBarV2() - opts.maximum = 100 - opts.minimum = 0 - opts.progress = progress - opts.rect = option.rect - opts.rect.setRight(option.rect.right()-1) - opts.rect.setHeight(option.rect.height()-1) - opts.textVisible = True - opts.textAlignment = Qt.AlignCenter - if not wait is None: - opts.text = QString(_("waiting %d seconds") % (wait,)) - else: - opts.text = QString.number(opts.progress) + "%" - QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter) - return - QItemDelegate.paint(self, painter, option, index) - diff --git a/module/gui/SettingsWidget.py b/module/gui/SettingsWidget.py deleted file mode 100644 index cd22a7b9e..000000000 --- a/module/gui/SettingsWidget.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from sip import delete - - -class SettingsWidget(QWidget): - def __init__(self): - QWidget.__init__(self) - self.connector = None - self.sections = {} - self.psections = {} - self.data = None - self.pdata = None - self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) - - def setConnector(self, connector): - self.connector = connector - - def loadConfig(self): - if self.sections and self.psections: - self.data = self.connector.getConfig() - self.pdata = self.connector.getPluginConfig() - - self.reloadSection(self.sections, self.data) - self.reloadSection(self.psections, self.pdata) - - return - - if self.layout(): - delete(self.layout()) - - for s in self.sections.values()+self.psections.values(): - delete(s) - - self.sections = {} - self.setLayout(QVBoxLayout()) - self.clearConfig() - layout = self.layout() - layout.setSizeConstraint(QLayout.SetMinAndMaxSize) - - general = QTabWidget() - self.general = general - - plugins = QTabWidget() - self.plugins = plugins - - tab = QTabWidget() - self.tab = tab - - gw = QWidget() - gw.setLayout(QVBoxLayout()) - gw.layout().addWidget(self.general) - pw = QWidget() - pw.setLayout(QVBoxLayout()) - pw.layout().addWidget(self.plugins) - tab.addTab(gw, _("General")) - tab.addTab(pw, _("Plugins")) - - layout.addWidget(tab) - - self.data = self.connector.getConfig() - self.pdata = self.connector.getPluginConfig() - for k, section in self.data.iteritems(): - s = Section(section, general) - self.sections[k] = s - - for k, section in self.pdata.iteritems(): - s = Section(section, plugins, "plugin") - self.psections[k] = s - - rel = QPushButton(_("Reload")) - save = QPushButton(_("Save")) - - layout.addWidget(save) - - cont = QHBoxLayout() - cont.addWidget(rel) - cont.addWidget(save) - - layout.addLayout(cont) - - self.connect(save, SIGNAL("clicked()"), self.saveConfig) - self.connect(rel, SIGNAL("clicked()"), self.loadConfig) - - def clearConfig(self): - self.sections = {} - - def reloadSection(self, sections, pdata): - - for k, section in enumerate(pdata): - if k in sections: - widget = sections[k] - for item in section.items: - if item.name in widget.inputs: - i = widget.inputs[item.name] - - if item.type == "int": - i.setValue(int(item.value)) - elif not item.type.find(";") == -1: - i.setCurrentIndex(i.findText(item.value)) - elif item.type == "bool": - if True if item.value.lower() in ("1","true", "on", "an","yes") else False: - i.setCurrentIndex(0) - else: - i.setCurrentIndex(1) - else: - i.setText(item.value) - - - def saveConfig(self): - self.data = self.connector.getConfig() - self.pdata = self.connector.getPluginConfig() - - self.saveSection(self.sections, self.data) - self.saveSection(self.psections, self.pdata, "plugin") - - - def saveSection(self, sections, pdata, sec="core"): - for k, section in enumerate(pdata): - if k in sections: - widget = sections[k] - for item in section.items: - if item.name in widget.inputs: - i = widget.inputs[item.name] - - #TODO : unresolved reference: option - - if item.type == "int": - if i.value() != int(item.value): - self.connector.setConfigValue(k, option, i.value(), sec) - elif not item.type.find(";") == -1: - if i.currentText() != item.value: - self.connector.setConfigValue(k, option, i.currentText(), sec) - elif item.type == "bool": - if (True if item.value.lower() in ("1","true", "on", "an","yes") else False) ^ (not i.currentIndex()): - self.connector.setConfigValue(k, option, not i.currentIndex(), sec) - else: - if i.text() != item.value: - self.connector.setConfigValue(k, option, str(i.text()), sec) - -class Section(QGroupBox): - def __init__(self, data, parent, ctype="core"): - self.data = data - QGroupBox.__init__(self, data.description, parent) - self.labels = {} - self.inputs = {} - self.ctype = ctype - layout = QFormLayout(self) - self.setLayout(layout) - - sw = QWidget() - sw.setLayout(QVBoxLayout()) - sw.layout().addWidget(self) - - sa = QScrollArea() - sa.setWidgetResizable(True) - sa.setWidget(sw) - sa.setFrameShape(sa.NoFrame) - - parent.addTab(sa, data.description) - - for option in self.data.items: - if option.type == "int": - i = QSpinBox(self) - i.setMaximum(999999) - i.setValue(int(option.value)) - elif not option.type.find(";") == -1: - choices = option.type.split(";") - i = QComboBox(self) - i.addItems(choices) - i.setCurrentIndex(i.findText(option.value)) - elif option.type == "bool": - i = QComboBox(self) - i.addItem(_("Yes"), QVariant(True)) - i.addItem(_("No"), QVariant(False)) - if True if option.value.lower() in ("1","true", "on", "an","yes") else False: - i.setCurrentIndex(0) - else: - i.setCurrentIndex(1) - else: - i = QLineEdit(self) - i.setText(option.value) - layout.addRow(option.description, i) - layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) diff --git a/module/gui/XMLParser.py b/module/gui/XMLParser.py deleted file mode 100644 index 5e3b7bf65..000000000 --- a/module/gui/XMLParser.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" -from __future__ import with_statement - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.QtXml import * - -import os - -class XMLParser(): - def __init__(self, data, dfile=""): - self.mutex = QMutex() - self.mutex.lock() - self.xml = QDomDocument() - self.file = data - self.dfile = dfile - self.mutex.unlock() - self.loadData() - self.root = self.xml.documentElement() - - def loadData(self): - self.mutex.lock() - f = self.file - if not os.path.exists(f): - f = self.dfile - with open(f, 'r') as fh: - content = fh.read() - self.xml.setContent(content) - self.mutex.unlock() - - def saveData(self): - self.mutex.lock() - content = self.xml.toString() - with open(self.file, 'w') as fh: - fh.write(content) - self.mutex.unlock() - return content - - def parseNode(self, node, ret_type="list"): - if ret_type == "dict": - childNodes = {} - else: - childNodes = [] - child = node.firstChild() - while True: - n = child.toElement() - if n.isNull(): - break - else: - if ret_type == "dict": - childNodes[str(n.tagName())] = n - else: - childNodes.append(n) - child = child.nextSibling() - return childNodes diff --git a/module/gui/__init__.py b/module/gui/__init__.py deleted file mode 100644 index 8d1c8b69c..000000000 --- a/module/gui/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/module/gui/connector.py b/module/gui/connector.py deleted file mode 100644 index c16ccd08e..000000000 --- a/module/gui/connector.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -SERVER_VERSION = "0.4.9" - -from time import sleep -from uuid import uuid4 as uuid - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -import socket - -from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin, NoSSL, NoConnection -from thrift.Thrift import TException - -class Connector(QObject): - """ - manages the connection to the pyload core via thrift - """ - - firstAttempt = True - - def __init__(self): - QObject.__init__(self) - self.mutex = QMutex() - self.connectionID = None - self.host = None - self.port = None - self.user = None - self.password = None - self.ssl = None - self.running = True - self.internal = False - self.proxy = self.Dummy() - - def setConnectionData(self, host, port, user, password, ssl=False): - """ - set connection data for connection attempt, called from slotConnect - """ - self.host = host - self.port = port - self.user = user - self.password = password - self.ssl = ssl - - def connectProxy(self): - """ - initialize thrift rpc client, - check for ssl, check auth, - setup dispatcher, - connect error signals, - check server version - """ - if self.internal: return True - - err = None - try: - client = ThriftClient(self.host, self.port, self.user, self.password) - except WrongLogin: - err = _("bad login credentials") - except NoSSL: - err = _("no ssl support") - except NoConnection: - err = _("can't connect to host") - if err: - if not Connector.firstAttempt: - self.emit(SIGNAL("errorBox"), err) - Connector.firstAttempt = False - return False - - self.proxy = DispatchRPC(self.mutex, client) - self.connect(self.proxy, SIGNAL("connectionLost"), self, SIGNAL("connectionLost")) - - server_version = self.proxy.getServerVersion() - self.connectionID = uuid().hex - - if not server_version == SERVER_VERSION: - self.emit(SIGNAL("errorBox"), _("server is version %(new)s client accepts version %(current)s") % { "new": server_version, "current": SERVER_VERSION}) - return False - - return True - - def __getattr__(self, attr): - """ - redirect rpc calls to dispatcher - """ - return getattr(self.proxy, attr) - - class Dummy(object): - """ - dummy rpc proxy, to prevent errors - """ - def __nonzero__(self): - return False - - def __getattr__(self, attr): - def dummy(*args, **kwargs): - return None - return dummy - -class DispatchRPC(QObject): - """ - wraps the thrift client, to catch critical exceptions (connection lost) - adds thread safety - """ - - def __init__(self, mutex, server): - QObject.__init__(self) - self.mutex = mutex - self.server = server - - def __getattr__(self, attr): - """ - redirect and wrap call in Wrapper instance, locks dispatcher - """ - self.mutex.lock() - self.fname = attr - f = self.Wrapper(getattr(self.server, attr), self.mutex, self) - return f - - class Wrapper(object): - """ - represents a rpc call - """ - - def __init__(self, f, mutex, dispatcher): - self.f = f - self.mutex = mutex - self.dispatcher = dispatcher - - def __call__(self, *args, **kwargs): - """ - instance is called, rpc is executed - exceptions are processed - finally dispatcher is unlocked - """ - lost = False - try: - return self.f(*args, **kwargs) - except socket.error: #necessary? - lost = True - except TException: - lost = True - finally: - self.mutex.unlock() - if lost: - from traceback import print_exc - print_exc() - self.dispatcher.emit(SIGNAL("connectionLost")) diff --git a/module/lib/BeautifulSoup.py b/module/lib/BeautifulSoup.py deleted file mode 100644 index 55567f588..000000000 --- a/module/lib/BeautifulSoup.py +++ /dev/null @@ -1,2012 +0,0 @@ -"""Beautiful Soup -Elixir and Tonic -"The Screen-Scraper's Friend" -http://www.crummy.com/software/BeautifulSoup/ - -Beautiful Soup parses a (possibly invalid) XML or HTML document into a -tree representation. It provides methods and Pythonic idioms that make -it easy to navigate, search, and modify the tree. - -A well-formed XML/HTML document yields a well-formed data -structure. An ill-formed XML/HTML document yields a correspondingly -ill-formed data structure. If your document is only locally -well-formed, you can use this library to find and process the -well-formed part of it. - -Beautiful Soup works with Python 2.2 and up. It has no external -dependencies, but you'll have more success at converting data to UTF-8 -if you also install these three packages: - -* chardet, for auto-detecting character encodings - http://chardet.feedparser.org/ -* cjkcodecs and iconv_codec, which add more encodings to the ones supported - by stock Python. - http://cjkpython.i18n.org/ - -Beautiful Soup defines classes for two main parsing strategies: - - * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific - language that kind of looks like XML. - - * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid - or invalid. This class has web browser-like heuristics for - obtaining a sensible parse tree in the face of common HTML errors. - -Beautiful Soup also defines a class (UnicodeDammit) for autodetecting -the encoding of an HTML or XML document, and converting it to -Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser. - -For more than you ever wanted to know about Beautiful Soup, see the -documentation: -http://www.crummy.com/software/BeautifulSoup/documentation.html - -Here, have some legalese: - -Copyright (c) 2004-2010, Leonard Richardson - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the the Beautiful Soup Consortium and All - Night Kosher Bakery nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. - -""" -from __future__ import generators - -__author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "3.0.8.1" -__copyright__ = "Copyright (c) 2004-2010 Leonard Richardson" -__license__ = "New-style BSD" - -from sgmllib import SGMLParser, SGMLParseError -import codecs -import markupbase -import types -import re -import sgmllib -try: - from htmlentitydefs import name2codepoint -except ImportError: - name2codepoint = {} -try: - set -except NameError: - from sets import Set as set - -#These hacks make Beautiful Soup able to parse XML with namespaces -sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') -markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match - -DEFAULT_OUTPUT_ENCODING = "utf-8" - -def _match_css_class(str): - """Build a RE to match the given CSS class.""" - return re.compile(r"(^|.*\s)%s($|\s)" % str) - -# First, the classes that represent markup elements. - -class PageElement(object): - """Contains the navigational information for some part of the page - (either a tag or a piece of text)""" - - def setup(self, parent=None, previous=None): - """Sets up the initial relations between this element and - other elements.""" - self.parent = parent - self.previous = previous - self.next = None - self.previousSibling = None - self.nextSibling = None - if self.parent and self.parent.contents: - self.previousSibling = self.parent.contents[-1] - self.previousSibling.nextSibling = self - - def replaceWith(self, replaceWith): - oldParent = self.parent - myIndex = self.parent.index(self) - if hasattr(replaceWith, "parent")\ - and replaceWith.parent is self.parent: - # We're replacing this element with one of its siblings. - index = replaceWith.parent.index(replaceWith) - if index and index < myIndex: - # Furthermore, it comes before this element. That - # means that when we extract it, the index of this - # element will change. - myIndex = myIndex - 1 - self.extract() - oldParent.insert(myIndex, replaceWith) - - def replaceWithChildren(self): - myParent = self.parent - myIndex = self.parent.index(self) - self.extract() - reversedChildren = list(self.contents) - reversedChildren.reverse() - for child in reversedChildren: - myParent.insert(myIndex, child) - - def extract(self): - """Destructively rips this element out of the tree.""" - if self.parent: - try: - del self.parent.contents[self.parent.index(self)] - except ValueError: - pass - - #Find the two elements that would be next to each other if - #this element (and any children) hadn't been parsed. Connect - #the two. - lastChild = self._lastRecursiveChild() - nextElement = lastChild.next - - if self.previous: - self.previous.next = nextElement - if nextElement: - nextElement.previous = self.previous - self.previous = None - lastChild.next = None - - self.parent = None - if self.previousSibling: - self.previousSibling.nextSibling = self.nextSibling - if self.nextSibling: - self.nextSibling.previousSibling = self.previousSibling - self.previousSibling = self.nextSibling = None - return self - - def _lastRecursiveChild(self): - "Finds the last element beneath this object to be parsed." - lastChild = self - while hasattr(lastChild, 'contents') and lastChild.contents: - lastChild = lastChild.contents[-1] - return lastChild - - def insert(self, position, newChild): - if isinstance(newChild, basestring) \ - and not isinstance(newChild, NavigableString): - newChild = NavigableString(newChild) - - position = min(position, len(self.contents)) - if hasattr(newChild, 'parent') and newChild.parent is not None: - # We're 'inserting' an element that's already one - # of this object's children. - if newChild.parent is self: - index = self.index(newChild) - if index > position: - # Furthermore we're moving it further down the - # list of this object's children. That means that - # when we extract this element, our target index - # will jump down one. - position = position - 1 - newChild.extract() - - newChild.parent = self - previousChild = None - if position == 0: - newChild.previousSibling = None - newChild.previous = self - else: - previousChild = self.contents[position-1] - newChild.previousSibling = previousChild - newChild.previousSibling.nextSibling = newChild - newChild.previous = previousChild._lastRecursiveChild() - if newChild.previous: - newChild.previous.next = newChild - - newChildsLastElement = newChild._lastRecursiveChild() - - if position >= len(self.contents): - newChild.nextSibling = None - - parent = self - parentsNextSibling = None - while not parentsNextSibling: - parentsNextSibling = parent.nextSibling - parent = parent.parent - if not parent: # This is the last element in the document. - break - if parentsNextSibling: - newChildsLastElement.next = parentsNextSibling - else: - newChildsLastElement.next = None - else: - nextChild = self.contents[position] - newChild.nextSibling = nextChild - if newChild.nextSibling: - newChild.nextSibling.previousSibling = newChild - newChildsLastElement.next = nextChild - - if newChildsLastElement.next: - newChildsLastElement.next.previous = newChildsLastElement - self.contents.insert(position, newChild) - - def append(self, tag): - """Appends the given tag to the contents of this tag.""" - self.insert(len(self.contents), tag) - - def findNext(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears after this Tag in the document.""" - return self._findOne(self.findAllNext, name, attrs, text, **kwargs) - - def findAllNext(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.nextGenerator, - **kwargs) - - def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears after this Tag in the document.""" - return self._findOne(self.findNextSiblings, name, attrs, text, - **kwargs) - - def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.nextSiblingGenerator, **kwargs) - fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x - - def findPrevious(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears before this Tag in the document.""" - return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) - - def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.previousGenerator, - **kwargs) - fetchPrevious = findAllPrevious # Compatibility with pre-3.x - - def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears before this Tag in the document.""" - return self._findOne(self.findPreviousSiblings, name, attrs, text, - **kwargs) - - def findPreviousSiblings(self, name=None, attrs={}, text=None, - limit=None, **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.previousSiblingGenerator, **kwargs) - fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x - - def findParent(self, name=None, attrs={}, **kwargs): - """Returns the closest parent of this Tag that matches the given - criteria.""" - # NOTE: We can't use _findOne because findParents takes a different - # set of arguments. - r = None - l = self.findParents(name, attrs, 1) - if l: - r = l[0] - return r - - def findParents(self, name=None, attrs={}, limit=None, **kwargs): - """Returns the parents of this Tag that match the given - criteria.""" - - return self._findAll(name, attrs, None, limit, self.parentGenerator, - **kwargs) - fetchParents = findParents # Compatibility with pre-3.x - - #These methods do the real heavy lifting. - - def _findOne(self, method, name, attrs, text, **kwargs): - r = None - l = method(name, attrs, text, 1, **kwargs) - if l: - r = l[0] - return r - - def _findAll(self, name, attrs, text, limit, generator, **kwargs): - "Iterates over a generator looking for things that match." - - if isinstance(name, SoupStrainer): - strainer = name - # (Possibly) special case some findAll*(...) searches - elif text is None and not limit and not attrs and not kwargs: - # findAll*(True) - if name is True: - return [element for element in generator() - if isinstance(element, Tag)] - # findAll*('tag-name') - elif isinstance(name, basestring): - return [element for element in generator() - if isinstance(element, Tag) and - element.name == name] - else: - strainer = SoupStrainer(name, attrs, text, **kwargs) - # Build a SoupStrainer - else: - strainer = SoupStrainer(name, attrs, text, **kwargs) - results = ResultSet(strainer) - g = generator() - while True: - try: - i = g.next() - except StopIteration: - break - if i: - found = strainer.search(i) - if found: - results.append(found) - if limit and len(results) >= limit: - break - return results - - #These Generators can be used to navigate starting from both - #NavigableStrings and Tags. - def nextGenerator(self): - i = self - while i is not None: - i = i.next - yield i - - def nextSiblingGenerator(self): - i = self - while i is not None: - i = i.nextSibling - yield i - - def previousGenerator(self): - i = self - while i is not None: - i = i.previous - yield i - - def previousSiblingGenerator(self): - i = self - while i is not None: - i = i.previousSibling - yield i - - def parentGenerator(self): - i = self - while i is not None: - i = i.parent - yield i - - # Utility methods - def substituteEncoding(self, str, encoding=None): - encoding = encoding or "utf-8" - return str.replace("%SOUP-ENCODING%", encoding) - - def toEncoding(self, s, encoding=None): - """Encodes an object to a string in some encoding, or to Unicode. - .""" - if isinstance(s, unicode): - if encoding: - s = s.encode(encoding) - elif isinstance(s, str): - if encoding: - s = s.encode(encoding) - else: - s = unicode(s) - else: - if encoding: - s = self.toEncoding(str(s), encoding) - else: - s = unicode(s) - return s - -class NavigableString(unicode, PageElement): - - def __new__(cls, value): - """Create a new NavigableString. - - When unpickling a NavigableString, this method is called with - the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be - passed in to the superclass's __new__ or the superclass won't know - how to handle non-ASCII characters. - """ - if isinstance(value, unicode): - return unicode.__new__(cls, value) - return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) - - def __getnewargs__(self): - return (NavigableString.__str__(self),) - - def __getattr__(self, attr): - """text.string gives you text. This is for backwards - compatibility for Navigable*String, but for CData* it lets you - get the string without the CData wrapper.""" - if attr == 'string': - return self - else: - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) - - def __unicode__(self): - return str(self).decode(DEFAULT_OUTPUT_ENCODING) - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - if encoding: - return self.encode(encoding) - else: - return self - -class CData(NavigableString): - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "<![CDATA[%s]]>" % NavigableString.__str__(self, encoding) - -class ProcessingInstruction(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - output = self - if "%SOUP-ENCODING%" in output: - output = self.substituteEncoding(output, encoding) - return "<?%s?>" % self.toEncoding(output, encoding) - -class Comment(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "<!--%s-->" % NavigableString.__str__(self, encoding) - -class Declaration(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "<!%s>" % NavigableString.__str__(self, encoding) - -class Tag(PageElement): - - """Represents a found HTML tag with its attributes and contents.""" - - def _invert(h): - "Cheap function to invert a hash." - i = {} - for k,v in h.items(): - i[v] = k - return i - - XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'", - "quot" : '"', - "amp" : "&", - "lt" : "<", - "gt" : ">" } - - XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS) - - def _convertEntities(self, match): - """Used in a call to re.sub to replace HTML, XML, and numeric - entities with the appropriate Unicode characters. If HTML - entities are being converted, any unrecognized entities are - escaped.""" - x = match.group(1) - if self.convertHTMLEntities and x in name2codepoint: - return unichr(name2codepoint[x]) - elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS: - if self.convertXMLEntities: - return self.XML_ENTITIES_TO_SPECIAL_CHARS[x] - else: - return u'&%s;' % x - elif len(x) > 0 and x[0] == '#': - # Handle numeric entities - if len(x) > 1 and x[1] == 'x': - return unichr(int(x[2:], 16)) - else: - return unichr(int(x[1:])) - - elif self.escapeUnrecognizedEntities: - return u'&%s;' % x - else: - return u'&%s;' % x - - def __init__(self, parser, name, attrs=None, parent=None, - previous=None): - "Basic constructor." - - # We don't actually store the parser object: that lets extracted - # chunks be garbage-collected - self.parserClass = parser.__class__ - self.isSelfClosing = parser.isSelfClosingTag(name) - self.name = name - if attrs is None: - attrs = [] - self.attrs = attrs - self.contents = [] - self.setup(parent, previous) - self.hidden = False - self.containsSubstitutions = False - self.convertHTMLEntities = parser.convertHTMLEntities - self.convertXMLEntities = parser.convertXMLEntities - self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities - - # Convert any HTML, XML, or numeric entities in the attribute values. - convert = lambda(k, val): (k, - re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);", - self._convertEntities, - val)) - self.attrs = map(convert, self.attrs) - - def getString(self): - if (len(self.contents) == 1 - and isinstance(self.contents[0], NavigableString)): - return self.contents[0] - - def setString(self, string): - """Replace the contents of the tag with a string""" - self.clear() - self.append(string) - - string = property(getString, setString) - - def getText(self, separator=u""): - if not len(self.contents): - return u"" - stopNode = self._lastRecursiveChild().next - strings = [] - current = self.contents[0] - while current is not stopNode: - if isinstance(current, NavigableString): - strings.append(current.strip()) - current = current.next - return separator.join(strings) - - text = property(getText) - - def get(self, key, default=None): - """Returns the value of the 'key' attribute for the tag, or - the value given for 'default' if it doesn't have that - attribute.""" - return self._getAttrMap().get(key, default) - - def clear(self): - """Extract all children.""" - for child in self.contents[:]: - child.extract() - - def index(self, element): - for i, child in enumerate(self.contents): - if child is element: - return i - raise ValueError("Tag.index: element not in tag") - - def has_key(self, key): - return self._getAttrMap().has_key(key) - - def __getitem__(self, key): - """tag[key] returns the value of the 'key' attribute for the tag, - and throws an exception if it's not there.""" - return self._getAttrMap()[key] - - def __iter__(self): - "Iterating over a tag iterates over its contents." - return iter(self.contents) - - def __len__(self): - "The length of a tag is the length of its list of contents." - return len(self.contents) - - def __contains__(self, x): - return x in self.contents - - def __nonzero__(self): - "A tag is non-None even if it has no contents." - return True - - def __setitem__(self, key, value): - """Setting tag[key] sets the value of the 'key' attribute for the - tag.""" - self._getAttrMap() - self.attrMap[key] = value - found = False - for i in range(0, len(self.attrs)): - if self.attrs[i][0] == key: - self.attrs[i] = (key, value) - found = True - if not found: - self.attrs.append((key, value)) - self._getAttrMap()[key] = value - - def __delitem__(self, key): - "Deleting tag[key] deletes all 'key' attributes for the tag." - for item in self.attrs: - if item[0] == key: - self.attrs.remove(item) - #We don't break because bad HTML can define the same - #attribute multiple times. - self._getAttrMap() - if self.attrMap.has_key(key): - del self.attrMap[key] - - def __call__(self, *args, **kwargs): - """Calling a tag like a function is the same as calling its - findAll() method. Eg. tag('a') returns a list of all the A tags - found within this tag.""" - return apply(self.findAll, args, kwargs) - - def __getattr__(self, tag): - #print "Getattr %s.%s" % (self.__class__, tag) - if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: - return self.find(tag[:-3]) - elif tag.find('__') != 0: - return self.find(tag) - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag) - - def __eq__(self, other): - """Returns true iff this tag has the same name, the same attributes, - and the same contents (recursively) as the given tag. - - NOTE: right now this will return false if two tags have the - same attributes in a different order. Should this be fixed?""" - if other is self: - return True - if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): - return False - for i in range(0, len(self.contents)): - if self.contents[i] != other.contents[i]: - return False - return True - - def __ne__(self, other): - """Returns true iff this tag is not identical to the other tag, - as defined in __eq__.""" - return not self == other - - def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): - """Renders this tag as a string.""" - return self.__str__(encoding) - - def __unicode__(self): - return self.__str__(None) - - BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" - + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" - + ")") - - def _sub_entity(self, x): - """Used with a regular expression to substitute the - appropriate XML entity for an XML special character.""" - return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";" - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - """Returns a string or Unicode representation of this tag and - its contents. To get Unicode, pass None for encoding. - - NOTE: since Python's HTML parser consumes whitespace, this - method is not certain to reproduce the whitespace present in - the original string.""" - - encodedName = self.toEncoding(self.name, encoding) - - attrs = [] - if self.attrs: - for key, val in self.attrs: - fmt = '%s="%s"' - if isinstance(val, basestring): - if self.containsSubstitutions and '%SOUP-ENCODING%' in val: - val = self.substituteEncoding(val, encoding) - - # The attribute value either: - # - # * Contains no embedded double quotes or single quotes. - # No problem: we enclose it in double quotes. - # * Contains embedded single quotes. No problem: - # double quotes work here too. - # * Contains embedded double quotes. No problem: - # we enclose it in single quotes. - # * Embeds both single _and_ double quotes. This - # can't happen naturally, but it can happen if - # you modify an attribute value after parsing - # the document. Now we have a bit of a - # problem. We solve it by enclosing the - # attribute in single quotes, and escaping any - # embedded single quotes to XML entities. - if '"' in val: - fmt = "%s='%s'" - if "'" in val: - # TODO: replace with apos when - # appropriate. - val = val.replace("'", "&squot;") - - # Now we're okay w/r/t quotes. But the attribute - # value might also contain angle brackets, or - # ampersands that aren't part of entities. We need - # to escape those to XML entities too. - val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val) - - attrs.append(fmt % (self.toEncoding(key, encoding), - self.toEncoding(val, encoding))) - close = '' - closeTag = '' - if self.isSelfClosing: - close = ' /' - else: - closeTag = '</%s>' % encodedName - - indentTag, indentContents = 0, 0 - if prettyPrint: - indentTag = indentLevel - space = (' ' * (indentTag-1)) - indentContents = indentTag + 1 - contents = self.renderContents(encoding, prettyPrint, indentContents) - if self.hidden: - s = contents - else: - s = [] - attributeString = '' - if attrs: - attributeString = ' ' + ' '.join(attrs) - if prettyPrint: - s.append(space) - s.append('<%s%s%s>' % (encodedName, attributeString, close)) - if prettyPrint: - s.append("\n") - s.append(contents) - if prettyPrint and contents and contents[-1] != "\n": - s.append("\n") - if prettyPrint and closeTag: - s.append(space) - s.append(closeTag) - if prettyPrint and closeTag and self.nextSibling: - s.append("\n") - s = ''.join(s) - return s - - def decompose(self): - """Recursively destroys the contents of this tree.""" - self.extract() - if len(self.contents) == 0: - return - current = self.contents[0] - while current is not None: - next = current.next - if isinstance(current, Tag): - del current.contents[:] - current.parent = None - current.previous = None - current.previousSibling = None - current.next = None - current.nextSibling = None - current = next - - def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): - return self.__str__(encoding, True) - - def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - """Renders the contents of this tag as a string in the given - encoding. If encoding is None, returns a Unicode string..""" - s=[] - for c in self: - text = None - if isinstance(c, NavigableString): - text = c.__str__(encoding) - elif isinstance(c, Tag): - s.append(c.__str__(encoding, prettyPrint, indentLevel)) - if text and prettyPrint: - text = text.strip() - if text: - if prettyPrint: - s.append(" " * (indentLevel-1)) - s.append(text) - if prettyPrint: - s.append("\n") - return ''.join(s) - - #Soup methods - - def find(self, name=None, attrs={}, recursive=True, text=None, - **kwargs): - """Return only the first child of this Tag matching the given - criteria.""" - r = None - l = self.findAll(name, attrs, recursive, text, 1, **kwargs) - if l: - r = l[0] - return r - findChild = find - - def findAll(self, name=None, attrs={}, recursive=True, text=None, - limit=None, **kwargs): - """Extracts a list of Tag objects that match the given - criteria. You can specify the name of the Tag and any - attributes you want the Tag to have. - - The value of a key-value pair in the 'attrs' map can be a - string, a list of strings, a regular expression object, or a - callable that takes a string and returns whether or not the - string matches for some custom definition of 'matches'. The - same is true of the tag name.""" - generator = self.recursiveChildGenerator - if not recursive: - generator = self.childGenerator - return self._findAll(name, attrs, text, limit, generator, **kwargs) - findChildren = findAll - - # Pre-3.x compatibility methods - first = find - fetch = findAll - - def fetchText(self, text=None, recursive=True, limit=None): - return self.findAll(text=text, recursive=recursive, limit=limit) - - def firstText(self, text=None, recursive=True): - return self.find(text=text, recursive=recursive) - - #Private methods - - def _getAttrMap(self): - """Initializes a map representation of this tag's attributes, - if not already initialized.""" - if not getattr(self, 'attrMap'): - self.attrMap = {} - for (key, value) in self.attrs: - self.attrMap[key] = value - return self.attrMap - - #Generator methods - def childGenerator(self): - # Just use the iterator from the contents - return iter(self.contents) - - def recursiveChildGenerator(self): - if not len(self.contents): - raise StopIteration - stopNode = self._lastRecursiveChild().next - current = self.contents[0] - while current is not stopNode: - yield current - current = current.next - - -# Next, a couple classes to represent queries and their results. -class SoupStrainer: - """Encapsulates a number of ways of matching a markup element (tag or - text).""" - - def __init__(self, name=None, attrs={}, text=None, **kwargs): - self.name = name - if isinstance(attrs, basestring): - kwargs['class'] = _match_css_class(attrs) - attrs = None - if kwargs: - if attrs: - attrs = attrs.copy() - attrs.update(kwargs) - else: - attrs = kwargs - self.attrs = attrs - self.text = text - - def __str__(self): - if self.text: - return self.text - else: - return "%s|%s" % (self.name, self.attrs) - - def searchTag(self, markupName=None, markupAttrs={}): - found = None - markup = None - if isinstance(markupName, Tag): - markup = markupName - markupAttrs = markup - callFunctionWithTagData = callable(self.name) \ - and not isinstance(markupName, Tag) - - if (not self.name) \ - or callFunctionWithTagData \ - or (markup and self._matches(markup, self.name)) \ - or (not markup and self._matches(markupName, self.name)): - if callFunctionWithTagData: - match = self.name(markupName, markupAttrs) - else: - match = True - markupAttrMap = None - for attr, matchAgainst in self.attrs.items(): - if not markupAttrMap: - if hasattr(markupAttrs, 'get'): - markupAttrMap = markupAttrs - else: - markupAttrMap = {} - for k,v in markupAttrs: - markupAttrMap[k] = v - attrValue = markupAttrMap.get(attr) - if not self._matches(attrValue, matchAgainst): - match = False - break - if match: - if markup: - found = markup - else: - found = markupName - return found - - def search(self, markup): - #print 'looking for %s in %s' % (self, markup) - found = None - # If given a list of items, scan it for a text element that - # matches. - if hasattr(markup, "__iter__") \ - and not isinstance(markup, Tag): - for element in markup: - if isinstance(element, NavigableString) \ - and self.search(element): - found = element - break - # If it's a Tag, make sure its name or attributes match. - # Don't bother with Tags if we're searching for text. - elif isinstance(markup, Tag): - if not self.text: - found = self.searchTag(markup) - # If it's text, make sure the text matches. - elif isinstance(markup, NavigableString) or \ - isinstance(markup, basestring): - if self._matches(markup, self.text): - found = markup - else: - raise Exception, "I don't know how to match against a %s" \ - % markup.__class__ - return found - - def _matches(self, markup, matchAgainst): - #print "Matching %s against %s" % (markup, matchAgainst) - result = False - if matchAgainst is True: - result = markup is not None - elif callable(matchAgainst): - result = matchAgainst(markup) - else: - #Custom match methods take the tag as an argument, but all - #other ways of matching match the tag name as a string. - if isinstance(markup, Tag): - markup = markup.name - if markup and not isinstance(markup, basestring): - markup = unicode(markup) - #Now we know that chunk is either a string, or None. - if hasattr(matchAgainst, 'match'): - # It's a regexp object. - result = markup and matchAgainst.search(markup) - elif hasattr(matchAgainst, '__iter__'): # list-like - result = markup in matchAgainst - elif hasattr(matchAgainst, 'items'): - result = markup.has_key(matchAgainst) - elif matchAgainst and isinstance(markup, basestring): - if isinstance(markup, unicode): - matchAgainst = unicode(matchAgainst) - else: - matchAgainst = str(matchAgainst) - - if not result: - result = matchAgainst == markup - return result - -class ResultSet(list): - """A ResultSet is just a list that keeps track of the SoupStrainer - that created it.""" - def __init__(self, source): - list.__init__([]) - self.source = source - -# Now, some helper functions. - -def buildTagMap(default, *args): - """Turns a list of maps, lists, or scalars into a single map. - Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and - NESTING_RESET_TAGS maps out of lists and partial maps.""" - built = {} - for portion in args: - if hasattr(portion, 'items'): - #It's a map. Merge it. - for k,v in portion.items(): - built[k] = v - elif hasattr(portion, '__iter__'): # is a list - #It's a list. Map each item to the default. - for k in portion: - built[k] = default - else: - #It's a scalar. Map it to the default. - built[portion] = default - return built - -# Now, the parser classes. - -class BeautifulStoneSoup(Tag, SGMLParser): - - """This class contains the basic parser and search code. It defines - a parser that knows nothing about tag behavior except for the - following: - - You can't close a tag without closing all the tags it encloses. - That is, "<foo><bar></foo>" actually means - "<foo><bar></bar></foo>". - - [Another possible explanation is "<foo><bar /></foo>", but since - this class defines no SELF_CLOSING_TAGS, it will never use that - explanation.] - - This class is useful for parsing XML or made-up markup languages, - or when BeautifulSoup makes an assumption counter to what you were - expecting.""" - - SELF_CLOSING_TAGS = {} - NESTABLE_TAGS = {} - RESET_NESTING_TAGS = {} - QUOTE_TAGS = {} - PRESERVE_WHITESPACE_TAGS = [] - - MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), - lambda x: x.group(1) + ' />'), - (re.compile('<!\s+([^<>]*)>'), - lambda x: '<!' + x.group(1) + '>') - ] - - ROOT_TAG_NAME = u'[document]' - - HTML_ENTITIES = "html" - XML_ENTITIES = "xml" - XHTML_ENTITIES = "xhtml" - # TODO: This only exists for backwards-compatibility - ALL_ENTITIES = XHTML_ENTITIES - - # Used when determining whether a text node is all whitespace and - # can be replaced with a single space. A text node that contains - # fancy Unicode spaces (usually non-breaking) should be left - # alone. - STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, } - - def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, - markupMassage=True, smartQuotesTo=XML_ENTITIES, - convertEntities=None, selfClosingTags=None, isHTML=False): - """The Soup object is initialized as the 'root tag', and the - provided markup (which can be a string or a file-like object) - is fed into the underlying parser. - - sgmllib will process most bad HTML, and the BeautifulSoup - class has some tricks for dealing with some HTML that kills - sgmllib, but Beautiful Soup can nonetheless choke or lose data - if your data uses self-closing tags or declarations - incorrectly. - - By default, Beautiful Soup uses regexes to sanitize input, - avoiding the vast majority of these problems. If the problems - don't apply to you, pass in False for markupMassage, and - you'll get better performance. - - The default parser massage techniques fix the two most common - instances of invalid HTML that choke sgmllib: - - <br/> (No space between name of closing tag and tag close) - <! --Comment--> (Extraneous whitespace in declaration) - - You can pass in a custom list of (RE object, replace method) - tuples to get Beautiful Soup to scrub your input the way you - want.""" - - self.parseOnlyThese = parseOnlyThese - self.fromEncoding = fromEncoding - self.smartQuotesTo = smartQuotesTo - self.convertEntities = convertEntities - # Set the rules for how we'll deal with the entities we - # encounter - if self.convertEntities: - # It doesn't make sense to convert encoded characters to - # entities even while you're converting entities to Unicode. - # Just convert it all to Unicode. - self.smartQuotesTo = None - if convertEntities == self.HTML_ENTITIES: - self.convertXMLEntities = False - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = True - elif convertEntities == self.XHTML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = False - elif convertEntities == self.XML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - else: - self.convertXMLEntities = False - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - - self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) - SGMLParser.__init__(self) - - if hasattr(markup, 'read'): # It's a file-type object. - markup = markup.read() - self.markup = markup - self.markupMassage = markupMassage - try: - self._feed(isHTML=isHTML) - except StopParsing: - pass - self.markup = None # The markup can now be GCed - - def convert_charref(self, name): - """This method fixes a bug in Python's SGMLParser.""" - try: - n = int(name) - except ValueError: - return - if not 0 <= n <= 127 : # ASCII ends at 127, not 255 - return - return self.convert_codepoint(n) - - def _feed(self, inDocumentEncoding=None, isHTML=False): - # Convert the document to Unicode. - markup = self.markup - if isinstance(markup, unicode): - if not hasattr(self, 'originalEncoding'): - self.originalEncoding = None - else: - dammit = UnicodeDammit\ - (markup, [self.fromEncoding, inDocumentEncoding], - smartQuotesTo=self.smartQuotesTo, isHTML=isHTML) - markup = dammit.unicode - self.originalEncoding = dammit.originalEncoding - self.declaredHTMLEncoding = dammit.declaredHTMLEncoding - if markup: - if self.markupMassage: - if not hasattr(self.markupMassage, "__iter__"): - self.markupMassage = self.MARKUP_MASSAGE - for fix, m in self.markupMassage: - markup = fix.sub(m, markup) - # TODO: We get rid of markupMassage so that the - # soup object can be deepcopied later on. Some - # Python installations can't copy regexes. If anyone - # was relying on the existence of markupMassage, this - # might cause problems. - del(self.markupMassage) - self.reset() - - SGMLParser.feed(self, markup) - # Close out any unfinished strings and close all the open tags. - self.endData() - while self.currentTag.name != self.ROOT_TAG_NAME: - self.popTag() - - def __getattr__(self, methodName): - """This method routes method call requests to either the SGMLParser - superclass or the Tag superclass, depending on the method name.""" - #print "__getattr__ called on %s.%s" % (self.__class__, methodName) - - if methodName.startswith('start_') or methodName.startswith('end_') \ - or methodName.startswith('do_'): - return SGMLParser.__getattr__(self, methodName) - elif not methodName.startswith('__'): - return Tag.__getattr__(self, methodName) - else: - raise AttributeError - - def isSelfClosingTag(self, name): - """Returns true iff the given string is the name of a - self-closing tag according to this parser.""" - return self.SELF_CLOSING_TAGS.has_key(name) \ - or self.instanceSelfClosingTags.has_key(name) - - def reset(self): - Tag.__init__(self, self, self.ROOT_TAG_NAME) - self.hidden = 1 - SGMLParser.reset(self) - self.currentData = [] - self.currentTag = None - self.tagStack = [] - self.quoteStack = [] - self.pushTag(self) - - def popTag(self): - tag = self.tagStack.pop() - - #print "Pop", tag.name - if self.tagStack: - self.currentTag = self.tagStack[-1] - return self.currentTag - - def pushTag(self, tag): - #print "Push", tag.name - if self.currentTag: - self.currentTag.contents.append(tag) - self.tagStack.append(tag) - self.currentTag = self.tagStack[-1] - - def endData(self, containerClass=NavigableString): - if self.currentData: - currentData = u''.join(self.currentData) - if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and - not set([tag.name for tag in self.tagStack]).intersection( - self.PRESERVE_WHITESPACE_TAGS)): - if '\n' in currentData: - currentData = '\n' - else: - currentData = ' ' - self.currentData = [] - if self.parseOnlyThese and len(self.tagStack) <= 1 and \ - (not self.parseOnlyThese.text or \ - not self.parseOnlyThese.search(currentData)): - return - o = containerClass(currentData) - o.setup(self.currentTag, self.previous) - if self.previous: - self.previous.next = o - self.previous = o - self.currentTag.contents.append(o) - - - def _popToTag(self, name, inclusivePop=True): - """Pops the tag stack up to and including the most recent - instance of the given tag. If inclusivePop is false, pops the tag - stack up to but *not* including the most recent instqance of - the given tag.""" - #print "Popping to %s" % name - if name == self.ROOT_TAG_NAME: - return - - numPops = 0 - mostRecentTag = None - for i in range(len(self.tagStack)-1, 0, -1): - if name == self.tagStack[i].name: - numPops = len(self.tagStack)-i - break - if not inclusivePop: - numPops = numPops - 1 - - for i in range(0, numPops): - mostRecentTag = self.popTag() - return mostRecentTag - - def _smartPop(self, name): - - """We need to pop up to the previous tag of this type, unless - one of this tag's nesting reset triggers comes between this - tag and the previous tag of this type, OR unless this tag is a - generic nesting trigger and another generic nesting trigger - comes between this tag and the previous tag of this type. - - Examples: - <p>Foo<b>Bar *<p>* should pop to 'p', not 'b'. - <p>Foo<table>Bar *<p>* should pop to 'table', not 'p'. - <p>Foo<table><tr>Bar *<p>* should pop to 'tr', not 'p'. - - <li><ul><li> *<li>* should pop to 'ul', not the first 'li'. - <tr><table><tr> *<tr>* should pop to 'table', not the first 'tr' - <td><tr><td> *<td>* should pop to 'tr', not the first 'td' - """ - - nestingResetTriggers = self.NESTABLE_TAGS.get(name) - isNestable = nestingResetTriggers is not None - isResetNesting = self.RESET_NESTING_TAGS.has_key(name) - popTo = None - inclusive = True - for i in range(len(self.tagStack)-1, 0, -1): - p = self.tagStack[i] - if (not p or p.name == name) and not isNestable: - #Non-nestable tags get popped to the top or to their - #last occurance. - popTo = name - break - if (nestingResetTriggers is not None - and p.name in nestingResetTriggers) \ - or (nestingResetTriggers is None and isResetNesting - and self.RESET_NESTING_TAGS.has_key(p.name)): - - #If we encounter one of the nesting reset triggers - #peculiar to this tag, or we encounter another tag - #that causes nesting to reset, pop up to but not - #including that tag. - popTo = p.name - inclusive = False - break - p = p.parent - if popTo: - self._popToTag(popTo, inclusive) - - def unknown_starttag(self, name, attrs, selfClosing=0): - #print "Start tag %s: %s" % (name, attrs) - if self.quoteStack: - #This is not a real tag. - #print "<%s> is not real!" % name - attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs]) - self.handle_data('<%s%s>' % (name, attrs)) - return - self.endData() - - if not self.isSelfClosingTag(name) and not selfClosing: - self._smartPop(name) - - if self.parseOnlyThese and len(self.tagStack) <= 1 \ - and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): - return - - tag = Tag(self, name, attrs, self.currentTag, self.previous) - if self.previous: - self.previous.next = tag - self.previous = tag - self.pushTag(tag) - if selfClosing or self.isSelfClosingTag(name): - self.popTag() - if name in self.QUOTE_TAGS: - #print "Beginning quote (%s)" % name - self.quoteStack.append(name) - self.literal = 1 - return tag - - def unknown_endtag(self, name): - #print "End tag %s" % name - if self.quoteStack and self.quoteStack[-1] != name: - #This is not a real end tag. - #print "</%s> is not real!" % name - self.handle_data('</%s>' % name) - return - self.endData() - self._popToTag(name) - if self.quoteStack and self.quoteStack[-1] == name: - self.quoteStack.pop() - self.literal = (len(self.quoteStack) > 0) - - def handle_data(self, data): - self.currentData.append(data) - - def _toStringSubclass(self, text, subclass): - """Adds a certain piece of text to the tree as a NavigableString - subclass.""" - self.endData() - self.handle_data(text) - self.endData(subclass) - - def handle_pi(self, text): - """Handle a processing instruction as a ProcessingInstruction - object, possibly one with a %SOUP-ENCODING% slot into which an - encoding will be plugged later.""" - if text[:3] == "xml": - text = u"xml version='1.0' encoding='%SOUP-ENCODING%'" - self._toStringSubclass(text, ProcessingInstruction) - - def handle_comment(self, text): - "Handle comments as Comment objects." - self._toStringSubclass(text, Comment) - - def handle_charref(self, ref): - "Handle character references as data." - if self.convertEntities: - data = unichr(int(ref)) - else: - data = '&#%s;' % ref - self.handle_data(data) - - def handle_entityref(self, ref): - """Handle entity references as data, possibly converting known - HTML and/or XML entity references to the corresponding Unicode - characters.""" - data = None - if self.convertHTMLEntities: - try: - data = unichr(name2codepoint[ref]) - except KeyError: - pass - - if not data and self.convertXMLEntities: - data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref) - - if not data and self.convertHTMLEntities and \ - not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref): - # TODO: We've got a problem here. We're told this is - # an entity reference, but it's not an XML entity - # reference or an HTML entity reference. Nonetheless, - # the logical thing to do is to pass it through as an - # unrecognized entity reference. - # - # Except: when the input is "&carol;" this function - # will be called with input "carol". When the input is - # "AT&T", this function will be called with input - # "T". We have no way of knowing whether a semicolon - # was present originally, so we don't know whether - # this is an unknown entity or just a misplaced - # ampersand. - # - # The more common case is a misplaced ampersand, so I - # escape the ampersand and omit the trailing semicolon. - data = "&%s" % ref - if not data: - # This case is different from the one above, because we - # haven't already gone through a supposedly comprehensive - # mapping of entities to Unicode characters. We might not - # have gone through any mapping at all. So the chances are - # very high that this is a real entity, and not a - # misplaced ampersand. - data = "&%s;" % ref - self.handle_data(data) - - def handle_decl(self, data): - "Handle DOCTYPEs and the like as Declaration objects." - self._toStringSubclass(data, Declaration) - - def parse_declaration(self, i): - """Treat a bogus SGML declaration as raw data. Treat a CDATA - declaration as a CData object.""" - j = None - if self.rawdata[i:i+9] == '<![CDATA[': - k = self.rawdata.find(']]>', i) - if k == -1: - k = len(self.rawdata) - data = self.rawdata[i+9:k] - j = k+3 - self._toStringSubclass(data, CData) - else: - try: - j = SGMLParser.parse_declaration(self, i) - except SGMLParseError: - toHandle = self.rawdata[i:] - self.handle_data(toHandle) - j = i + len(toHandle) - return j - -class BeautifulSoup(BeautifulStoneSoup): - - """This parser knows the following facts about HTML: - - * Some tags have no closing tag and should be interpreted as being - closed as soon as they are encountered. - - * The text inside some tags (ie. 'script') may contain tags which - are not really part of the document and which should be parsed - as text, not tags. If you want to parse the text as tags, you can - always fetch it and parse it explicitly. - - * Tag nesting rules: - - Most tags can't be nested at all. For instance, the occurance of - a <p> tag should implicitly close the previous <p> tag. - - <p>Para1<p>Para2 - should be transformed into: - <p>Para1</p><p>Para2 - - Some tags can be nested arbitrarily. For instance, the occurance - of a <blockquote> tag should _not_ implicitly close the previous - <blockquote> tag. - - Alice said: <blockquote>Bob said: <blockquote>Blah - should NOT be transformed into: - Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah - - Some tags can be nested, but the nesting is reset by the - interposition of other tags. For instance, a <tr> tag should - implicitly close the previous <tr> tag within the same <table>, - but not close a <tr> tag in another table. - - <table><tr>Blah<tr>Blah - should be transformed into: - <table><tr>Blah</tr><tr>Blah - but, - <tr>Blah<table><tr>Blah - should NOT be transformed into - <tr>Blah<table></tr><tr>Blah - - Differing assumptions about tag nesting rules are a major source - of problems with the BeautifulSoup class. If BeautifulSoup is not - treating as nestable a tag your page author treats as nestable, - try ICantBelieveItsBeautifulSoup, MinimalSoup, or - BeautifulStoneSoup before writing your own subclass.""" - - def __init__(self, *args, **kwargs): - if not kwargs.has_key('smartQuotesTo'): - kwargs['smartQuotesTo'] = self.HTML_ENTITIES - kwargs['isHTML'] = True - BeautifulStoneSoup.__init__(self, *args, **kwargs) - - SELF_CLOSING_TAGS = buildTagMap(None, - ('br' , 'hr', 'input', 'img', 'meta', - 'spacer', 'link', 'frame', 'base', 'col')) - - PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea']) - - QUOTE_TAGS = {'script' : None, 'textarea' : None} - - #According to the HTML standard, each of these inline tags can - #contain another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', - 'center') - - #According to the HTML standard, these block tags can contain - #another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del') - - #Lists can contain other lists, but there are restrictions. - NESTABLE_LIST_TAGS = { 'ol' : [], - 'ul' : [], - 'li' : ['ul', 'ol'], - 'dl' : [], - 'dd' : ['dl'], - 'dt' : ['dl'] } - - #Tables can contain other tables, but there are restrictions. - NESTABLE_TABLE_TAGS = {'table' : [], - 'tr' : ['table', 'tbody', 'tfoot', 'thead'], - 'td' : ['tr'], - 'th' : ['tr'], - 'thead' : ['table'], - 'tbody' : ['table'], - 'tfoot' : ['table'], - } - - NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre') - - #If one of these tags is encountered, all tags up to the next tag of - #this type are popped. - RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', - NON_NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, - NESTABLE_TABLE_TAGS) - - NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) - - # Used to detect the charset in a META tag; see start_meta - CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) - - def start_meta(self, attrs): - """Beautiful Soup can detect a charset included in a META tag, - try to convert the document to that charset, and re-parse the - document from the beginning.""" - httpEquiv = None - contentType = None - contentTypeIndex = None - tagNeedsEncodingSubstitution = False - - for i in range(0, len(attrs)): - key, value = attrs[i] - key = key.lower() - if key == 'http-equiv': - httpEquiv = value - elif key == 'content': - contentType = value - contentTypeIndex = i - - if httpEquiv and contentType: # It's an interesting meta tag. - match = self.CHARSET_RE.search(contentType) - if match: - if (self.declaredHTMLEncoding is not None or - self.originalEncoding == self.fromEncoding): - # An HTML encoding was sniffed while converting - # the document to Unicode, or an HTML encoding was - # sniffed during a previous pass through the - # document, or an encoding was specified - # explicitly and it worked. Rewrite the meta tag. - def rewrite(match): - return match.group(1) + "%SOUP-ENCODING%" - newAttr = self.CHARSET_RE.sub(rewrite, contentType) - attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], - newAttr) - tagNeedsEncodingSubstitution = True - else: - # This is our first pass through the document. - # Go through it again with the encoding information. - newCharset = match.group(3) - if newCharset and newCharset != self.originalEncoding: - self.declaredHTMLEncoding = newCharset - self._feed(self.declaredHTMLEncoding) - raise StopParsing - pass - tag = self.unknown_starttag("meta", attrs) - if tag and tagNeedsEncodingSubstitution: - tag.containsSubstitutions = True - -class StopParsing(Exception): - pass - -class ICantBelieveItsBeautifulSoup(BeautifulSoup): - - """The BeautifulSoup class is oriented towards skipping over - common HTML errors like unclosed tags. However, sometimes it makes - errors of its own. For instance, consider this fragment: - - <b>Foo<b>Bar</b></b> - - This is perfectly valid (if bizarre) HTML. However, the - BeautifulSoup class will implicitly close the first b tag when it - encounters the second 'b'. It will think the author wrote - "<b>Foo<b>Bar", and didn't close the first 'b' tag, because - there's no real-world reason to bold something that's already - bold. When it encounters '</b></b>' it will close two more 'b' - tags, for a grand total of three tags closed instead of two. This - can throw off the rest of your document structure. The same is - true of a number of other tags, listed below. - - It's much more common for someone to forget to close a 'b' tag - than to actually use nested 'b' tags, and the BeautifulSoup class - handles the common case. This class handles the not-co-common - case: where you can't believe someone wrote what they did, but - it's valid HTML and BeautifulSoup screwed up by assuming it - wouldn't be.""" - - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ - ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', - 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', - 'big') - - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript',) - - NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) - -class MinimalSoup(BeautifulSoup): - """The MinimalSoup class is for parsing HTML that contains - pathologically bad markup. It makes no assumptions about tag - nesting, but it does know which tags are self-closing, that - <script> tags contain Javascript and should not be parsed, that - META tags may contain encoding information, and so on. - - This also makes it better for subclassing than BeautifulStoneSoup - or BeautifulSoup.""" - - RESET_NESTING_TAGS = buildTagMap('noscript') - NESTABLE_TAGS = {} - -class BeautifulSOAP(BeautifulStoneSoup): - """This class will push a tag with only a single string child into - the tag's parent as an attribute. The attribute's name is the tag - name, and the value is the string child. An example should give - the flavor of the change: - - <foo><bar>baz</bar></foo> - => - <foo bar="baz"><bar>baz</bar></foo> - - You can then access fooTag['bar'] instead of fooTag.barTag.string. - - This is, of course, useful for scraping structures that tend to - use subelements instead of attributes, such as SOAP messages. Note - that it modifies its input, so don't print the modified version - out. - - I'm not sure how many people really want to use this class; let me - know if you do. Mainly I like the name.""" - - def popTag(self): - if len(self.tagStack) > 1: - tag = self.tagStack[-1] - parent = self.tagStack[-2] - parent._getAttrMap() - if (isinstance(tag, Tag) and len(tag.contents) == 1 and - isinstance(tag.contents[0], NavigableString) and - not parent.attrMap.has_key(tag.name)): - parent[tag.name] = tag.contents[0] - BeautifulStoneSoup.popTag(self) - -#Enterprise class names! It has come to our attention that some people -#think the names of the Beautiful Soup parser classes are too silly -#and "unprofessional" for use in enterprise screen-scraping. We feel -#your pain! For such-minded folk, the Beautiful Soup Consortium And -#All-Night Kosher Bakery recommends renaming this file to -#"RobustParser.py" (or, in cases of extreme enterprisiness, -#"RobustParserBeanInterface.class") and using the following -#enterprise-friendly class aliases: -class RobustXMLParser(BeautifulStoneSoup): - pass -class RobustHTMLParser(BeautifulSoup): - pass -class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup): - pass -class RobustInsanelyWackAssHTMLParser(MinimalSoup): - pass -class SimplifyingSOAPParser(BeautifulSOAP): - pass - -###################################################### -# -# Bonus library: Unicode, Dammit -# -# This class forces XML data into a standard format (usually to UTF-8 -# or Unicode). It is heavily based on code from Mark Pilgrim's -# Universal Feed Parser. It does not rewrite the XML or HTML to -# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi -# (XML) and BeautifulSoup.start_meta (HTML). - -# Autodetects character encodings. -# Download from http://chardet.feedparser.org/ -try: - import chardet -# import chardet.constants -# chardet.constants._debug = 1 -except ImportError: - chardet = None - -# cjkcodecs and iconv_codec make Python know about more character encodings. -# Both are available from http://cjkpython.i18n.org/ -# They're built in if you use Python 2.4. -try: - import cjkcodecs.aliases -except ImportError: - pass -try: - import iconv_codec -except ImportError: - pass - -class UnicodeDammit: - """A class for detecting the encoding of a *ML document and - converting it to a Unicode string. If the source encoding is - windows-1252, can replace MS smart quotes with their HTML or XML - equivalents.""" - - # This dictionary maps commonly seen values for "charset" in HTML - # meta tags to the corresponding Python codec names. It only covers - # values that aren't in Python's aliases and can't be determined - # by the heuristics in find_codec. - CHARSET_ALIASES = { "macintosh" : "mac-roman", - "x-sjis" : "shift-jis" } - - def __init__(self, markup, overrideEncodings=[], - smartQuotesTo='xml', isHTML=False): - self.declaredHTMLEncoding = None - self.markup, documentEncoding, sniffedEncoding = \ - self._detectEncoding(markup, isHTML) - self.smartQuotesTo = smartQuotesTo - self.triedEncodings = [] - if markup == '' or isinstance(markup, unicode): - self.originalEncoding = None - self.unicode = unicode(markup) - return - - u = None - for proposedEncoding in overrideEncodings: - u = self._convertFrom(proposedEncoding) - if u: break - if not u: - for proposedEncoding in (documentEncoding, sniffedEncoding): - u = self._convertFrom(proposedEncoding) - if u: break - - # If no luck and we have auto-detection library, try that: - if not u and chardet and not isinstance(self.markup, unicode): - u = self._convertFrom(chardet.detect(self.markup)['encoding']) - - # As a last resort, try utf-8 and windows-1252: - if not u: - for proposed_encoding in ("utf-8", "windows-1252"): - u = self._convertFrom(proposed_encoding) - if u: break - - self.unicode = u - if not u: self.originalEncoding = None - - def _subMSChar(self, orig): - """Changes a MS smart quote character to an XML or HTML - entity.""" - sub = self.MS_CHARS.get(orig) - if isinstance(sub, tuple): - if self.smartQuotesTo == 'xml': - sub = '&#x%s;' % sub[1] - else: - sub = '&%s;' % sub[0] - return sub - - def _convertFrom(self, proposed): - proposed = self.find_codec(proposed) - if not proposed or proposed in self.triedEncodings: - return None - self.triedEncodings.append(proposed) - markup = self.markup - - # Convert smart quotes to HTML if coming from an encoding - # that might have them. - if self.smartQuotesTo and proposed.lower() in("windows-1252", - "iso-8859-1", - "iso-8859-2"): - markup = re.compile("([\x80-\x9f])").sub \ - (lambda(x): self._subMSChar(x.group(1)), - markup) - - try: - # print "Trying to convert document to %s" % proposed - u = self._toUnicode(markup, proposed) - self.markup = u - self.originalEncoding = proposed - except Exception, e: - # print "That didn't work!" - # print e - return None - #print "Correct encoding: %s" % proposed - return self.markup - - def _toUnicode(self, data, encoding): - '''Given a string and its encoding, decodes the string into Unicode. - %encoding is a string recognized by encodings.aliases''' - - # strip Byte Order Mark (if present) - if (len(data) >= 4) and (data[:2] == '\xfe\xff') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16be' - data = data[2:] - elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16le' - data = data[2:] - elif data[:3] == '\xef\xbb\xbf': - encoding = 'utf-8' - data = data[3:] - elif data[:4] == '\x00\x00\xfe\xff': - encoding = 'utf-32be' - data = data[4:] - elif data[:4] == '\xff\xfe\x00\x00': - encoding = 'utf-32le' - data = data[4:] - newdata = unicode(data, encoding) - return newdata - - def _detectEncoding(self, xml_data, isHTML=False): - """Given a document, tries to detect its XML encoding.""" - xml_encoding = sniffed_xml_encoding = None - try: - if xml_data[:4] == '\x4c\x6f\xa7\x94': - # EBCDIC - xml_data = self._ebcdic_to_ascii(xml_data) - elif xml_data[:4] == '\x00\x3c\x00\x3f': - # UTF-16BE - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data, 'utf-16be').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \ - and (xml_data[2:4] != '\x00\x00'): - # UTF-16BE with BOM - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8') - elif xml_data[:4] == '\x3c\x00\x3f\x00': - # UTF-16LE - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data, 'utf-16le').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \ - (xml_data[2:4] != '\x00\x00'): - # UTF-16LE with BOM - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8') - elif xml_data[:4] == '\x00\x00\x00\x3c': - # UTF-32BE - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data, 'utf-32be').encode('utf-8') - elif xml_data[:4] == '\x3c\x00\x00\x00': - # UTF-32LE - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data, 'utf-32le').encode('utf-8') - elif xml_data[:4] == '\x00\x00\xfe\xff': - # UTF-32BE with BOM - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8') - elif xml_data[:4] == '\xff\xfe\x00\x00': - # UTF-32LE with BOM - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8') - elif xml_data[:3] == '\xef\xbb\xbf': - # UTF-8 with BOM - sniffed_xml_encoding = 'utf-8' - xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8') - else: - sniffed_xml_encoding = 'ascii' - pass - except: - xml_encoding_match = None - xml_encoding_match = re.compile( - '^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data) - if not xml_encoding_match and isHTML: - regexp = re.compile('<\s*meta[^>]+charset=([^>]*?)[;\'">]', re.I) - xml_encoding_match = regexp.search(xml_data) - if xml_encoding_match is not None: - xml_encoding = xml_encoding_match.groups()[0].lower() - if isHTML: - self.declaredHTMLEncoding = xml_encoding - if sniffed_xml_encoding and \ - (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', - 'iso-10646-ucs-4', 'ucs-4', 'csucs4', - 'utf-16', 'utf-32', 'utf_16', 'utf_32', - 'utf16', 'u16')): - xml_encoding = sniffed_xml_encoding - return xml_data, xml_encoding, sniffed_xml_encoding - - - def find_codec(self, charset): - return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \ - or (charset and self._codec(charset.replace("-", ""))) \ - or (charset and self._codec(charset.replace("-", "_"))) \ - or charset - - def _codec(self, charset): - if not charset: return charset - codec = None - try: - codecs.lookup(charset) - codec = charset - except (LookupError, ValueError): - pass - return codec - - EBCDIC_TO_ASCII_MAP = None - def _ebcdic_to_ascii(self, s): - c = self.__class__ - if not c.EBCDIC_TO_ASCII_MAP: - emap = (0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15, - 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31, - 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7, - 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26, - 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33, - 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94, - 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63, - 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34, - 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200, - 201,202,106,107,108,109,110,111,112,113,114,203,204,205, - 206,207,208,209,126,115,116,117,118,119,120,121,122,210, - 211,212,213,214,215,216,217,218,219,220,221,222,223,224, - 225,226,227,228,229,230,231,123,65,66,67,68,69,70,71,72, - 73,232,233,234,235,236,237,125,74,75,76,77,78,79,80,81, - 82,238,239,240,241,242,243,92,159,83,84,85,86,87,88,89, - 90,244,245,246,247,248,249,48,49,50,51,52,53,54,55,56,57, - 250,251,252,253,254,255) - import string - c.EBCDIC_TO_ASCII_MAP = string.maketrans( \ - ''.join(map(chr, range(256))), ''.join(map(chr, emap))) - return s.translate(c.EBCDIC_TO_ASCII_MAP) - - MS_CHARS = { '\x80' : ('euro', '20AC'), - '\x81' : ' ', - '\x82' : ('sbquo', '201A'), - '\x83' : ('fnof', '192'), - '\x84' : ('bdquo', '201E'), - '\x85' : ('hellip', '2026'), - '\x86' : ('dagger', '2020'), - '\x87' : ('Dagger', '2021'), - '\x88' : ('circ', '2C6'), - '\x89' : ('permil', '2030'), - '\x8A' : ('Scaron', '160'), - '\x8B' : ('lsaquo', '2039'), - '\x8C' : ('OElig', '152'), - '\x8D' : '?', - '\x8E' : ('#x17D', '17D'), - '\x8F' : '?', - '\x90' : '?', - '\x91' : ('lsquo', '2018'), - '\x92' : ('rsquo', '2019'), - '\x93' : ('ldquo', '201C'), - '\x94' : ('rdquo', '201D'), - '\x95' : ('bull', '2022'), - '\x96' : ('ndash', '2013'), - '\x97' : ('mdash', '2014'), - '\x98' : ('tilde', '2DC'), - '\x99' : ('trade', '2122'), - '\x9a' : ('scaron', '161'), - '\x9b' : ('rsaquo', '203A'), - '\x9c' : ('oelig', '153'), - '\x9d' : '?', - '\x9e' : ('#x17E', '17E'), - '\x9f' : ('Yuml', ''),} - -####################################################################### - - -#By default, act as an HTML pretty-printer. -if __name__ == '__main__': - import sys - soup = BeautifulSoup(sys.stdin) - print soup.prettify() diff --git a/module/lib/MultipartPostHandler.py b/module/lib/MultipartPostHandler.py deleted file mode 100644 index 94aee0193..000000000 --- a/module/lib/MultipartPostHandler.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#### -# 02/2006 Will Holcomb <wholcomb@gmail.com> -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# 7/26/07 Slightly modified by Brian Schneider -# in order to support unicode files ( multipart_encode function ) -""" -Usage: - Enables the use of multipart/form-data for posting forms - -Inspirations: - Upload files in python: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 - urllib2_file: - Fabien Seisen: <fabien@seisen.org> - -Example: - import MultipartPostHandler, urllib2, cookielib - - cookies = cookielib.CookieJar() - opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), - MultipartPostHandler.MultipartPostHandler) - params = { "username" : "bob", "password" : "riviera", - "file" : open("filename", "rb") } - opener.open("http://wwww.bobsite.com/upload/", params) - -Further Example: - The main function of this file is a sample which downloads a page and - then uploads it to the W3C validator. -""" - -from urllib import urlencode -from urllib2 import BaseHandler, HTTPHandler, build_opener -import mimetools, mimetypes -from os import write, remove -from cStringIO import StringIO - -class Callable: - def __init__(self, anycallable): - self.__call__ = anycallable - -# Controls how sequences are uncoded. If true, elements may be given multiple values by -# assigning a sequence. -doseq = 1 - -class MultipartPostHandler(BaseHandler): - handler_order = HTTPHandler.handler_order - 10 # needs to run first - - def http_request(self, request): - data = request.get_data() - if data is not None and type(data) != str: - v_files = [] - v_vars = [] - try: - for(key, value) in data.items(): - if type(value) == file: - v_files.append((key, value)) - else: - v_vars.append((key, value)) - except TypeError: - systype, value, traceback = sys.exc_info() - raise TypeError, "not a valid non-string sequence or mapping object", traceback - - if len(v_files) == 0: - data = urlencode(v_vars, doseq) - else: - boundary, data = self.multipart_encode(v_vars, v_files) - - contenttype = 'multipart/form-data; boundary=%s' % boundary - if(request.has_header('Content-Type') - and request.get_header('Content-Type').find('multipart/form-data') != 0): - print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') - request.add_unredirected_header('Content-Type', contenttype) - - request.add_data(data) - - return request - - def multipart_encode(vars, files, boundary = None, buf = None): - if boundary is None: - boundary = mimetools.choose_boundary() - if buf is None: - buf = StringIO() - for(key, value) in vars: - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"' % key) - buf.write('\r\n\r\n' + value + '\r\n') - for(key, fd) in files: - #file_size = os.fstat(fd.fileno())[stat.ST_SIZE] - filename = fd.name.split('/')[-1] - contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) - buf.write('Content-Type: %s\r\n' % contenttype) - # buffer += 'Content-Length: %s\r\n' % file_size - fd.seek(0) - buf.write('\r\n' + fd.read() + '\r\n') - buf.write('--' + boundary + '--\r\n\r\n') - buf = buf.getvalue() - return boundary, buf - multipart_encode = Callable(multipart_encode) - - https_request = http_request - -def main(): - import tempfile, sys - - validatorURL = "http://validator.w3.org/check" - opener = build_opener(MultipartPostHandler) - - def validateFile(url): - temp = tempfile.mkstemp(suffix=".html") - write(temp[0], opener.open(url).read()) - params = { "ss" : "0", # show source - "doctype" : "Inline", - "uploaded_file" : open(temp[1], "rb") } - print opener.open(validatorURL, params).read() - remove(temp[1]) - - if len(sys.argv[1:]) > 0: - for arg in sys.argv[1:]: - validateFile(arg) - else: - validateFile("http://www.google.com") - -if __name__=="__main__": - main() diff --git a/module/lib/Unzip.py b/module/lib/Unzip.py deleted file mode 100644 index f56fbe751..000000000 --- a/module/lib/Unzip.py +++ /dev/null @@ -1,50 +0,0 @@ -import zipfile -import os - -class Unzip: - def __init__(self): - pass - - def extract(self, file, dir): - if not dir.endswith(':') and not os.path.exists(dir): - os.mkdir(dir) - - zf = zipfile.ZipFile(file) - - # create directory structure to house files - self._createstructure(file, dir) - - # extract files to directory structure - for i, name in enumerate(zf.namelist()): - - if not name.endswith('/') and not name.endswith("config"): - print "extracting", name.replace("pyload/","") - outfile = open(os.path.join(dir, name.replace("pyload/","")), 'wb') - outfile.write(zf.read(name)) - outfile.flush() - outfile.close() - - def _createstructure(self, file, dir): - self._makedirs(self._listdirs(file), dir) - - def _makedirs(self, directories, basedir): - """ Create any directories that don't currently exist """ - for dir in directories: - curdir = os.path.join(basedir, dir) - if not os.path.exists(curdir): - os.mkdir(curdir) - - def _listdirs(self, file): - """ Grabs all the directories in the zip structure - This is necessary to create the structure before trying - to extract the file to it. """ - zf = zipfile.ZipFile(file) - - dirs = [] - - for name in zf.namelist(): - if name.endswith('/'): - dirs.append(name.replace("pyload/","")) - - dirs.sort() - return dirs diff --git a/module/lib/bottle.py b/module/lib/bottle.py deleted file mode 100644 index 2c243278e..000000000 --- a/module/lib/bottle.py +++ /dev/null @@ -1,2922 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Bottle is a fast and simple micro-framework for small web applications. It -offers request dispatching (Routes) with url parameter support, templates, -a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and -template engines - all in a single file and with no dependencies other than the -Python Standard Library. - -Homepage and documentation: http://bottlepy.org/ - -Copyright (c) 2011, Marcel Hellkamp. -License: MIT (see LICENSE.txt for details) -""" - -from __future__ import with_statement - -__author__ = 'Marcel Hellkamp' -__version__ = '0.10.2' -__license__ = 'MIT' - -# The gevent server adapter needs to patch some modules before they are imported -# This is why we parse the commandline parameters here but handle them later -if __name__ == '__main__': - from optparse import OptionParser - _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app") - _opt = _cmd_parser.add_option - _opt("--version", action="store_true", help="show version number.") - _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") - _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.") - _opt("-p", "--plugin", action="append", help="install additional plugin/s.") - _opt("--debug", action="store_true", help="start server in debug mode.") - _opt("--reload", action="store_true", help="auto-reload on file changes.") - _cmd_options, _cmd_args = _cmd_parser.parse_args() - if _cmd_options.server and _cmd_options.server.startswith('gevent'): - import gevent.monkey; gevent.monkey.patch_all() - -import sys -import base64 -import cgi -import email.utils -import functools -import hmac -import httplib -import imp -import itertools -import mimetypes -import os -import re -import subprocess -import tempfile -import thread -import threading -import time -import warnings - -from Cookie import SimpleCookie -from datetime import date as datedate, datetime, timedelta -from tempfile import TemporaryFile -from traceback import format_exc, print_exc -from urlparse import urljoin, SplitResult as UrlSplitResult - -# Workaround for a bug in some versions of lib2to3 (fixed on CPython 2.7 and 3.2) -import urllib -urlencode = urllib.urlencode -urlquote = urllib.quote -urlunquote = urllib.unquote - -try: from collections import MutableMapping as DictMixin -except ImportError: # pragma: no cover - from UserDict import DictMixin - -try: from urlparse import parse_qs -except ImportError: # pragma: no cover - from cgi import parse_qs - -try: import cPickle as pickle -except ImportError: # pragma: no cover - import pickle - -try: from json import dumps as json_dumps, loads as json_lds -except ImportError: # pragma: no cover - try: from simplejson import dumps as json_dumps, loads as json_lds - except ImportError: # pragma: no cover - try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds - except ImportError: # pragma: no cover - def json_dumps(data): - raise ImportError("JSON support requires Python 2.6 or simplejson.") - json_lds = json_dumps - -py3k = sys.version_info >= (3,0,0) -NCTextIOWrapper = None - -if py3k: # pragma: no cover - json_loads = lambda s: json_lds(touni(s)) - # See Request.POST - from io import BytesIO - def touni(x, enc='utf8', err='strict'): - """ Convert anything to unicode """ - return str(x, enc, err) if isinstance(x, bytes) else str(x) - if sys.version_info < (3,2,0): - from io import TextIOWrapper - class NCTextIOWrapper(TextIOWrapper): - ''' Garbage collecting an io.TextIOWrapper(buffer) instance closes - the wrapped buffer. This subclass keeps it open. ''' - def close(self): pass -else: - json_loads = json_lds - from StringIO import StringIO as BytesIO - bytes = str - def touni(x, enc='utf8', err='strict'): - """ Convert anything to unicode """ - return x if isinstance(x, unicode) else unicode(str(x), enc, err) - -def tob(data, enc='utf8'): - """ Convert anything to bytes """ - return data.encode(enc) if isinstance(data, unicode) else bytes(data) - -tonat = touni if py3k else tob -tonat.__doc__ = """ Convert anything to native strings """ - -def try_update_wrapper(wrapper, wrapped, *a, **ka): - try: # Bug: functools breaks if wrapper is an instane method - functools.update_wrapper(wrapper, wrapped, *a, **ka) - except AttributeError: pass - -# Backward compatibility -def depr(message): - warnings.warn(message, DeprecationWarning, stacklevel=3) - - -# Small helpers -def makelist(data): - if isinstance(data, (tuple, list, set, dict)): return list(data) - elif data: return [data] - else: return [] - - -class DictProperty(object): - ''' Property that maps to a key in a local dict-like attribute. ''' - def __init__(self, attr, key=None, read_only=False): - self.attr, self.key, self.read_only = attr, key, read_only - - def __call__(self, func): - functools.update_wrapper(self, func, updated=[]) - self.getter, self.key = func, self.key or func.__name__ - return self - - def __get__(self, obj, cls): - if obj is None: return self - key, storage = self.key, getattr(obj, self.attr) - if key not in storage: storage[key] = self.getter(obj) - return storage[key] - - def __set__(self, obj, value): - if self.read_only: raise AttributeError("Read-Only property.") - getattr(obj, self.attr)[self.key] = value - - def __delete__(self, obj): - if self.read_only: raise AttributeError("Read-Only property.") - del getattr(obj, self.attr)[self.key] - - -class CachedProperty(object): - ''' A property that is only computed once per instance and then replaces - itself with an ordinary attribute. Deleting the attribute resets the - property. ''' - - def __init__(self, func): - self.func = func - - def __get__(self, obj, cls): - if obj is None: return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - -cached_property = CachedProperty - - -class lazy_attribute(object): # Does not need configuration -> lower-case name - ''' A property that caches itself to the class object. ''' - def __init__(self, func): - functools.update_wrapper(self, func, updated=[]) - self.getter = func - - def __get__(self, obj, cls): - value = self.getter(cls) - setattr(cls, self.__name__, value) - return value - - - - - - -############################################################################### -# Exceptions and Events ######################################################## -############################################################################### - - -class BottleException(Exception): - """ A base class for exceptions used by bottle. """ - pass - - -#TODO: These should subclass BaseRequest - -class HTTPResponse(BottleException): - """ Used to break execution and immediately finish the response """ - def __init__(self, output='', status=200, header=None): - super(BottleException, self).__init__("HTTP Response %d" % status) - self.status = int(status) - self.output = output - self.headers = HeaderDict(header) if header else None - - def apply(self, response): - if self.headers: - for key, value in self.headers.iterallitems(): - response.headers[key] = value - response.status = self.status - - -class HTTPError(HTTPResponse): - """ Used to generate an error page """ - def __init__(self, code=500, output='Unknown Error', exception=None, - traceback=None, header=None): - super(HTTPError, self).__init__(output, code, header) - self.exception = exception - self.traceback = traceback - - def __repr__(self): - return template(ERROR_PAGE_TEMPLATE, e=self) - - - - - - -############################################################################### -# Routing ###################################################################### -############################################################################### - - -class RouteError(BottleException): - """ This is a base class for all routing related exceptions """ - - -class RouteReset(BottleException): - """ If raised by a plugin or request handler, the route is reset and all - plugins are re-applied. """ - -class RouterUnknownModeError(RouteError): pass - -class RouteSyntaxError(RouteError): - """ The route parser found something not supported by this router """ - -class RouteBuildError(RouteError): - """ The route could not been built """ - -class Router(object): - ''' A Router is an ordered collection of route->target pairs. It is used to - efficiently match WSGI requests against a number of routes and return - the first target that satisfies the request. The target may be anything, - usually a string, ID or callable object. A route consists of a path-rule - and a HTTP method. - - The path-rule is either a static path (e.g. `/contact`) or a dynamic - path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax - and details on the matching order are described in docs:`routing`. - ''' - - default_pattern = '[^/]+' - default_filter = 're' - #: Sorry for the mess. It works. Trust me. - rule_syntax = re.compile('(\\\\*)'\ - '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ - '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ - '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))') - - def __init__(self, strict=False): - self.rules = {} # A {rule: Rule} mapping - self.builder = {} # A rule/name->build_info mapping - self.static = {} # Cache for static routes: {path: {method: target}} - self.dynamic = [] # Cache for dynamic routes. See _compile() - #: If true, static routes are no longer checked first. - self.strict_order = strict - self.filters = {'re': self.re_filter, 'int': self.int_filter, - 'float': self.re_filter, 'path': self.path_filter} - - def re_filter(self, conf): - return conf or self.default_pattern, None, None - - def int_filter(self, conf): - return r'-?\d+', int, lambda x: str(int(x)) - - def float_filter(self, conf): - return r'-?\d*\.\d+', float, lambda x: str(float(x)) - - def path_filter(self, conf): - return r'.*?', None, None - - def add_filter(self, name, func): - ''' Add a filter. The provided function is called with the configuration - string as parameter and must return a (regexp, to_python, to_url) tuple. - The first element is a string, the last two are callables or None. ''' - self.filters[name] = func - - def parse_rule(self, rule): - ''' Parses a rule into a (name, filter, conf) token stream. If mode is - None, name contains a static rule part. ''' - offset, prefix = 0, '' - for match in self.rule_syntax.finditer(rule): - prefix += rule[offset:match.start()] - g = match.groups() - if len(g[0])%2: # Escaped wildcard - prefix += match.group(0)[len(g[0]):] - offset = match.end() - continue - if prefix: yield prefix, None, None - name, filtr, conf = g[1:4] if not g[2] is None else g[4:7] - if not filtr: filtr = self.default_filter - yield name, filtr, conf or None - offset, prefix = match.end(), '' - if offset <= len(rule) or prefix: - yield prefix+rule[offset:], None, None - - def add(self, rule, method, target, name=None): - ''' Add a new route or replace the target for an existing route. ''' - if rule in self.rules: - self.rules[rule][method] = target - if name: self.builder[name] = self.builder[rule] - return - - target = self.rules[rule] = {method: target} - - # Build pattern and other structures for dynamic routes - anons = 0 # Number of anonymous wildcards - pattern = '' # Regular expression pattern - filters = [] # Lists of wildcard input filters - builder = [] # Data structure for the URL builder - is_static = True - for key, mode, conf in self.parse_rule(rule): - if mode: - is_static = False - mask, in_filter, out_filter = self.filters[mode](conf) - if key: - pattern += '(?P<%s>%s)' % (key, mask) - else: - pattern += '(?:%s)' % mask - key = 'anon%d' % anons; anons += 1 - if in_filter: filters.append((key, in_filter)) - builder.append((key, out_filter or str)) - elif key: - pattern += re.escape(key) - builder.append((None, key)) - self.builder[rule] = builder - if name: self.builder[name] = builder - - if is_static and not self.strict_order: - self.static[self.build(rule)] = target - return - - def fpat_sub(m): - return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:' - flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern) - - try: - re_match = re.compile('^(%s)$' % pattern).match - except re.error, e: - raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e)) - - def match(path): - """ Return an url-argument dictionary. """ - url_args = re_match(path).groupdict() - for name, wildcard_filter in filters: - try: - url_args[name] = wildcard_filter(url_args[name]) - except ValueError: - raise HTTPError(400, 'Path has wrong format.') - return url_args - - try: - combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern) - self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1]) - self.dynamic[-1][1].append((match, target)) - except (AssertionError, IndexError), e: # AssertionError: Too many groups - self.dynamic.append((re.compile('(^%s$)' % flat_pattern), - [(match, target)])) - return match - - def build(self, _name, *anons, **query): - ''' Build an URL by filling the wildcards in a rule. ''' - builder = self.builder.get(_name) - if not builder: raise RouteBuildError("No route with that name.", _name) - try: - for i, value in enumerate(anons): query['anon%d'%i] = value - url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder]) - return url if not query else url+'?'+urlencode(query) - except KeyError, e: - raise RouteBuildError('Missing URL argument: %r' % e.args[0]) - - def match(self, environ): - ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). ''' - path, targets, urlargs = environ['PATH_INFO'] or '/', None, {} - if path in self.static: - targets = self.static[path] - else: - for combined, rules in self.dynamic: - match = combined.match(path) - if not match: continue - getargs, targets = rules[match.lastindex - 1] - urlargs = getargs(path) if getargs else {} - break - - if not targets: - raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO'])) - method = environ['REQUEST_METHOD'].upper() - if method in targets: - return targets[method], urlargs - if method == 'HEAD' and 'GET' in targets: - return targets['GET'], urlargs - if 'ANY' in targets: - return targets['ANY'], urlargs - allowed = [verb for verb in targets if verb != 'ANY'] - if 'GET' in allowed and 'HEAD' not in allowed: - allowed.append('HEAD') - raise HTTPError(405, "Method not allowed.", - header=[('Allow',",".join(allowed))]) - - - -class Route(object): - ''' This class wraps a route callback along with route specific metadata and - configuration and applies Plugins on demand. It is also responsible for - turing an URL path rule into a regular expression usable by the Router. - ''' - - - def __init__(self, app, rule, method, callback, name=None, - plugins=None, skiplist=None, **config): - #: The application this route is installed to. - self.app = app - #: The path-rule string (e.g. ``/wiki/:page``). - self.rule = rule - #: The HTTP method as a string (e.g. ``GET``). - self.method = method - #: The original callback with no plugins applied. Useful for introspection. - self.callback = callback - #: The name of the route (if specified) or ``None``. - self.name = name or None - #: A list of route-specific plugins (see :meth:`Bottle.route`). - self.plugins = plugins or [] - #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). - self.skiplist = skiplist or [] - #: Additional keyword arguments passed to the :meth:`Bottle.route` - #: decorator are stored in this dictionary. Used for route-specific - #: plugin configuration and meta-data. - self.config = ConfigDict(config) - - def __call__(self, *a, **ka): - depr("Some APIs changed to return Route() instances instead of"\ - " callables. Make sure to use the Route.call method and not to"\ - " call Route instances directly.") - return self.call(*a, **ka) - - @cached_property - def call(self): - ''' The route callback with all plugins applied. This property is - created on demand and then cached to speed up subsequent requests.''' - return self._make_callback() - - def reset(self): - ''' Forget any cached values. The next time :attr:`call` is accessed, - all plugins are re-applied. ''' - self.__dict__.pop('call', None) - - def prepare(self): - ''' Do all on-demand work immediately (useful for debugging).''' - self.call - - @property - def _context(self): - depr('Switch to Plugin API v2 and access the Route object directly.') - return dict(rule=self.rule, method=self.method, callback=self.callback, - name=self.name, app=self.app, config=self.config, - apply=self.plugins, skip=self.skiplist) - - def all_plugins(self): - ''' Yield all Plugins affecting this route. ''' - unique = set() - for p in reversed(self.app.plugins + self.plugins): - if True in self.skiplist: break - name = getattr(p, 'name', False) - if name and (name in self.skiplist or name in unique): continue - if p in self.skiplist or type(p) in self.skiplist: continue - if name: unique.add(name) - yield p - - def _make_callback(self): - callback = self.callback - for plugin in self.all_plugins(): - try: - if hasattr(plugin, 'apply'): - api = getattr(plugin, 'api', 1) - context = self if api > 1 else self._context - callback = plugin.apply(callback, context) - else: - callback = plugin(callback) - except RouteReset: # Try again with changed configuration. - return self._make_callback() - if not callback is self.callback: - try_update_wrapper(callback, self.callback) - return callback - - - - - - -############################################################################### -# Application Object ########################################################### -############################################################################### - - -class Bottle(object): - """ WSGI application """ - - def __init__(self, catchall=True, autojson=True, config=None): - """ Create a new bottle instance. - You usually don't do that. Use `bottle.app.push()` instead. - """ - self.routes = [] # List of installed :class:`Route` instances. - self.router = Router() # Maps requests to :class:`Route` instances. - self.plugins = [] # List of installed plugins. - - self.error_handler = {} - #: If true, most exceptions are catched and returned as :exc:`HTTPError` - self.config = ConfigDict(config or {}) - self.catchall = catchall - #: An instance of :class:`HooksPlugin`. Empty by default. - self.hooks = HooksPlugin() - self.install(self.hooks) - if autojson: - self.install(JSONPlugin()) - self.install(TemplatePlugin()) - - def mount(self, prefix, app, **options): - ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific - URL prefix. Example:: - - root_app.mount('/admin/', admin_app) - - :param prefix: path prefix or `mount-point`. If it ends in a slash, - that slash is mandatory. - :param app: an instance of :class:`Bottle` or a WSGI application. - - All other parameters are passed to the underlying :meth:`route` call. - ''' - if isinstance(app, basestring): - prefix, app = app, prefix - depr('Parameter order of Bottle.mount() changed.') # 0.10 - - parts = filter(None, prefix.split('/')) - if not parts: raise ValueError('Empty path prefix.') - path_depth = len(parts) - options.setdefault('skip', True) - options.setdefault('method', 'ANY') - - @self.route('/%s/:#.*#' % '/'.join(parts), **options) - def mountpoint(): - try: - request.path_shift(path_depth) - rs = BaseResponse([], 200) - def start_response(status, header): - rs.status = status - for name, value in header: rs.add_header(name, value) - return rs.body.append - rs.body = itertools.chain(rs.body, app(request.environ, start_response)) - return HTTPResponse(rs.body, rs.status, rs.headers) - finally: - request.path_shift(-path_depth) - - if not prefix.endswith('/'): - self.route('/' + '/'.join(parts), callback=mountpoint, **options) - - def install(self, plugin): - ''' Add a plugin to the list of plugins and prepare it for being - applied to all routes of this application. A plugin may be a simple - decorator or an object that implements the :class:`Plugin` API. - ''' - if hasattr(plugin, 'setup'): plugin.setup(self) - if not callable(plugin) and not hasattr(plugin, 'apply'): - raise TypeError("Plugins must be callable or implement .apply()") - self.plugins.append(plugin) - self.reset() - return plugin - - def uninstall(self, plugin): - ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type - object to remove all plugins that match that type, a string to remove - all plugins with a matching ``name`` attribute or ``True`` to remove all - plugins. Return the list of removed plugins. ''' - removed, remove = [], plugin - for i, plugin in list(enumerate(self.plugins))[::-1]: - if remove is True or remove is plugin or remove is type(plugin) \ - or getattr(plugin, 'name', True) == remove: - removed.append(plugin) - del self.plugins[i] - if hasattr(plugin, 'close'): plugin.close() - if removed: self.reset() - return removed - - def reset(self, route=None): - ''' Reset all routes (force plugins to be re-applied) and clear all - caches. If an ID or route object is given, only that specific route - is affected. ''' - if route is None: routes = self.routes - elif isinstance(route, Route): routes = [route] - else: routes = [self.routes[route]] - for route in routes: route.reset() - if DEBUG: - for route in routes: route.prepare() - self.hooks.trigger('app_reset') - - def close(self): - ''' Close the application and all installed plugins. ''' - for plugin in self.plugins: - if hasattr(plugin, 'close'): plugin.close() - self.stopped = True - - def match(self, environ): - """ Search for a matching route and return a (:class:`Route` , urlargs) - tuple. The second value is a dictionary with parameters extracted - from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" - return self.router.match(environ) - - def get_url(self, routename, **kargs): - """ Return a string that matches a named route """ - scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' - location = self.router.build(routename, **kargs).lstrip('/') - return urljoin(urljoin('/', scriptname), location) - - def route(self, path=None, method='GET', callback=None, name=None, - apply=None, skip=None, **config): - """ A decorator to bind a function to a request URL. Example:: - - @app.route('/hello/:name') - def hello(name): - return 'Hello %s' % name - - The ``:name`` part is a wildcard. See :class:`Router` for syntax - details. - - :param path: Request path or a list of paths to listen to. If no - path is specified, it is automatically generated from the - signature of the function. - :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of - methods to listen to. (default: `GET`) - :param callback: An optional shortcut to avoid the decorator - syntax. ``route(..., callback=func)`` equals ``route(...)(func)`` - :param name: The name for this route. (default: None) - :param apply: A decorator or plugin or a list of plugins. These are - applied to the route callback in addition to installed plugins. - :param skip: A list of plugins, plugin classes or names. Matching - plugins are not installed to this route. ``True`` skips all. - - Any additional keyword arguments are stored as route-specific - configuration and passed to plugins (see :meth:`Plugin.apply`). - """ - if callable(path): path, callback = None, path - plugins = makelist(apply) - skiplist = makelist(skip) - def decorator(callback): - # TODO: Documentation and tests - if isinstance(callback, basestring): callback = load(callback) - for rule in makelist(path) or yieldroutes(callback): - for verb in makelist(method): - verb = verb.upper() - route = Route(self, rule, verb, callback, name=name, - plugins=plugins, skiplist=skiplist, **config) - self.routes.append(route) - self.router.add(rule, verb, route, name=name) - if DEBUG: route.prepare() - return callback - return decorator(callback) if callback else decorator - - def get(self, path=None, method='GET', **options): - """ Equals :meth:`route`. """ - return self.route(path, method, **options) - - def post(self, path=None, method='POST', **options): - """ Equals :meth:`route` with a ``POST`` method parameter. """ - return self.route(path, method, **options) - - def put(self, path=None, method='PUT', **options): - """ Equals :meth:`route` with a ``PUT`` method parameter. """ - return self.route(path, method, **options) - - def delete(self, path=None, method='DELETE', **options): - """ Equals :meth:`route` with a ``DELETE`` method parameter. """ - return self.route(path, method, **options) - - def error(self, code=500): - """ Decorator: Register an output handler for a HTTP error code""" - def wrapper(handler): - self.error_handler[int(code)] = handler - return handler - return wrapper - - def hook(self, name): - """ Return a decorator that attaches a callback to a hook. """ - def wrapper(func): - self.hooks.add(name, func) - return func - return wrapper - - def handle(self, path, method='GET'): - """ (deprecated) Execute the first matching route callback and return - the result. :exc:`HTTPResponse` exceptions are catched and returned. - If :attr:`Bottle.catchall` is true, other exceptions are catched as - well and returned as :exc:`HTTPError` instances (500). - """ - depr("This method will change semantics in 0.10. Try to avoid it.") - if isinstance(path, dict): - return self._handle(path) - return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()}) - - def _handle(self, environ): - try: - route, args = self.router.match(environ) - environ['route.handle'] = environ['bottle.route'] = route - environ['route.url_args'] = args - return route.call(**args) - except HTTPResponse, r: - return r - except RouteReset: - route.reset() - return self._handle(environ) - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception, e: - if not self.catchall: raise - stacktrace = format_exc(10) - environ['wsgi.errors'].write(stacktrace) - return HTTPError(500, "Internal Server Error", e, stacktrace) - - def _cast(self, out, request, response, peek=None): - """ Try to convert the parameter into something WSGI compatible and set - correct HTTP headers when possible. - Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like, - iterable of strings and iterable of unicodes - """ - - # Empty output is done here - if not out: - response['Content-Length'] = 0 - return [] - # Join lists of byte or unicode strings. Mixed lists are NOT supported - if isinstance(out, (tuple, list))\ - and isinstance(out[0], (bytes, unicode)): - out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' - # Encode unicode strings - if isinstance(out, unicode): - out = out.encode(response.charset) - # Byte Strings are just returned - if isinstance(out, bytes): - response['Content-Length'] = len(out) - return [out] - # HTTPError or HTTPException (recursive, because they may wrap anything) - # TODO: Handle these explicitly in handle() or make them iterable. - if isinstance(out, HTTPError): - out.apply(response) - out = self.error_handler.get(out.status, repr)(out) - if isinstance(out, HTTPResponse): - depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9 - return self._cast(out, request, response) - if isinstance(out, HTTPResponse): - out.apply(response) - return self._cast(out.output, request, response) - - # File-like objects. - if hasattr(out, 'read'): - if 'wsgi.file_wrapper' in request.environ: - return request.environ['wsgi.file_wrapper'](out) - elif hasattr(out, 'close') or not hasattr(out, '__iter__'): - return WSGIFileWrapper(out) - - # Handle Iterables. We peek into them to detect their inner type. - try: - out = iter(out) - first = out.next() - while not first: - first = out.next() - except StopIteration: - return self._cast('', request, response) - except HTTPResponse, e: - first = e - except Exception, e: - first = HTTPError(500, 'Unhandled exception', e, format_exc(10)) - if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\ - or not self.catchall: - raise - # These are the inner types allowed in iterator or generator objects. - if isinstance(first, HTTPResponse): - return self._cast(first, request, response) - if isinstance(first, bytes): - return itertools.chain([first], out) - if isinstance(first, unicode): - return itertools.imap(lambda x: x.encode(response.charset), - itertools.chain([first], out)) - return self._cast(HTTPError(500, 'Unsupported response type: %s'\ - % type(first)), request, response) - - def wsgi(self, environ, start_response): - """ The bottle WSGI-interface. """ - try: - environ['bottle.app'] = self - request.bind(environ) - response.bind() - out = self._cast(self._handle(environ), request, response) - # rfc2616 section 4.3 - if response._status_code in (100, 101, 204, 304)\ - or request.method == 'HEAD': - if hasattr(out, 'close'): out.close() - out = [] - start_response(response._status_line, list(response.iter_headers())) - return out - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception, e: - if not self.catchall: raise - err = '<h1>Critical error while processing request: %s</h1>' \ - % environ.get('PATH_INFO', '/') - if DEBUG: - err += '<h2>Error:</h2>\n<pre>%s</pre>\n' % repr(e) - err += '<h2>Traceback:</h2>\n<pre>%s</pre>\n' % format_exc(10) - environ['wsgi.errors'].write(err) #TODO: wsgi.error should not get html - start_response('500 INTERNAL SERVER ERROR', [('Content-Type', 'text/html')]) - return [tob(err)] - - def __call__(self, environ, start_response): - ''' Each instance of :class:'Bottle' is a WSGI application. ''' - return self.wsgi(environ, start_response) - - - - - - -############################################################################### -# HTTP and WSGI Tools ########################################################## -############################################################################### - - -class BaseRequest(DictMixin): - """ A wrapper for WSGI environment dictionaries that adds a lot of - convenient access methods and properties. Most of them are read-only.""" - - #: Maximum size of memory buffer for :attr:`body` in bytes. - MEMFILE_MAX = 102400 - - def __init__(self, environ): - """ Wrap a WSGI environ dictionary. """ - #: The wrapped WSGI environ dictionary. This is the only real attribute. - #: All other attributes actually are read-only properties. - self.environ = environ - environ['bottle.request'] = self - - @property - def path(self): - ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix - broken clients and avoid the "empty path" edge case). ''' - return '/' + self.environ.get('PATH_INFO','').lstrip('/') - - @property - def method(self): - ''' The ``REQUEST_METHOD`` value as an uppercase string. ''' - return self.environ.get('REQUEST_METHOD', 'GET').upper() - - @DictProperty('environ', 'bottle.request.headers', read_only=True) - def headers(self): - ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to - HTTP request headers. ''' - return WSGIHeaderDict(self.environ) - - def get_header(self, name, default=None): - ''' Return the value of a request header, or a given default value. ''' - return self.headers.get(name, default) - - @DictProperty('environ', 'bottle.request.cookies', read_only=True) - def cookies(self): - """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT - decoded. Use :meth:`get_cookie` if you expect signed cookies. """ - cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')) - return FormsDict((c.key, c.value) for c in cookies.itervalues()) - - def get_cookie(self, key, default=None, secret=None): - """ Return the content of a cookie. To read a `Signed Cookie`, the - `secret` must match the one used to create the cookie (see - :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing - cookie or wrong signature), return a default value. """ - value = self.cookies.get(key) - if secret and value: - dec = cookie_decode(value, secret) # (key, value) tuple or None - return dec[1] if dec and dec[0] == key else default - return value or default - - @DictProperty('environ', 'bottle.request.query', read_only=True) - def query(self): - ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These - values are sometimes called "URL arguments" or "GET parameters", but - not to be confused with "URL wildcards" as they are provided by the - :class:`Router`. ''' - data = parse_qs(self.query_string, keep_blank_values=True) - get = self.environ['bottle.get'] = FormsDict() - for key, values in data.iteritems(): - for value in values: - get[key] = value - return get - - @DictProperty('environ', 'bottle.request.forms', read_only=True) - def forms(self): - """ Form values parsed from an `url-encoded` or `multipart/form-data` - encoded POST or PUT request body. The result is retuned as a - :class:`FormsDict`. All keys and values are strings. File uploads - are stored separately in :attr:`files`. """ - forms = FormsDict() - for name, item in self.POST.iterallitems(): - if not hasattr(item, 'filename'): - forms[name] = item - return forms - - @DictProperty('environ', 'bottle.request.params', read_only=True) - def params(self): - """ A :class:`FormsDict` with the combined values of :attr:`query` and - :attr:`forms`. File uploads are stored in :attr:`files`. """ - params = FormsDict() - for key, value in self.query.iterallitems(): - params[key] = value - for key, value in self.forms.iterallitems(): - params[key] = value - return params - - @DictProperty('environ', 'bottle.request.files', read_only=True) - def files(self): - """ File uploads parsed from an `url-encoded` or `multipart/form-data` - encoded POST or PUT request body. The values are instances of - :class:`cgi.FieldStorage`. The most important attributes are: - - filename - The filename, if specified; otherwise None; this is the client - side filename, *not* the file name on which it is stored (that's - a temporary file you don't deal with) - file - The file(-like) object from which you can read the data. - value - The value as a *string*; for file uploads, this transparently - reads the file every time you request the value. Do not do this - on big files. - """ - files = FormsDict() - for name, item in self.POST.iterallitems(): - if hasattr(item, 'filename'): - files[name] = item - return files - - @DictProperty('environ', 'bottle.request.json', read_only=True) - def json(self): - ''' If the ``Content-Type`` header is ``application/json``, this - property holds the parsed content of the request body. Only requests - smaller than :attr:`MEMFILE_MAX` are processed to avoid memory - exhaustion. ''' - if 'application/json' in self.environ.get('CONTENT_TYPE', '') \ - and 0 < self.content_length < self.MEMFILE_MAX: - return json_loads(self.body.read(self.MEMFILE_MAX)) - return None - - @DictProperty('environ', 'bottle.request.body', read_only=True) - def _body(self): - maxread = max(0, self.content_length) - stream = self.environ['wsgi.input'] - body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b') - while maxread > 0: - part = stream.read(min(maxread, self.MEMFILE_MAX)) - if not part: break - body.write(part) - maxread -= len(part) - self.environ['wsgi.input'] = body - body.seek(0) - return body - - @property - def body(self): - """ The HTTP request body as a seek-able file-like object. Depending on - :attr:`MEMFILE_MAX`, this is either a temporary file or a - :class:`io.BytesIO` instance. Accessing this property for the first - time reads and replaces the ``wsgi.input`` environ variable. - Subsequent accesses just do a `seek(0)` on the file object. """ - self._body.seek(0) - return self._body - - #: An alias for :attr:`query`. - GET = query - - @DictProperty('environ', 'bottle.request.post', read_only=True) - def POST(self): - """ The values of :attr:`forms` and :attr:`files` combined into a single - :class:`FormsDict`. Values are either strings (form values) or - instances of :class:`cgi.FieldStorage` (file uploads). - """ - post = FormsDict() - safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi - for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): - if key in self.environ: safe_env[key] = self.environ[key] - if NCTextIOWrapper: - fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n') - else: - fb = self.body - data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True) - for item in data.list or []: - post[item.name] = item if item.filename else item.value - return post - - @property - def COOKIES(self): - ''' Alias for :attr:`cookies` (deprecated). ''' - depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).') - return self.cookies - - @property - def url(self): - """ The full request URI including hostname and scheme. If your app - lives behind a reverse proxy or load balancer and you get confusing - results, make sure that the ``X-Forwarded-Host`` header is set - correctly. """ - return self.urlparts.geturl() - - @DictProperty('environ', 'bottle.request.urlparts', read_only=True) - def urlparts(self): - ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. - The tuple contains (scheme, host, path, query_string and fragment), - but the fragment is always empty because it is not visible to the - server. ''' - env = self.environ - http = env.get('wsgi.url_scheme', 'http') - host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') - if not host: - # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. - host = env.get('SERVER_NAME', '127.0.0.1') - port = env.get('SERVER_PORT') - if port and port != ('80' if http == 'http' else '443'): - host += ':' + port - path = urlquote(self.fullpath) - return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '') - - @property - def fullpath(self): - """ Request path including :attr:`script_name` (if present). """ - return urljoin(self.script_name, self.path.lstrip('/')) - - @property - def query_string(self): - """ The raw :attr:`query` part of the URL (everything in between ``?`` - and ``#``) as a string. """ - return self.environ.get('QUERY_STRING', '') - - @property - def script_name(self): - ''' The initial portion of the URL's `path` that was removed by a higher - level (server or routing middleware) before the application was - called. This script path is returned with leading and tailing - slashes. ''' - script_name = self.environ.get('SCRIPT_NAME', '').strip('/') - return '/' + script_name + '/' if script_name else '/' - - def path_shift(self, shift=1): - ''' Shift path segments from :attr:`path` to :attr:`script_name` and - vice versa. - - :param shift: The number of path segments to shift. May be negative - to change the shift direction. (default: 1) - ''' - script = self.environ.get('SCRIPT_NAME','/') - self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift) - - @property - def content_length(self): - ''' The request body length as an integer. The client is responsible to - set this header. Otherwise, the real length of the body is unknown - and -1 is returned. In this case, :attr:`body` will be empty. ''' - return int(self.environ.get('CONTENT_LENGTH') or -1) - - @property - def is_xhr(self): - ''' True if the request was triggered by a XMLHttpRequest. This only - works with JavaScript libraries that support the `X-Requested-With` - header (most of the popular libraries do). ''' - requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','') - return requested_with.lower() == 'xmlhttprequest' - - @property - def is_ajax(self): - ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. ''' - return self.is_xhr - - @property - def auth(self): - """ HTTP authentication data as a (user, password) tuple. This - implementation currently supports basic (not digest) authentication - only. If the authentication happened at a higher level (e.g. in the - front web-server or a middleware), the password field is None, but - the user field is looked up from the ``REMOTE_USER`` environ - variable. On any errors, None is returned. """ - basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION','')) - if basic: return basic - ruser = self.environ.get('REMOTE_USER') - if ruser: return (ruser, None) - return None - - @property - def remote_route(self): - """ A list of all IPs that were involved in this request, starting with - the client IP and followed by zero or more proxies. This does only - work if all proxies support the ```X-Forwarded-For`` header. Note - that this information can be forged by malicious clients. """ - proxy = self.environ.get('HTTP_X_FORWARDED_FOR') - if proxy: return [ip.strip() for ip in proxy.split(',')] - remote = self.environ.get('REMOTE_ADDR') - return [remote] if remote else [] - - @property - def remote_addr(self): - """ The client IP as a string. Note that this information can be forged - by malicious clients. """ - route = self.remote_route - return route[0] if route else None - - def copy(self): - """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """ - return Request(self.environ.copy()) - - def __getitem__(self, key): return self.environ[key] - def __delitem__(self, key): self[key] = ""; del(self.environ[key]) - def __iter__(self): return iter(self.environ) - def __len__(self): return len(self.environ) - def keys(self): return self.environ.keys() - def __setitem__(self, key, value): - """ Change an environ value and clear all caches that depend on it. """ - - if self.environ.get('bottle.request.readonly'): - raise KeyError('The environ dictionary is read-only.') - - self.environ[key] = value - todelete = () - - if key == 'wsgi.input': - todelete = ('body', 'forms', 'files', 'params', 'post', 'json') - elif key == 'QUERY_STRING': - todelete = ('query', 'params') - elif key.startswith('HTTP_'): - todelete = ('headers', 'cookies') - - for key in todelete: - self.environ.pop('bottle.request.'+key, None) - - def __repr__(self): - return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url) - -def _hkey(s): - return s.title().replace('_','-') - - -class HeaderProperty(object): - def __init__(self, name, reader=None, writer=str, default=''): - self.name, self.reader, self.writer, self.default = name, reader, writer, default - self.__doc__ = 'Current value of the %r header.' % name.title() - - def __get__(self, obj, cls): - if obj is None: return self - value = obj.headers.get(self.name) - return self.reader(value) if (value and self.reader) else (value or self.default) - - def __set__(self, obj, value): - if self.writer: value = self.writer(value) - obj.headers[self.name] = value - - def __delete__(self, obj): - if self.name in obj.headers: - del obj.headers[self.name] - - -class BaseResponse(object): - """ Storage class for a response body as well as headers and cookies. - - This class does support dict-like case-insensitive item-access to - headers, but is NOT a dict. Most notably, iterating over a response - yields parts of the body and not the headers. - """ - - default_status = 200 - default_content_type = 'text/html; charset=UTF-8' - - # Header blacklist for specific response codes - # (rfc2616 section 10.2.3 and 10.3.5) - bad_headers = { - 204: set(('Content-Type',)), - 304: set(('Allow', 'Content-Encoding', 'Content-Language', - 'Content-Length', 'Content-Range', 'Content-Type', - 'Content-Md5', 'Last-Modified'))} - - def __init__(self, body='', status=None, **headers): - self._status_line = None - self._status_code = None - self.body = body - self._cookies = None - self._headers = {'Content-Type': [self.default_content_type]} - self.status = status or self.default_status - if headers: - for name, value in headers.items(): - self[name] = value - - def copy(self): - ''' Returns a copy of self. ''' - copy = Response() - copy.status = self.status - copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) - return copy - - def __iter__(self): - return iter(self.body) - - def close(self): - if hasattr(self.body, 'close'): - self.body.close() - - @property - def status_line(self): - ''' The HTTP status line as a string (e.g. ``404 Not Found``).''' - return self._status_line - - @property - def status_code(self): - ''' The HTTP status code as an integer (e.g. 404).''' - return self._status_code - - def _set_status(self, status): - if isinstance(status, int): - code, status = status, _HTTP_STATUS_LINES.get(status) - elif ' ' in status: - status = status.strip() - code = int(status.split()[0]) - else: - raise ValueError('String status line without a reason phrase.') - if not 100 <= code <= 999: raise ValueError('Status code out of range.') - self._status_code = code - self._status_line = status or ('%d Unknown' % code) - - def _get_status(self): - depr('BaseReuqest.status will change to return a string in 0.11. Use'\ - ' status_line and status_code to make sure.') #0.10 - return self._status_code - - status = property(_get_status, _set_status, None, - ''' A writeable property to change the HTTP response status. It accepts - either a numeric code (100-999) or a string with a custom reason - phrase (e.g. "404 Brain not found"). Both :data:`status_line` and - :data:`status_code` are updates accordingly. The return value is - always a numeric code. ''') - del _get_status, _set_status - - @property - def headers(self): - ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like - view on the response headers. ''' - self.__dict__['headers'] = hdict = HeaderDict() - hdict.dict = self._headers - return hdict - - def __contains__(self, name): return _hkey(name) in self._headers - def __delitem__(self, name): del self._headers[_hkey(name)] - def __getitem__(self, name): return self._headers[_hkey(name)][-1] - def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)] - - def get_header(self, name, default=None): - ''' Return the value of a previously defined header. If there is no - header with that name, return a default value. ''' - return self._headers.get(_hkey(name), [default])[-1] - - def set_header(self, name, value, append=False): - ''' Create a new response header, replacing any previously defined - headers with the same name. ''' - if append: - self.add_header(name, value) - else: - self._headers[_hkey(name)] = [str(value)] - - def add_header(self, name, value): - ''' Add an additional response header, not removing duplicates. ''' - self._headers.setdefault(_hkey(name), []).append(str(value)) - - def iter_headers(self): - ''' Yield (header, value) tuples, skipping headers that are not - allowed with the current response status code. ''' - headers = self._headers.iteritems() - bad_headers = self.bad_headers.get(self.status_code) - if bad_headers: - headers = [h for h in headers if h[0] not in bad_headers] - for name, values in headers: - for value in values: - yield name, value - if self._cookies: - for c in self._cookies.values(): - yield 'Set-Cookie', c.OutputString() - - def wsgiheader(self): - depr('The wsgiheader method is deprecated. See headerlist.') #0.10 - return self.headerlist - - @property - def headerlist(self): - ''' WSGI conform list of (header, value) tuples. ''' - return list(self.iter_headers()) - - content_type = HeaderProperty('Content-Type') - content_length = HeaderProperty('Content-Length', reader=int) - - @property - def charset(self): - """ Return the charset specified in the content-type header (default: utf8). """ - if 'charset=' in self.content_type: - return self.content_type.split('charset=')[-1].split(';')[0].strip() - return 'UTF-8' - - @property - def COOKIES(self): - """ A dict-like SimpleCookie instance. This should not be used directly. - See :meth:`set_cookie`. """ - depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10 - if not self._cookies: - self._cookies = SimpleCookie() - return self._cookies - - def set_cookie(self, name, value, secret=None, **options): - ''' Create a new cookie or replace an old one. If the `secret` parameter is - set, create a `Signed Cookie` (described below). - - :param name: the name of the cookie. - :param value: the value of the cookie. - :param secret: a signature key required for signed cookies. - - Additionally, this method accepts all RFC 2109 attributes that are - supported by :class:`cookie.Morsel`, including: - - :param max_age: maximum age in seconds. (default: None) - :param expires: a datetime object or UNIX timestamp. (default: None) - :param domain: the domain that is allowed to read the cookie. - (default: current domain) - :param path: limits the cookie to a given path (default: current path) - :param secure: limit the cookie to HTTPS connections (default: off). - :param httponly: prevents client-side javascript to read this cookie - (default: off, requires Python 2.6 or newer). - - If neither `expires` nor `max_age` is set (default), the cookie will - expire at the end of the browser session (as soon as the browser - window is closed). - - Signed cookies may store any pickle-able object and are - cryptographically signed to prevent manipulation. Keep in mind that - cookies are limited to 4kb in most browsers. - - Warning: Signed cookies are not encrypted (the client can still see - the content) and not copy-protected (the client can restore an old - cookie). The main intention is to make pickling and unpickling - save, not to store secret information at client side. - ''' - if not self._cookies: - self._cookies = SimpleCookie() - - if secret: - value = touni(cookie_encode((name, value), secret)) - elif not isinstance(value, basestring): - raise TypeError('Secret key missing for non-string Cookie.') - - if len(value) > 4096: raise ValueError('Cookie value to long.') - self._cookies[name] = value - - for key, value in options.iteritems(): - if key == 'max_age': - if isinstance(value, timedelta): - value = value.seconds + value.days * 24 * 3600 - if key == 'expires': - if isinstance(value, (datedate, datetime)): - value = value.timetuple() - elif isinstance(value, (int, float)): - value = time.gmtime(value) - value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value) - self._cookies[name][key.replace('_', '-')] = value - - def delete_cookie(self, key, **kwargs): - ''' Delete a cookie. Be sure to use the same `domain` and `path` - settings as used to create the cookie. ''' - kwargs['max_age'] = -1 - kwargs['expires'] = 0 - self.set_cookie(key, '', **kwargs) - - def __repr__(self): - out = '' - for name, value in self.headerlist: - out += '%s: %s\n' % (name.title(), value.strip()) - return out - - -class LocalRequest(BaseRequest, threading.local): - ''' A thread-local subclass of :class:`BaseRequest`. ''' - def __init__(self): pass - bind = BaseRequest.__init__ - - -class LocalResponse(BaseResponse, threading.local): - ''' A thread-local subclass of :class:`BaseResponse`. ''' - bind = BaseResponse.__init__ - -Response = LocalResponse # BC 0.9 -Request = LocalRequest # BC 0.9 - - - - - - -############################################################################### -# Plugins ###################################################################### -############################################################################### - -class PluginError(BottleException): pass - -class JSONPlugin(object): - name = 'json' - api = 2 - - def __init__(self, json_dumps=json_dumps): - self.json_dumps = json_dumps - - def apply(self, callback, context): - dumps = self.json_dumps - if not dumps: return callback - def wrapper(*a, **ka): - rv = callback(*a, **ka) - if isinstance(rv, dict): - #Attempt to serialize, raises exception on failure - json_response = dumps(rv) - #Set content type only if serialization succesful - response.content_type = 'application/json' - return json_response - return rv - return wrapper - - -class HooksPlugin(object): - name = 'hooks' - api = 2 - - _names = 'before_request', 'after_request', 'app_reset' - - def __init__(self): - self.hooks = dict((name, []) for name in self._names) - self.app = None - - def _empty(self): - return not (self.hooks['before_request'] or self.hooks['after_request']) - - def setup(self, app): - self.app = app - - def add(self, name, func): - ''' Attach a callback to a hook. ''' - was_empty = self._empty() - self.hooks.setdefault(name, []).append(func) - if self.app and was_empty and not self._empty(): self.app.reset() - - def remove(self, name, func): - ''' Remove a callback from a hook. ''' - was_empty = self._empty() - if name in self.hooks and func in self.hooks[name]: - self.hooks[name].remove(func) - if self.app and not was_empty and self._empty(): self.app.reset() - - def trigger(self, name, *a, **ka): - ''' Trigger a hook and return a list of results. ''' - hooks = self.hooks[name] - if ka.pop('reversed', False): hooks = hooks[::-1] - return [hook(*a, **ka) for hook in hooks] - - def apply(self, callback, context): - if self._empty(): return callback - def wrapper(*a, **ka): - self.trigger('before_request') - rv = callback(*a, **ka) - self.trigger('after_request', reversed=True) - return rv - return wrapper - - -class TemplatePlugin(object): - ''' This plugin applies the :func:`view` decorator to all routes with a - `template` config parameter. If the parameter is a tuple, the second - element must be a dict with additional options (e.g. `template_engine`) - or default variables for the template. ''' - name = 'template' - api = 2 - - def apply(self, callback, route): - conf = route.config.get('template') - if isinstance(conf, (tuple, list)) and len(conf) == 2: - return view(conf[0], **conf[1])(callback) - elif isinstance(conf, str) and 'template_opts' in route.config: - depr('The `template_opts` parameter is deprecated.') #0.9 - return view(conf, **route.config['template_opts'])(callback) - elif isinstance(conf, str): - return view(conf)(callback) - else: - return callback - - -#: Not a plugin, but part of the plugin API. TODO: Find a better place. -class _ImportRedirect(object): - def __init__(self, name, impmask): - ''' Create a virtual package that redirects imports (see PEP 302). ''' - self.name = name - self.impmask = impmask - self.module = sys.modules.setdefault(name, imp.new_module(name)) - self.module.__dict__.update({'__file__': __file__, '__path__': [], - '__all__': [], '__loader__': self}) - sys.meta_path.append(self) - - def find_module(self, fullname, path=None): - if '.' not in fullname: return - packname, modname = fullname.rsplit('.', 1) - if packname != self.name: return - return self - - def load_module(self, fullname): - if fullname in sys.modules: return sys.modules[fullname] - packname, modname = fullname.rsplit('.', 1) - realname = self.impmask % modname - __import__(realname) - module = sys.modules[fullname] = sys.modules[realname] - setattr(self.module, modname, module) - module.__loader__ = self - return module - - - - - - -############################################################################### -# Common Utilities ############################################################# -############################################################################### - - -class MultiDict(DictMixin): - """ This dict stores multiple values per key, but behaves exactly like a - normal dict in that it returns only the newest value for any given key. - There are special methods available to access the full list of values. - """ - - def __init__(self, *a, **k): - self.dict = dict((k, [v]) for k, v in dict(*a, **k).iteritems()) - def __len__(self): return len(self.dict) - def __iter__(self): return iter(self.dict) - def __contains__(self, key): return key in self.dict - def __delitem__(self, key): del self.dict[key] - def __getitem__(self, key): return self.dict[key][-1] - def __setitem__(self, key, value): self.append(key, value) - def iterkeys(self): return self.dict.iterkeys() - def itervalues(self): return (v[-1] for v in self.dict.itervalues()) - def iteritems(self): return ((k, v[-1]) for (k, v) in self.dict.iteritems()) - def iterallitems(self): - for key, values in self.dict.iteritems(): - for value in values: - yield key, value - - # 2to3 is not able to fix these automatically. - keys = iterkeys if py3k else lambda self: list(self.iterkeys()) - values = itervalues if py3k else lambda self: list(self.itervalues()) - items = iteritems if py3k else lambda self: list(self.iteritems()) - allitems = iterallitems if py3k else lambda self: list(self.iterallitems()) - - def get(self, key, default=None, index=-1, type=None): - ''' Return the most recent value for a key. - - :param default: The default value to be returned if the key is not - present or the type conversion fails. - :param index: An index for the list of available values. - :param type: If defined, this callable is used to cast the value - into a specific type. Exception are suppressed and result in - the default value to be returned. - ''' - try: - val = self.dict[key][index] - return type(val) if type else val - except Exception, e: - pass - return default - - def append(self, key, value): - ''' Add a new value to the list of values for this key. ''' - self.dict.setdefault(key, []).append(value) - - def replace(self, key, value): - ''' Replace the list of values with a single value. ''' - self.dict[key] = [value] - - def getall(self, key): - ''' Return a (possibly empty) list of values for a key. ''' - return self.dict.get(key) or [] - - #: Aliases for WTForms to mimic other multi-dict APIs (Django) - getone = get - getlist = getall - - - -class FormsDict(MultiDict): - ''' This :class:`MultiDict` subclass is used to store request form data. - Additionally to the normal dict-like item access methods (which return - unmodified data as native strings), this container also supports - attribute-like access to its values. Attribues are automatiically de- or - recoded to match :attr:`input_encoding` (default: 'utf8'). Missing - attributes default to an empty string. ''' - - #: Encoding used for attribute values. - input_encoding = 'utf8' - - def getunicode(self, name, default=None, encoding=None): - value, enc = self.get(name, default), encoding or self.input_encoding - try: - if isinstance(value, bytes): # Python 2 WSGI - return value.decode(enc) - elif isinstance(value, unicode): # Python 3 WSGI - return value.encode('latin1').decode(enc) - return value - except UnicodeError, e: - return default - - def __getattr__(self, name): return self.getunicode(name, default=u'') - - -class HeaderDict(MultiDict): - """ A case-insensitive version of :class:`MultiDict` that defaults to - replace the old value instead of appending it. """ - - def __init__(self, *a, **ka): - self.dict = {} - if a or ka: self.update(*a, **ka) - - def __contains__(self, key): return _hkey(key) in self.dict - def __delitem__(self, key): del self.dict[_hkey(key)] - def __getitem__(self, key): return self.dict[_hkey(key)][-1] - def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)] - def append(self, key, value): - self.dict.setdefault(_hkey(key), []).append(str(value)) - def replace(self, key, value): self.dict[_hkey(key)] = [str(value)] - def getall(self, key): return self.dict.get(_hkey(key)) or [] - def get(self, key, default=None, index=-1): - return MultiDict.get(self, _hkey(key), default, index) - def filter(self, names): - for name in map(_hkey, names): - if name in self.dict: - del self.dict[name] - - -class WSGIHeaderDict(DictMixin): - ''' This dict-like class wraps a WSGI environ dict and provides convenient - access to HTTP_* fields. Keys and values are native strings - (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI - environment contains non-native string values, these are de- or encoded - using a lossless 'latin1' character set. - - The API will remain stable even on changes to the relevant PEPs. - Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one - that uses non-native strings.) - ''' - #: List of keys that do not have a 'HTTP_' prefix. - cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') - - def __init__(self, environ): - self.environ = environ - - def _ekey(self, key): - ''' Translate header field name to CGI/WSGI environ key. ''' - key = key.replace('-','_').upper() - if key in self.cgikeys: - return key - return 'HTTP_' + key - - def raw(self, key, default=None): - ''' Return the header value as is (may be bytes or unicode). ''' - return self.environ.get(self._ekey(key), default) - - def __getitem__(self, key): - return tonat(self.environ[self._ekey(key)], 'latin1') - - def __setitem__(self, key, value): - raise TypeError("%s is read-only." % self.__class__) - - def __delitem__(self, key): - raise TypeError("%s is read-only." % self.__class__) - - def __iter__(self): - for key in self.environ: - if key[:5] == 'HTTP_': - yield key[5:].replace('_', '-').title() - elif key in self.cgikeys: - yield key.replace('_', '-').title() - - def keys(self): return [x for x in self] - def __len__(self): return len(self.keys()) - def __contains__(self, key): return self._ekey(key) in self.environ - - -class ConfigDict(dict): - ''' A dict-subclass with some extras: You can access keys like attributes. - Uppercase attributes create new ConfigDicts and act as name-spaces. - Other missing attributes return None. Calling a ConfigDict updates its - values and returns itself. - - >>> cfg = ConfigDict() - >>> cfg.Namespace.value = 5 - >>> cfg.OtherNamespace(a=1, b=2) - >>> cfg - {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}} - ''' - - def __getattr__(self, key): - if key not in self and key[0].isupper(): - self[key] = ConfigDict() - return self.get(key) - - def __setattr__(self, key, value): - if hasattr(dict, key): - raise AttributeError('Read-only attribute.') - if key in self and self[key] and isinstance(self[key], ConfigDict): - raise AttributeError('Non-empty namespace attribute.') - self[key] = value - - def __delattr__(self, key): - if key in self: del self[key] - - def __call__(self, *a, **ka): - for key, value in dict(*a, **ka).iteritems(): setattr(self, key, value) - return self - - -class AppStack(list): - """ A stack-like list. Calling it returns the head of the stack. """ - - def __call__(self): - """ Return the current default application. """ - return self[-1] - - def push(self, value=None): - """ Add a new :class:`Bottle` instance to the stack """ - if not isinstance(value, Bottle): - value = Bottle() - self.append(value) - return value - - -class WSGIFileWrapper(object): - - def __init__(self, fp, buffer_size=1024*64): - self.fp, self.buffer_size = fp, buffer_size - for attr in ('fileno', 'close', 'read', 'readlines'): - if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr)) - - def __iter__(self): - read, buff = self.fp.read, self.buffer_size - while True: - part = read(buff) - if not part: break - yield part - - - - - - -############################################################################### -# Application Helper ########################################################### -############################################################################### - - -def abort(code=500, text='Unknown Error: Application stopped.'): - """ Aborts execution and causes a HTTP error. """ - raise HTTPError(code, text) - - -def redirect(url, code=None): - """ Aborts execution and causes a 303 or 302 redirect, depending on - the HTTP protocol version. """ - if code is None: - code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 - location = urljoin(request.url, url) - raise HTTPResponse("", status=code, header=dict(Location=location)) - - -def static_file(filename, root, mimetype='auto', download=False): - """ Open a file in a safe way and return :exc:`HTTPResponse` with status - code 200, 305, 401 or 404. Set Content-Type, Content-Encoding, - Content-Length and Last-Modified header. Obey If-Modified-Since header - and HEAD requests. - """ - root = os.path.abspath(root) + os.sep - filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) - header = dict() - - if not filename.startswith(root): - return HTTPError(403, "Access denied.") - if not os.path.exists(filename) or not os.path.isfile(filename): - return HTTPError(404, "File does not exist.") - if not os.access(filename, os.R_OK): - return HTTPError(403, "You do not have permission to access this file.") - - if mimetype == 'auto': - mimetype, encoding = mimetypes.guess_type(filename) - if mimetype: header['Content-Type'] = mimetype - if encoding: header['Content-Encoding'] = encoding - elif mimetype: - header['Content-Type'] = mimetype - - if download: - download = os.path.basename(filename if download == True else download) - header['Content-Disposition'] = 'attachment; filename="%s"' % download - - stats = os.stat(filename) - header['Content-Length'] = stats.st_size - lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) - header['Last-Modified'] = lm - - ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') - if ims: - ims = parse_date(ims.split(";")[0].strip()) - if ims is not None and ims >= int(stats.st_mtime): - header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) - return HTTPResponse(status=304, header=header) - - body = '' if request.method == 'HEAD' else open(filename, 'rb') - return HTTPResponse(body, header=header) - - - - - - -############################################################################### -# HTTP Utilities and MISC (TODO) ############################################### -############################################################################### - - -def debug(mode=True): - """ Change the debug level. - There is only one debug level supported at the moment.""" - global DEBUG - DEBUG = bool(mode) - - -def parse_date(ims): - """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ - try: - ts = email.utils.parsedate_tz(ims) - return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone - except (TypeError, ValueError, IndexError, OverflowError): - return None - - -def parse_auth(header): - """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" - try: - method, data = header.split(None, 1) - if method.lower() == 'basic': - #TODO: Add 2to3 save base64[encode/decode] functions. - user, pwd = touni(base64.b64decode(tob(data))).split(':',1) - return user, pwd - except (KeyError, ValueError): - return None - - -def _lscmp(a, b): - ''' Compares two strings in a cryptographically save way: - Runtime is not affected by length of common prefix. ''' - return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) - - -def cookie_encode(data, key): - ''' Encode and sign a pickle-able object. Return a (byte) string ''' - msg = base64.b64encode(pickle.dumps(data, -1)) - sig = base64.b64encode(hmac.new(tob(key), msg).digest()) - return tob('!') + sig + tob('?') + msg - - -def cookie_decode(data, key): - ''' Verify and decode an encoded string. Return an object or None.''' - data = tob(data) - if cookie_is_encoded(data): - sig, msg = data.split(tob('?'), 1) - if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())): - return pickle.loads(base64.b64decode(msg)) - return None - - -def cookie_is_encoded(data): - ''' Return True if the argument looks like a encoded cookie.''' - return bool(data.startswith(tob('!')) and tob('?') in data) - - -def html_escape(string): - ''' Escape HTML special characters ``&<>`` and quotes ``'"``. ''' - return string.replace('&','&').replace('<','<').replace('>','>')\ - .replace('"','"').replace("'",''') - - -def html_quote(string): - ''' Escape and quote a string to be used as an HTTP attribute.''' - return '"%s"' % html_escape(string).replace('\n','%#10;')\ - .replace('\r',' ').replace('\t','	') - - -def yieldroutes(func): - """ Return a generator for routes that match the signature (name, args) - of the func parameter. This may yield more than one route if the function - takes optional keyword arguments. The output is best described by example:: - - a() -> '/a' - b(x, y) -> '/b/:x/:y' - c(x, y=5) -> '/c/:x' and '/c/:x/:y' - d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y' - """ - import inspect # Expensive module. Only import if necessary. - path = '/' + func.__name__.replace('__','/').lstrip('/') - spec = inspect.getargspec(func) - argc = len(spec[0]) - len(spec[3] or []) - path += ('/:%s' * argc) % tuple(spec[0][:argc]) - yield path - for arg in spec[0][argc:]: - path += '/:%s' % arg - yield path - - -def path_shift(script_name, path_info, shift=1): - ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. - - :return: The modified paths. - :param script_name: The SCRIPT_NAME path. - :param script_name: The PATH_INFO path. - :param shift: The number of path fragments to shift. May be negative to - change the shift direction. (default: 1) - ''' - if shift == 0: return script_name, path_info - pathlist = path_info.strip('/').split('/') - scriptlist = script_name.strip('/').split('/') - if pathlist and pathlist[0] == '': pathlist = [] - if scriptlist and scriptlist[0] == '': scriptlist = [] - if shift > 0 and shift <= len(pathlist): - moved = pathlist[:shift] - scriptlist = scriptlist + moved - pathlist = pathlist[shift:] - elif shift < 0 and shift >= -len(scriptlist): - moved = scriptlist[shift:] - pathlist = moved + pathlist - scriptlist = scriptlist[:shift] - else: - empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO' - raise AssertionError("Cannot shift. Nothing left from %s" % empty) - new_script_name = '/' + '/'.join(scriptlist) - new_path_info = '/' + '/'.join(pathlist) - if path_info.endswith('/') and pathlist: new_path_info += '/' - return new_script_name, new_path_info - - -def validate(**vkargs): - """ - Validates and manipulates keyword arguments by user defined callables. - Handles ValueError and missing arguments by raising HTTPError(403). - """ - depr('Use route wildcard filters instead.') - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kargs): - for key, value in vkargs.iteritems(): - if key not in kargs: - abort(403, 'Missing parameter: %s' % key) - try: - kargs[key] = value(kargs[key]) - except ValueError: - abort(403, 'Wrong parameter format for: %s' % key) - return func(*args, **kargs) - return wrapper - return decorator - - -def auth_basic(check, realm="private", text="Access denied"): - ''' Callback decorator to require HTTP auth (basic). - TODO: Add route(check_auth=...) parameter. ''' - def decorator(func): - def wrapper(*a, **ka): - user, password = request.auth or (None, None) - if user is None or not check(user, password): - response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm - return HTTPError(401, text) - return func(*a, **ka) - return wrapper - return decorator - - -def make_default_app_wrapper(name): - ''' Return a callable that relays calls to the current default app. ''' - @functools.wraps(getattr(Bottle, name)) - def wrapper(*a, **ka): - return getattr(app(), name)(*a, **ka) - return wrapper - - -for name in '''route get post put delete error mount - hook install uninstall'''.split(): - globals()[name] = make_default_app_wrapper(name) -url = make_default_app_wrapper('get_url') -del name - - - - - - -############################################################################### -# Server Adapter ############################################################### -############################################################################### - - -class ServerAdapter(object): - quiet = False - def __init__(self, host='127.0.0.1', port=8080, **config): - self.options = config - self.host = host - self.port = int(port) - - def run(self, handler): # pragma: no cover - pass - - def __repr__(self): - args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) - return "%s(%s)" % (self.__class__.__name__, args) - - -class CGIServer(ServerAdapter): - quiet = True - def run(self, handler): # pragma: no cover - from wsgiref.handlers import CGIHandler - def fixed_environ(environ, start_response): - environ.setdefault('PATH_INFO', '') - return handler(environ, start_response) - CGIHandler().run(fixed_environ) - - -class FlupFCGIServer(ServerAdapter): - def run(self, handler): # pragma: no cover - import flup.server.fcgi - self.options.setdefault('bindAddress', (self.host, self.port)) - flup.server.fcgi.WSGIServer(handler, **self.options).run() - - -class WSGIRefServer(ServerAdapter): - def run(self, handler): # pragma: no cover - from wsgiref.simple_server import make_server, WSGIRequestHandler - if self.quiet: - class QuietHandler(WSGIRequestHandler): - def log_request(*args, **kw): pass - self.options['handler_class'] = QuietHandler - srv = make_server(self.host, self.port, handler, **self.options) - srv.serve_forever() - - -class CherryPyServer(ServerAdapter): - def run(self, handler): # pragma: no cover - from cherrypy import wsgiserver - server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) - try: - server.start() - finally: - server.stop() - - -class PasteServer(ServerAdapter): - def run(self, handler): # pragma: no cover - from paste import httpserver - if not self.quiet: - from paste.translogger import TransLogger - handler = TransLogger(handler) - httpserver.serve(handler, host=self.host, port=str(self.port), - **self.options) - - -class MeinheldServer(ServerAdapter): - def run(self, handler): - from meinheld import server - server.listen((self.host, self.port)) - server.run(handler) - - -class FapwsServer(ServerAdapter): - """ Extremely fast webserver using libev. See http://www.fapws.org/ """ - def run(self, handler): # pragma: no cover - import fapws._evwsgi as evwsgi - from fapws import base, config - port = self.port - if float(config.SERVER_IDENT[-2:]) > 0.4: - # fapws3 silently changed its API in 0.5 - port = str(port) - evwsgi.start(self.host, port) - # fapws3 never releases the GIL. Complain upstream. I tried. No luck. - if 'BOTTLE_CHILD' in os.environ and not self.quiet: - print "WARNING: Auto-reloading does not work with Fapws3." - print " (Fapws3 breaks python thread support)" - evwsgi.set_base_module(base) - def app(environ, start_response): - environ['wsgi.multiprocess'] = False - return handler(environ, start_response) - evwsgi.wsgi_cb(('', app)) - evwsgi.run() - - -class TornadoServer(ServerAdapter): - """ The super hyped asynchronous server by facebook. Untested. """ - def run(self, handler): # pragma: no cover - import tornado.wsgi, tornado.httpserver, tornado.ioloop - container = tornado.wsgi.WSGIContainer(handler) - server = tornado.httpserver.HTTPServer(container) - server.listen(port=self.port) - tornado.ioloop.IOLoop.instance().start() - - -class AppEngineServer(ServerAdapter): - """ Adapter for Google App Engine. """ - quiet = True - def run(self, handler): - from google.appengine.ext.webapp import util - # A main() function in the handler script enables 'App Caching'. - # Lets makes sure it is there. This _really_ improves performance. - module = sys.modules.get('__main__') - if module and not hasattr(module, 'main'): - module.main = lambda: util.run_wsgi_app(handler) - util.run_wsgi_app(handler) - - -class TwistedServer(ServerAdapter): - """ Untested. """ - def run(self, handler): - from twisted.web import server, wsgi - from twisted.python.threadpool import ThreadPool - from twisted.internet import reactor - thread_pool = ThreadPool() - thread_pool.start() - reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) - factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) - reactor.listenTCP(self.port, factory, interface=self.host) - reactor.run() - - -class DieselServer(ServerAdapter): - """ Untested. """ - def run(self, handler): - from diesel.protocols.wsgi import WSGIApplication - app = WSGIApplication(handler, port=self.port) - app.run() - - -class GeventServer(ServerAdapter): - """ Untested. Options: - - * `monkey` (default: True) fixes the stdlib to use greenthreads. - * `fast` (default: False) uses libevent's http server, but has some - issues: No streaming, no pipelining, no SSL. - """ - def run(self, handler): - from gevent import wsgi as wsgi_fast, pywsgi, monkey, local - if self.options.get('monkey', True): - if not threading.local is local.local: monkey.patch_all() - wsgi = wsgi_fast if self.options.get('fast') else pywsgi - wsgi.WSGIServer((self.host, self.port), handler).serve_forever() - - -class GunicornServer(ServerAdapter): - """ Untested. See http://gunicorn.org/configure.html for options. """ - def run(self, handler): - from gunicorn.app.base import Application - - config = {'bind': "%s:%d" % (self.host, int(self.port))} - config.update(self.options) - - class GunicornApplication(Application): - def init(self, parser, opts, args): - return config - - def load(self): - return handler - - GunicornApplication().run() - - -class EventletServer(ServerAdapter): - """ Untested """ - def run(self, handler): - from eventlet import wsgi, listen - wsgi.server(listen((self.host, self.port)), handler) - - -class RocketServer(ServerAdapter): - """ Untested. """ - def run(self, handler): - from rocket import Rocket - server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler }) - server.start() - - -class BjoernServer(ServerAdapter): - """ Fast server written in C: https://github.com/jonashaag/bjoern """ - def run(self, handler): - from bjoern import run - run(handler, self.host, self.port) - - -class AutoServer(ServerAdapter): - """ Untested. """ - adapters = [PasteServer, CherryPyServer, TwistedServer, WSGIRefServer] - def run(self, handler): - for sa in self.adapters: - try: - return sa(self.host, self.port, **self.options).run(handler) - except ImportError: - pass - -server_names = { - 'cgi': CGIServer, - 'flup': FlupFCGIServer, - 'wsgiref': WSGIRefServer, - 'cherrypy': CherryPyServer, - 'paste': PasteServer, - 'fapws3': FapwsServer, - 'tornado': TornadoServer, - 'gae': AppEngineServer, - 'twisted': TwistedServer, - 'diesel': DieselServer, - 'meinheld': MeinheldServer, - 'gunicorn': GunicornServer, - 'eventlet': EventletServer, - 'gevent': GeventServer, - 'rocket': RocketServer, - 'bjoern' : BjoernServer, - 'auto': AutoServer, -} - - - - - - -############################################################################### -# Application Control ########################################################## -############################################################################### - - -def load(target, **namespace): - """ Import a module or fetch an object from a module. - - * ``package.module`` returns `module` as a module object. - * ``pack.mod:name`` returns the module variable `name` from `pack.mod`. - * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result. - - The last form accepts not only function calls, but any type of - expression. Keyword arguments passed to this function are available as - local variables. Example: ``import_string('re:compile(x)', x='[a-z]')`` - """ - module, target = target.split(":", 1) if ':' in target else (target, None) - if module not in sys.modules: __import__(module) - if not target: return sys.modules[module] - if target.isalnum(): return getattr(sys.modules[module], target) - package_name = module.split('.')[0] - namespace[package_name] = sys.modules[package_name] - return eval('%s.%s' % (module, target), namespace) - - -def load_app(target): - """ Load a bottle application from a module and make sure that the import - does not affect the current default application, but returns a separate - application object. See :func:`load` for the target parameter. """ - global NORUN; NORUN, nr_old = True, NORUN - try: - tmp = default_app.push() # Create a new "default application" - rv = load(target) # Import the target module - return rv if callable(rv) else tmp - finally: - default_app.remove(tmp) # Remove the temporary added default application - NORUN = nr_old - -def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, - interval=1, reloader=False, quiet=False, plugins=None, **kargs): - """ Start a server instance. This method blocks until the server terminates. - - :param app: WSGI application or target string supported by - :func:`load_app`. (default: :func:`default_app`) - :param server: Server adapter to use. See :data:`server_names` keys - for valid names or pass a :class:`ServerAdapter` subclass. - (default: `wsgiref`) - :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on - all interfaces including the external one. (default: 127.0.0.1) - :param port: Server port to bind to. Values below 1024 require root - privileges. (default: 8080) - :param reloader: Start auto-reloading server? (default: False) - :param interval: Auto-reloader interval in seconds (default: 1) - :param quiet: Suppress output to stdout and stderr? (default: False) - :param options: Options passed to the server adapter. - """ - if NORUN: return - if reloader and not os.environ.get('BOTTLE_CHILD'): - try: - fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') - os.close(fd) # We only need this file to exist. We never write to it - while os.path.exists(lockfile): - args = [sys.executable] + sys.argv - environ = os.environ.copy() - environ['BOTTLE_CHILD'] = 'true' - environ['BOTTLE_LOCKFILE'] = lockfile - p = subprocess.Popen(args, env=environ) - while p.poll() is None: # Busy wait... - os.utime(lockfile, None) # I am alive! - time.sleep(interval) - if p.poll() != 3: - if os.path.exists(lockfile): os.unlink(lockfile) - sys.exit(p.poll()) - except KeyboardInterrupt: - pass - finally: - if os.path.exists(lockfile): - os.unlink(lockfile) - return - - stderr = sys.stderr.write - - try: - app = app or default_app() - if isinstance(app, basestring): - app = load_app(app) - if not callable(app): - raise ValueError("Application is not callable: %r" % app) - - for plugin in plugins or []: - app.install(plugin) - - if server in server_names: - server = server_names.get(server) - if isinstance(server, basestring): - server = load(server) - if isinstance(server, type): - server = server(host=host, port=port, **kargs) - if not isinstance(server, ServerAdapter): - raise ValueError("Unknown or unsupported server: %r" % server) - - server.quiet = server.quiet or quiet - if not server.quiet: - stderr("Bottle server starting up (using %s)...\n" % repr(server)) - stderr("Listening on http://%s:%d/\n" % (server.host, server.port)) - stderr("Hit Ctrl-C to quit.\n\n") - - if reloader: - lockfile = os.environ.get('BOTTLE_LOCKFILE') - bgcheck = FileCheckerThread(lockfile, interval) - with bgcheck: - server.run(app) - if bgcheck.status == 'reload': - sys.exit(3) - else: - server.run(app) - except KeyboardInterrupt: - pass - except (SyntaxError, ImportError): - if not reloader: raise - if not getattr(server, 'quiet', False): print_exc() - sys.exit(3) - finally: - if not getattr(server, 'quiet', False): stderr('Shutdown...\n') - - -class FileCheckerThread(threading.Thread): - ''' Interrupt main-thread as soon as a changed module file is detected, - the lockfile gets deleted or gets to old. ''' - - def __init__(self, lockfile, interval): - threading.Thread.__init__(self) - self.lockfile, self.interval = lockfile, interval - #: Is one of 'reload', 'error' or 'exit' - self.status = None - - def run(self): - exists = os.path.exists - mtime = lambda path: os.stat(path).st_mtime - files = dict() - - for module in sys.modules.values(): - path = getattr(module, '__file__', '') - if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] - if path and exists(path): files[path] = mtime(path) - - while not self.status: - if not exists(self.lockfile)\ - or mtime(self.lockfile) < time.time() - self.interval - 5: - self.status = 'error' - thread.interrupt_main() - for path, lmtime in files.iteritems(): - if not exists(path) or mtime(path) > lmtime: - self.status = 'reload' - thread.interrupt_main() - break - time.sleep(self.interval) - - def __enter__(self): - self.start() - - def __exit__(self, exc_type, exc_val, exc_tb): - if not self.status: self.status = 'exit' # silent exit - self.join() - return issubclass(exc_type, KeyboardInterrupt) - - - - - -############################################################################### -# Template Adapters ############################################################ -############################################################################### - - -class TemplateError(HTTPError): - def __init__(self, message): - HTTPError.__init__(self, 500, message) - - -class BaseTemplate(object): - """ Base class and minimal API for template adapters """ - extensions = ['tpl','html','thtml','stpl'] - settings = {} #used in prepare() - defaults = {} #used in render() - - def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings): - """ Create a new template. - If the source parameter (str or buffer) is missing, the name argument - is used to guess a template filename. Subclasses can assume that - self.source and/or self.filename are set. Both are strings. - The lookup, encoding and settings parameters are stored as instance - variables. - The lookup parameter stores a list containing directory paths. - The encoding parameter should be used to decode byte strings or files. - The settings parameter contains a dict for engine-specific settings. - """ - self.name = name - self.source = source.read() if hasattr(source, 'read') else source - self.filename = source.filename if hasattr(source, 'filename') else None - self.lookup = map(os.path.abspath, lookup) - self.encoding = encoding - self.settings = self.settings.copy() # Copy from class variable - self.settings.update(settings) # Apply - if not self.source and self.name: - self.filename = self.search(self.name, self.lookup) - if not self.filename: - raise TemplateError('Template %s not found.' % repr(name)) - if not self.source and not self.filename: - raise TemplateError('No template specified.') - self.prepare(**self.settings) - - @classmethod - def search(cls, name, lookup=[]): - """ Search name in all directories specified in lookup. - First without, then with common extensions. Return first hit. """ - if os.path.isfile(name): return name - for spath in lookup: - fname = os.path.join(spath, name) - if os.path.isfile(fname): - return fname - for ext in cls.extensions: - if os.path.isfile('%s.%s' % (fname, ext)): - return '%s.%s' % (fname, ext) - - @classmethod - def global_config(cls, key, *args): - ''' This reads or sets the global settings stored in class.settings. ''' - if args: - cls.settings = cls.settings.copy() # Make settings local to class - cls.settings[key] = args[0] - else: - return cls.settings[key] - - def prepare(self, **options): - """ Run preparations (parsing, caching, ...). - It should be possible to call this again to refresh a template or to - update settings. - """ - raise NotImplementedError - - def render(self, *args, **kwargs): - """ Render the template with the specified local variables and return - a single byte or unicode string. If it is a byte string, the encoding - must match self.encoding. This method must be thread-safe! - Local variables may be provided in dictionaries (*args) - or directly, as keywords (**kwargs). - """ - raise NotImplementedError - - -class MakoTemplate(BaseTemplate): - def prepare(self, **options): - from mako.template import Template - from mako.lookup import TemplateLookup - options.update({'input_encoding':self.encoding}) - options.setdefault('format_exceptions', bool(DEBUG)) - lookup = TemplateLookup(directories=self.lookup, **options) - if self.source: - self.tpl = Template(self.source, lookup=lookup, **options) - else: - self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options) - - def render(self, *args, **kwargs): - for dictarg in args: kwargs.update(dictarg) - _defaults = self.defaults.copy() - _defaults.update(kwargs) - return self.tpl.render(**_defaults) - - -class CheetahTemplate(BaseTemplate): - def prepare(self, **options): - from Cheetah.Template import Template - self.context = threading.local() - self.context.vars = {} - options['searchList'] = [self.context.vars] - if self.source: - self.tpl = Template(source=self.source, **options) - else: - self.tpl = Template(file=self.filename, **options) - - def render(self, *args, **kwargs): - for dictarg in args: kwargs.update(dictarg) - self.context.vars.update(self.defaults) - self.context.vars.update(kwargs) - out = str(self.tpl) - self.context.vars.clear() - return out - - -class Jinja2Template(BaseTemplate): - def prepare(self, filters=None, tests=None, **kwargs): - from jinja2 import Environment, FunctionLoader - if 'prefix' in kwargs: # TODO: to be removed after a while - raise RuntimeError('The keyword argument `prefix` has been removed. ' - 'Use the full jinja2 environment name line_statement_prefix instead.') - self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) - if filters: self.env.filters.update(filters) - if tests: self.env.tests.update(tests) - if self.source: - self.tpl = self.env.from_string(self.source) - else: - self.tpl = self.env.get_template(self.filename) - - def render(self, *args, **kwargs): - for dictarg in args: kwargs.update(dictarg) - _defaults = self.defaults.copy() - _defaults.update(kwargs) - return self.tpl.render(**_defaults) - - def loader(self, name): - fname = self.search(name, self.lookup) - if fname: - with open(fname, "rb") as f: - return f.read().decode(self.encoding) - - -class SimpleTALTemplate(BaseTemplate): - ''' Untested! ''' - def prepare(self, **options): - from simpletal import simpleTAL - # TODO: add option to load METAL files during render - if self.source: - self.tpl = simpleTAL.compileHTMLTemplate(self.source) - else: - with open(self.filename, 'rb') as fp: - self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read())) - - def render(self, *args, **kwargs): - from simpletal import simpleTALES - for dictarg in args: kwargs.update(dictarg) - # TODO: maybe reuse a context instead of always creating one - context = simpleTALES.Context() - for k,v in self.defaults.items(): - context.addGlobal(k, v) - for k,v in kwargs.items(): - context.addGlobal(k, v) - output = StringIO() - self.tpl.expand(context, output) - return output.getvalue() - - -class SimpleTemplate(BaseTemplate): - blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while', - 'with', 'def', 'class') - dedent_blocks = ('elif', 'else', 'except', 'finally') - - @lazy_attribute - def re_pytokens(cls): - ''' This matches comments and all kinds of quoted strings but does - NOT match comments (#...) within quoted strings. (trust me) ''' - return re.compile(r''' - (''(?!')|""(?!")|'{6}|"{6} # Empty strings (all 4 types) - |'(?:[^\\']|\\.)+?' # Single quotes (') - |"(?:[^\\"]|\\.)+?" # Double quotes (") - |'{3}(?:[^\\]|\\.|\n)+?'{3} # Triple-quoted strings (') - |"{3}(?:[^\\]|\\.|\n)+?"{3} # Triple-quoted strings (") - |\#.* # Comments - )''', re.VERBOSE) - - def prepare(self, escape_func=html_escape, noescape=False, **kwargs): - self.cache = {} - enc = self.encoding - self._str = lambda x: touni(x, enc) - self._escape = lambda x: escape_func(touni(x, enc)) - if noescape: - self._str, self._escape = self._escape, self._str - - @classmethod - def split_comment(cls, code): - """ Removes comments (#...) from python code. """ - if '#' not in code: return code - #: Remove comments only (leave quoted strings as they are) - subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0) - return re.sub(cls.re_pytokens, subf, code) - - @cached_property - def co(self): - return compile(self.code, self.filename or '<string>', 'exec') - - @cached_property - def code(self): - stack = [] # Current Code indentation - lineno = 0 # Current line of code - ptrbuffer = [] # Buffer for printable strings and token tuple instances - codebuffer = [] # Buffer for generated python code - multiline = dedent = oneline = False - template = self.source or open(self.filename, 'rb').read() - - def yield_tokens(line): - for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)): - if i % 2: - if part.startswith('!'): yield 'RAW', part[1:] - else: yield 'CMD', part - else: yield 'TXT', part - - def flush(): # Flush the ptrbuffer - if not ptrbuffer: return - cline = '' - for line in ptrbuffer: - for token, value in line: - if token == 'TXT': cline += repr(value) - elif token == 'RAW': cline += '_str(%s)' % value - elif token == 'CMD': cline += '_escape(%s)' % value - cline += ', ' - cline = cline[:-2] + '\\\n' - cline = cline[:-2] - if cline[:-1].endswith('\\\\\\\\\\n'): - cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr' - cline = '_printlist([' + cline + '])' - del ptrbuffer[:] # Do this before calling code() again - code(cline) - - def code(stmt): - for line in stmt.splitlines(): - codebuffer.append(' ' * len(stack) + line.strip()) - - for line in template.splitlines(True): - lineno += 1 - line = line if isinstance(line, unicode)\ - else unicode(line, encoding=self.encoding) - if lineno <= 2: - m = re.search(r"%.*coding[:=]\s*([-\w\.]+)", line) - if m: self.encoding = m.group(1) - if m: line = line.replace('coding','coding (removed)') - if line.strip()[:2].count('%') == 1: - line = line.split('%',1)[1].lstrip() # Full line following the % - cline = self.split_comment(line).strip() - cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0] - flush() # You are actually reading this? Good luck, it's a mess :) - if cmd in self.blocks or multiline: - cmd = multiline or cmd - dedent = cmd in self.dedent_blocks # "else:" - if dedent and not oneline and not multiline: - cmd = stack.pop() - code(line) - oneline = not cline.endswith(':') # "if 1: pass" - multiline = cmd if cline.endswith('\\') else False - if not oneline and not multiline: - stack.append(cmd) - elif cmd == 'end' and stack: - code('#end(%s) %s' % (stack.pop(), line.strip()[3:])) - elif cmd == 'include': - p = cline.split(None, 2)[1:] - if len(p) == 2: - code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1])) - elif p: - code("_=_include(%s, _stdout)" % repr(p[0])) - else: # Empty %include -> reverse of %rebase - code("_printlist(_base)") - elif cmd == 'rebase': - p = cline.split(None, 2)[1:] - if len(p) == 2: - code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1])) - elif p: - code("globals()['_rebase']=(%s, {})" % repr(p[0])) - else: - code(line) - else: # Line starting with text (not '%') or '%%' (escaped) - if line.strip().startswith('%%'): - line = line.replace('%%', '%', 1) - ptrbuffer.append(yield_tokens(line)) - flush() - return '\n'.join(codebuffer) + '\n' - - def subtemplate(self, _name, _stdout, *args, **kwargs): - for dictarg in args: kwargs.update(dictarg) - if _name not in self.cache: - self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) - return self.cache[_name].execute(_stdout, kwargs) - - def execute(self, _stdout, *args, **kwargs): - for dictarg in args: kwargs.update(dictarg) - env = self.defaults.copy() - env.update({'_stdout': _stdout, '_printlist': _stdout.extend, - '_include': self.subtemplate, '_str': self._str, - '_escape': self._escape, 'get': env.get, - 'setdefault': env.setdefault, 'defined': env.__contains__}) - env.update(kwargs) - eval(self.co, env) - if '_rebase' in env: - subtpl, rargs = env['_rebase'] - rargs['_base'] = _stdout[:] #copy stdout - del _stdout[:] # clear stdout - return self.subtemplate(subtpl,_stdout,rargs) - return env - - def render(self, *args, **kwargs): - """ Render the template using keyword arguments as local variables. """ - for dictarg in args: kwargs.update(dictarg) - stdout = [] - self.execute(stdout, kwargs) - return ''.join(stdout) - - -def template(*args, **kwargs): - ''' - Get a rendered template as a string iterator. - You can use a name, a filename or a template string as first parameter. - Template rendering arguments can be passed as dictionaries - or directly (as keyword arguments). - ''' - tpl = args[0] if args else None - template_adapter = kwargs.pop('template_adapter', SimpleTemplate) - if tpl not in TEMPLATES or DEBUG: - settings = kwargs.pop('template_settings', {}) - lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) - if isinstance(tpl, template_adapter): - TEMPLATES[tpl] = tpl - if settings: TEMPLATES[tpl].prepare(**settings) - elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: - TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings) - else: - TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings) - if not TEMPLATES[tpl]: - abort(500, 'Template (%s) not found' % tpl) - for dictarg in args[1:]: kwargs.update(dictarg) - return TEMPLATES[tpl].render(kwargs) - -mako_template = functools.partial(template, template_adapter=MakoTemplate) -cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) -jinja2_template = functools.partial(template, template_adapter=Jinja2Template) -simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate) - - -def view(tpl_name, **defaults): - ''' Decorator: renders a template for a handler. - The handler can control its behavior like that: - - - return a dict of template vars to fill out the template - - return something other than a dict and the view decorator will not - process the template, but return the handler result as is. - This includes returning a HTTPResponse(dict) to get, - for instance, JSON with autojson or other castfilters. - ''' - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - if isinstance(result, (dict, DictMixin)): - tplvars = defaults.copy() - tplvars.update(result) - return template(tpl_name, **tplvars) - return result - return wrapper - return decorator - -mako_view = functools.partial(view, template_adapter=MakoTemplate) -cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) -jinja2_view = functools.partial(view, template_adapter=Jinja2Template) -simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate) - - - - - - -############################################################################### -# Constants and Globals ######################################################## -############################################################################### - - -TEMPLATE_PATH = ['./', './views/'] -TEMPLATES = {} -DEBUG = False -NORUN = False # If set, run() does nothing. Used by load_app() - -#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') -HTTP_CODES = httplib.responses -HTTP_CODES[418] = "I'm a teapot" # RFC 2324 -HTTP_CODES[428] = "Precondition Required" -HTTP_CODES[429] = "Too Many Requests" -HTTP_CODES[431] = "Request Header Fields Too Large" -HTTP_CODES[511] = "Network Authentication Required" -_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.iteritems()) - -#: The default template used for error pages. Override with @error() -ERROR_PAGE_TEMPLATE = """ -%try: - %from bottle import DEBUG, HTTP_CODES, request, touni - %status_name = HTTP_CODES.get(e.status, 'Unknown').title() - <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> - <html> - <head> - <title>Error {{e.status}}: {{status_name}}</title> - <style type="text/css"> - html {background-color: #eee; font-family: sans;} - body {background-color: #fff; border: 1px solid #ddd; - padding: 15px; margin: 15px;} - pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;} - </style> - </head> - <body> - <h1>Error {{e.status}}: {{status_name}}</h1> - <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt> - caused an error:</p> - <pre>{{e.output}}</pre> - %if DEBUG and e.exception: - <h2>Exception:</h2> - <pre>{{repr(e.exception)}}</pre> - %end - %if DEBUG and e.traceback: - <h2>Traceback:</h2> - <pre>{{e.traceback}}</pre> - %end - </body> - </html> -%except ImportError: - <b>ImportError:</b> Could not generate the error page. Please add bottle to - the import path. -%end -""" - -#: A thread-safe instance of :class:`Request` representing the `current` request. -request = Request() - -#: A thread-safe instance of :class:`Response` used to build the HTTP response. -response = Response() - -#: A thread-safe namespace. Not used by Bottle. -local = threading.local() - -# Initialize app stack (create first empty Bottle app) -# BC: 0.6.4 and needed for run() -app = default_app = AppStack() -app.push() - -#: A virtual package that redirects import statements. -#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. -ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module - -if __name__ == '__main__': - opt, args, parser = _cmd_options, _cmd_args, _cmd_parser - if opt.version: - print 'Bottle', __version__; sys.exit(0) - if not args: - parser.print_help() - print '\nError: No application specified.\n' - sys.exit(1) - - try: - sys.path.insert(0, '.') - sys.modules.setdefault('bottle', sys.modules['__main__']) - except (AttributeError, ImportError), e: - parser.error(e.args[0]) - - if opt.bind and ':' in opt.bind: - host, port = opt.bind.rsplit(':', 1) - else: - host, port = (opt.bind or 'localhost'), 8080 - - debug(opt.debug) - run(args[0], host=host, port=port, server=opt.server, reloader=opt.reload, plugins=opt.plugin) - -# THE END diff --git a/module/lib/feedparser.py b/module/lib/feedparser.py deleted file mode 100644 index a746ed8f5..000000000 --- a/module/lib/feedparser.py +++ /dev/null @@ -1,3885 +0,0 @@ -#!/usr/bin/env python -"""Universal feed parser - -Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds - -Visit http://feedparser.org/ for the latest version -Visit http://feedparser.org/docs/ for the latest documentation - -Required: Python 2.4 or later -Recommended: CJKCodecs and iconv_codec <http://cjkpython.i18n.org/> -""" - -__version__ = "5.0" -__license__ = """Copyright (c) 2002-2008, Mark Pilgrim, All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE.""" -__author__ = "Mark Pilgrim <http://diveintomark.org/>" -__contributors__ = ["Jason Diamond <http://injektilo.org/>", - "John Beimler <http://john.beimler.org/>", - "Fazal Majid <http://www.majid.info/mylos/weblog/>", - "Aaron Swartz <http://aaronsw.com/>", - "Kevin Marks <http://epeus.blogspot.com/>", - "Sam Ruby <http://intertwingly.net/>", - "Ade Oshineye <http://blog.oshineye.com/>", - "Martin Pool <http://sourcefrog.net/>", - "Kurt McKee <http://kurtmckee.org/>"] -_debug = 0 - -# HTTP "User-Agent" header to send to servers when downloading feeds. -# If you are embedding feedparser in a larger application, you should -# change this to your application name and URL. -USER_AGENT = "UniversalFeedParser/%s +http://feedparser.org/" % __version__ - -# HTTP "Accept" header to send to servers when downloading feeds. If you don't -# want to send an Accept header, set this to None. -ACCEPT_HEADER = "application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1" - -# List of preferred XML parsers, by SAX driver name. These will be tried first, -# but if they're not installed, Python will keep searching through its own list -# of pre-installed parsers until it finds one that supports everything we need. -PREFERRED_XML_PARSERS = ["drv_libxml2"] - -# If you want feedparser to automatically run HTML markup through HTML Tidy, set -# this to 1. Requires mxTidy <http://www.egenix.com/files/python/mxTidy.html> -# or utidylib <http://utidylib.berlios.de/>. -TIDY_MARKUP = 0 - -# List of Python interfaces for HTML Tidy, in order of preference. Only useful -# if TIDY_MARKUP = 1 -PREFERRED_TIDY_INTERFACES = ["uTidy", "mxTidy"] - -# If you want feedparser to automatically resolve all relative URIs, set this -# to 1. -RESOLVE_RELATIVE_URIS = 1 - -# If you want feedparser to automatically sanitize all potentially unsafe -# HTML content, set this to 1. -SANITIZE_HTML = 1 - -# ---------- Python 3 modules (make it work if possible) ---------- -try: - import rfc822 -except ImportError: - from email import _parseaddr as rfc822 - -try: - # Python 3.1 introduces bytes.maketrans and simultaneously - # deprecates string.maketrans; use bytes.maketrans if possible - _maketrans = bytes.maketrans -except (NameError, AttributeError): - import string - _maketrans = string.maketrans - -# base64 support for Atom feeds that contain embedded binary data -try: - import base64, binascii - # Python 3.1 deprecates decodestring in favor of decodebytes - _base64decode = getattr(base64, 'decodebytes', base64.decodestring) -except: - base64 = binascii = None - -def _s2bytes(s): - # Convert a UTF-8 str to bytes if the interpreter is Python 3 - try: - return bytes(s, 'utf8') - except (NameError, TypeError): - # In Python 2.5 and below, bytes doesn't exist (NameError) - # In Python 2.6 and above, bytes and str are the same (TypeError) - return s - -def _l2bytes(l): - # Convert a list of ints to bytes if the interpreter is Python 3 - try: - if bytes is not str: - # In Python 2.6 and above, this call won't raise an exception - # but it will return bytes([65]) as '[65]' instead of 'A' - return bytes(l) - raise NameError - except NameError: - return ''.join(map(chr, l)) - -# If you want feedparser to allow all URL schemes, set this to () -# List culled from Python's urlparse documentation at: -# http://docs.python.org/library/urlparse.html -# as well as from "URI scheme" at Wikipedia: -# https://secure.wikimedia.org/wikipedia/en/wiki/URI_scheme -# Many more will likely need to be added! -ACCEPTABLE_URI_SCHEMES = ( - 'file', 'ftp', 'gopher', 'h323', 'hdl', 'http', 'https', 'imap', 'mailto', - 'mms', 'news', 'nntp', 'prospero', 'rsync', 'rtsp', 'rtspu', 'sftp', - 'shttp', 'sip', 'sips', 'snews', 'svn', 'svn+ssh', 'telnet', 'wais', - # Additional common-but-unofficial schemes - 'aim', 'callto', 'cvs', 'facetime', 'feed', 'git', 'gtalk', 'irc', 'ircs', - 'irc6', 'itms', 'mms', 'msnim', 'skype', 'ssh', 'smb', 'svn', 'ymsg', -) -#ACCEPTABLE_URI_SCHEMES = () - -# ---------- required modules (should come with any Python distribution) ---------- -import sgmllib, re, sys, copy, urlparse, time, types, cgi, urllib, urllib2, datetime -try: - from io import BytesIO as _StringIO -except ImportError: - try: - from cStringIO import StringIO as _StringIO - except: - from StringIO import StringIO as _StringIO - -# ---------- optional modules (feedparser will work without these, but with reduced functionality) ---------- - -# gzip is included with most Python distributions, but may not be available if you compiled your own -try: - import gzip -except: - gzip = None -try: - import zlib -except: - zlib = None - -# If a real XML parser is available, feedparser will attempt to use it. feedparser has -# been tested with the built-in SAX parser, PyXML, and libxml2. On platforms where the -# Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some -# versions of FreeBSD), feedparser will quietly fall back on regex-based parsing. -try: - import xml.sax - xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers - from xml.sax.saxutils import escape as _xmlescape - _XML_AVAILABLE = 1 -except: - _XML_AVAILABLE = 0 - def _xmlescape(data,entities={}): - data = data.replace('&', '&') - data = data.replace('>', '>') - data = data.replace('<', '<') - for char, entity in entities: - data = data.replace(char, entity) - return data - -# cjkcodecs and iconv_codec provide support for more character encodings. -# Both are available from http://cjkpython.i18n.org/ -try: - import cjkcodecs.aliases -except: - pass -try: - import iconv_codec -except: - pass - -# chardet library auto-detects character encodings -# Download from http://chardet.feedparser.org/ -try: - import chardet - if _debug: - import chardet.constants - chardet.constants._debug = 1 -except: - chardet = None - -# reversable htmlentitydefs mappings for Python 2.2 -try: - from htmlentitydefs import name2codepoint, codepoint2name -except: - import htmlentitydefs - name2codepoint={} - codepoint2name={} - for (name,codepoint) in htmlentitydefs.entitydefs.iteritems(): - if codepoint.startswith('&#'): codepoint=unichr(int(codepoint[2:-1])) - name2codepoint[name]=ord(codepoint) - codepoint2name[ord(codepoint)]=name - -# BeautifulSoup parser used for parsing microformats from embedded HTML content -# http://www.crummy.com/software/BeautifulSoup/ -# feedparser is tested with BeautifulSoup 3.0.x, but it might work with the -# older 2.x series. If it doesn't, and you can figure out why, I'll accept a -# patch and modify the compatibility statement accordingly. -try: - import BeautifulSoup -except: - BeautifulSoup = None - -# ---------- don't touch these ---------- -class ThingsNobodyCaresAboutButMe(Exception): pass -class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): pass -class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): pass -class NonXMLContentType(ThingsNobodyCaresAboutButMe): pass -class UndeclaredNamespace(Exception): pass - -sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') -sgmllib.special = re.compile('<!') -sgmllib.charref = re.compile('&#(\d+|[xX][0-9a-fA-F]+);') - -if sgmllib.endbracket.search(' <').start(0): - class EndBracketRegEx: - def __init__(self): - # Overriding the built-in sgmllib.endbracket regex allows the - # parser to find angle brackets embedded in element attributes. - self.endbracket = re.compile('''([^'"<>]|"[^"]*"(?=>|/|\s|\w+=)|'[^']*'(?=>|/|\s|\w+=))*(?=[<>])|.*?(?=[<>])''') - def search(self,string,index=0): - match = self.endbracket.match(string,index) - if match is not None: - # Returning a new object in the calling thread's context - # resolves a thread-safety. - return EndBracketMatch(match) - return None - class EndBracketMatch: - def __init__(self, match): - self.match = match - def start(self, n): - return self.match.end(n) - sgmllib.endbracket = EndBracketRegEx() - -SUPPORTED_VERSIONS = {'': 'unknown', - 'rss090': 'RSS 0.90', - 'rss091n': 'RSS 0.91 (Netscape)', - 'rss091u': 'RSS 0.91 (Userland)', - 'rss092': 'RSS 0.92', - 'rss093': 'RSS 0.93', - 'rss094': 'RSS 0.94', - 'rss20': 'RSS 2.0', - 'rss10': 'RSS 1.0', - 'rss': 'RSS (unknown version)', - 'atom01': 'Atom 0.1', - 'atom02': 'Atom 0.2', - 'atom03': 'Atom 0.3', - 'atom10': 'Atom 1.0', - 'atom': 'Atom (unknown version)', - 'cdf': 'CDF', - 'hotrss': 'Hot RSS' - } - -try: - UserDict = dict -except NameError: - # Python 2.1 does not have dict - from UserDict import UserDict - def dict(aList): - rc = {} - for k, v in aList: - rc[k] = v - return rc - -class FeedParserDict(UserDict): - keymap = {'channel': 'feed', - 'items': 'entries', - 'guid': 'id', - 'date': 'updated', - 'date_parsed': 'updated_parsed', - 'description': ['summary', 'subtitle'], - 'url': ['href'], - 'modified': 'updated', - 'modified_parsed': 'updated_parsed', - 'issued': 'published', - 'issued_parsed': 'published_parsed', - 'copyright': 'rights', - 'copyright_detail': 'rights_detail', - 'tagline': 'subtitle', - 'tagline_detail': 'subtitle_detail'} - def __getitem__(self, key): - if key == 'category': - return UserDict.__getitem__(self, 'tags')[0]['term'] - if key == 'enclosures': - norel = lambda link: FeedParserDict([(name,value) for (name,value) in link.items() if name!='rel']) - return [norel(link) for link in UserDict.__getitem__(self, 'links') if link['rel']=='enclosure'] - if key == 'license': - for link in UserDict.__getitem__(self, 'links'): - if link['rel']=='license' and link.has_key('href'): - return link['href'] - if key == 'categories': - return [(tag['scheme'], tag['term']) for tag in UserDict.__getitem__(self, 'tags')] - realkey = self.keymap.get(key, key) - if type(realkey) == types.ListType: - for k in realkey: - if UserDict.__contains__(self, k): - return UserDict.__getitem__(self, k) - if UserDict.__contains__(self, key): - return UserDict.__getitem__(self, key) - return UserDict.__getitem__(self, realkey) - - def __setitem__(self, key, value): - for k in self.keymap.keys(): - if key == k: - key = self.keymap[k] - if type(key) == types.ListType: - key = key[0] - return UserDict.__setitem__(self, key, value) - - def get(self, key, default=None): - if self.has_key(key): - return self[key] - else: - return default - - def setdefault(self, key, value): - if not self.has_key(key): - self[key] = value - return self[key] - - def has_key(self, key): - try: - return hasattr(self, key) or UserDict.__contains__(self, key) - except AttributeError: - return False - # This alias prevents the 2to3 tool from changing the semantics of the - # __contains__ function below and exhausting the maximum recursion depth - __has_key = has_key - - def __getattr__(self, key): - try: - return self.__dict__[key] - except KeyError: - pass - try: - assert not key.startswith('_') - return self.__getitem__(key) - except: - raise AttributeError, "object has no attribute '%s'" % key - - def __setattr__(self, key, value): - if key.startswith('_') or key == 'data': - self.__dict__[key] = value - else: - return self.__setitem__(key, value) - - def __contains__(self, key): - return self.__has_key(key) - -def zopeCompatibilityHack(): - global FeedParserDict - del FeedParserDict - def FeedParserDict(aDict=None): - rc = {} - if aDict: - rc.update(aDict) - return rc - -_ebcdic_to_ascii_map = None -def _ebcdic_to_ascii(s): - global _ebcdic_to_ascii_map - if not _ebcdic_to_ascii_map: - emap = ( - 0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15, - 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31, - 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7, - 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26, - 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33, - 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94, - 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63, - 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34, - 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200,201, - 202,106,107,108,109,110,111,112,113,114,203,204,205,206,207,208, - 209,126,115,116,117,118,119,120,121,122,210,211,212,213,214,215, - 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231, - 123,65,66,67,68,69,70,71,72,73,232,233,234,235,236,237, - 125,74,75,76,77,78,79,80,81,82,238,239,240,241,242,243, - 92,159,83,84,85,86,87,88,89,90,244,245,246,247,248,249, - 48,49,50,51,52,53,54,55,56,57,250,251,252,253,254,255 - ) - _ebcdic_to_ascii_map = _maketrans( \ - _l2bytes(range(256)), _l2bytes(emap)) - return s.translate(_ebcdic_to_ascii_map) - -_cp1252 = { - unichr(128): unichr(8364), # euro sign - unichr(130): unichr(8218), # single low-9 quotation mark - unichr(131): unichr( 402), # latin small letter f with hook - unichr(132): unichr(8222), # double low-9 quotation mark - unichr(133): unichr(8230), # horizontal ellipsis - unichr(134): unichr(8224), # dagger - unichr(135): unichr(8225), # double dagger - unichr(136): unichr( 710), # modifier letter circumflex accent - unichr(137): unichr(8240), # per mille sign - unichr(138): unichr( 352), # latin capital letter s with caron - unichr(139): unichr(8249), # single left-pointing angle quotation mark - unichr(140): unichr( 338), # latin capital ligature oe - unichr(142): unichr( 381), # latin capital letter z with caron - unichr(145): unichr(8216), # left single quotation mark - unichr(146): unichr(8217), # right single quotation mark - unichr(147): unichr(8220), # left double quotation mark - unichr(148): unichr(8221), # right double quotation mark - unichr(149): unichr(8226), # bullet - unichr(150): unichr(8211), # en dash - unichr(151): unichr(8212), # em dash - unichr(152): unichr( 732), # small tilde - unichr(153): unichr(8482), # trade mark sign - unichr(154): unichr( 353), # latin small letter s with caron - unichr(155): unichr(8250), # single right-pointing angle quotation mark - unichr(156): unichr( 339), # latin small ligature oe - unichr(158): unichr( 382), # latin small letter z with caron - unichr(159): unichr( 376)} # latin capital letter y with diaeresis - -_urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)') -def _urljoin(base, uri): - uri = _urifixer.sub(r'\1\3', uri) - try: - return urlparse.urljoin(base, uri) - except: - uri = urlparse.urlunparse([urllib.quote(part) for part in urlparse.urlparse(uri)]) - return urlparse.urljoin(base, uri) - -class _FeedParserMixin: - namespaces = {'': '', - 'http://backend.userland.com/rss': '', - 'http://blogs.law.harvard.edu/tech/rss': '', - 'http://purl.org/rss/1.0/': '', - 'http://my.netscape.com/rdf/simple/0.9/': '', - 'http://example.com/newformat#': '', - 'http://example.com/necho': '', - 'http://purl.org/echo/': '', - 'uri/of/echo/namespace#': '', - 'http://purl.org/pie/': '', - 'http://purl.org/atom/ns#': '', - 'http://www.w3.org/2005/Atom': '', - 'http://purl.org/rss/1.0/modules/rss091#': '', - - 'http://webns.net/mvcb/': 'admin', - 'http://purl.org/rss/1.0/modules/aggregation/': 'ag', - 'http://purl.org/rss/1.0/modules/annotate/': 'annotate', - 'http://media.tangent.org/rss/1.0/': 'audio', - 'http://backend.userland.com/blogChannelModule': 'blogChannel', - 'http://web.resource.org/cc/': 'cc', - 'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons', - 'http://purl.org/rss/1.0/modules/company': 'co', - 'http://purl.org/rss/1.0/modules/content/': 'content', - 'http://my.theinfo.org/changed/1.0/rss/': 'cp', - 'http://purl.org/dc/elements/1.1/': 'dc', - 'http://purl.org/dc/terms/': 'dcterms', - 'http://purl.org/rss/1.0/modules/email/': 'email', - 'http://purl.org/rss/1.0/modules/event/': 'ev', - 'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner', - 'http://freshmeat.net/rss/fm/': 'fm', - 'http://xmlns.com/foaf/0.1/': 'foaf', - 'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo', - 'http://postneo.com/icbm/': 'icbm', - 'http://purl.org/rss/1.0/modules/image/': 'image', - 'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes', - 'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes', - 'http://purl.org/rss/1.0/modules/link/': 'l', - 'http://search.yahoo.com/mrss': 'media', - #Version 1.1.2 of the Media RSS spec added the trailing slash on the namespace - 'http://search.yahoo.com/mrss/': 'media', - 'http://madskills.com/public/xml/rss/module/pingback/': 'pingback', - 'http://prismstandard.org/namespaces/1.2/basic/': 'prism', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf', - 'http://www.w3.org/2000/01/rdf-schema#': 'rdfs', - 'http://purl.org/rss/1.0/modules/reference/': 'ref', - 'http://purl.org/rss/1.0/modules/richequiv/': 'reqv', - 'http://purl.org/rss/1.0/modules/search/': 'search', - 'http://purl.org/rss/1.0/modules/slash/': 'slash', - 'http://schemas.xmlsoap.org/soap/envelope/': 'soap', - 'http://purl.org/rss/1.0/modules/servicestatus/': 'ss', - 'http://hacks.benhammersley.com/rss/streaming/': 'str', - 'http://purl.org/rss/1.0/modules/subscription/': 'sub', - 'http://purl.org/rss/1.0/modules/syndication/': 'sy', - 'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf', - 'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo', - 'http://purl.org/rss/1.0/modules/threading/': 'thr', - 'http://purl.org/rss/1.0/modules/textinput/': 'ti', - 'http://madskills.com/public/xml/rss/module/trackback/':'trackback', - 'http://wellformedweb.org/commentAPI/': 'wfw', - 'http://purl.org/rss/1.0/modules/wiki/': 'wiki', - 'http://www.w3.org/1999/xhtml': 'xhtml', - 'http://www.w3.org/1999/xlink': 'xlink', - 'http://www.w3.org/XML/1998/namespace': 'xml' -} - _matchnamespaces = {} - - can_be_relative_uri = ['link', 'id', 'wfw_comment', 'wfw_commentrss', 'docs', 'url', 'href', 'comments', 'icon', 'logo'] - can_contain_relative_uris = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description'] - can_contain_dangerous_markup = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description'] - html_types = ['text/html', 'application/xhtml+xml'] - - def __init__(self, baseuri=None, baselang=None, encoding='utf-8'): - if _debug: sys.stderr.write('initializing FeedParser\n') - if not self._matchnamespaces: - for k, v in self.namespaces.items(): - self._matchnamespaces[k.lower()] = v - self.feeddata = FeedParserDict() # feed-level data - self.encoding = encoding # character encoding - self.entries = [] # list of entry-level data - self.version = '' # feed type/version, see SUPPORTED_VERSIONS - self.namespacesInUse = {} # dictionary of namespaces defined by the feed - - # the following are used internally to track state; - # this is really out of control and should be refactored - self.infeed = 0 - self.inentry = 0 - self.incontent = 0 - self.intextinput = 0 - self.inimage = 0 - self.inauthor = 0 - self.incontributor = 0 - self.inpublisher = 0 - self.insource = 0 - self.sourcedata = FeedParserDict() - self.contentparams = FeedParserDict() - self._summaryKey = None - self.namespacemap = {} - self.elementstack = [] - self.basestack = [] - self.langstack = [] - self.baseuri = baseuri or '' - self.lang = baselang or None - self.svgOK = 0 - self.hasTitle = 0 - if baselang: - self.feeddata['language'] = baselang.replace('_','-') - - def unknown_starttag(self, tag, attrs): - if _debug: sys.stderr.write('start %s with %s\n' % (tag, attrs)) - # normalize attrs - attrs = [(k.lower(), v) for k, v in attrs] - attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs] - # the sgml parser doesn't handle entities in attributes, but - # strict xml parsers do -- account for this difference - if isinstance(self, _LooseFeedParser): - attrs = [(k, v.replace('&', '&')) for k, v in attrs] - - # track xml:base and xml:lang - attrsD = dict(attrs) - baseuri = attrsD.get('xml:base', attrsD.get('base')) or self.baseuri - if type(baseuri) != type(u''): - try: - baseuri = unicode(baseuri, self.encoding) - except: - baseuri = unicode(baseuri, 'iso-8859-1') - # ensure that self.baseuri is always an absolute URI that - # uses a whitelisted URI scheme (e.g. not `javscript:`) - if self.baseuri: - self.baseuri = _makeSafeAbsoluteURI(self.baseuri, baseuri) or self.baseuri - else: - self.baseuri = _urljoin(self.baseuri, baseuri) - lang = attrsD.get('xml:lang', attrsD.get('lang')) - if lang == '': - # xml:lang could be explicitly set to '', we need to capture that - lang = None - elif lang is None: - # if no xml:lang is specified, use parent lang - lang = self.lang - if lang: - if tag in ('feed', 'rss', 'rdf:RDF'): - self.feeddata['language'] = lang.replace('_','-') - self.lang = lang - self.basestack.append(self.baseuri) - self.langstack.append(lang) - - # track namespaces - for prefix, uri in attrs: - if prefix.startswith('xmlns:'): - self.trackNamespace(prefix[6:], uri) - elif prefix == 'xmlns': - self.trackNamespace(None, uri) - - # track inline content - if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'): - if tag in ['xhtml:div', 'div']: return # typepad does this 10/2007 - # element declared itself as escaped markup, but it isn't really - self.contentparams['type'] = 'application/xhtml+xml' - if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml': - if tag.find(':') <> -1: - prefix, tag = tag.split(':', 1) - namespace = self.namespacesInUse.get(prefix, '') - if tag=='math' and namespace=='http://www.w3.org/1998/Math/MathML': - attrs.append(('xmlns',namespace)) - if tag=='svg' and namespace=='http://www.w3.org/2000/svg': - attrs.append(('xmlns',namespace)) - if tag == 'svg': self.svgOK += 1 - return self.handle_data('<%s%s>' % (tag, self.strattrs(attrs)), escape=0) - - # match namespaces - if tag.find(':') <> -1: - prefix, suffix = tag.split(':', 1) - else: - prefix, suffix = '', tag - prefix = self.namespacemap.get(prefix, prefix) - if prefix: - prefix = prefix + '_' - - # special hack for better tracking of empty textinput/image elements in illformed feeds - if (not prefix) and tag not in ('title', 'link', 'description', 'name'): - self.intextinput = 0 - if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'): - self.inimage = 0 - - # call special handler (if defined) or default handler - methodname = '_start_' + prefix + suffix - try: - method = getattr(self, methodname) - return method(attrsD) - except AttributeError: - # Since there's no handler or something has gone wrong we explicitly add the element and its attributes - unknown_tag = prefix + suffix - if len(attrsD) == 0: - # No attributes so merge it into the encosing dictionary - return self.push(unknown_tag, 1) - else: - # Has attributes so create it in its own dictionary - context = self._getContext() - context[unknown_tag] = attrsD - - def unknown_endtag(self, tag): - if _debug: sys.stderr.write('end %s\n' % tag) - # match namespaces - if tag.find(':') <> -1: - prefix, suffix = tag.split(':', 1) - else: - prefix, suffix = '', tag - prefix = self.namespacemap.get(prefix, prefix) - if prefix: - prefix = prefix + '_' - if suffix == 'svg' and self.svgOK: self.svgOK -= 1 - - # call special handler (if defined) or default handler - methodname = '_end_' + prefix + suffix - try: - if self.svgOK: raise AttributeError() - method = getattr(self, methodname) - method() - except AttributeError: - self.pop(prefix + suffix) - - # track inline content - if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'): - # element declared itself as escaped markup, but it isn't really - if tag in ['xhtml:div', 'div']: return # typepad does this 10/2007 - self.contentparams['type'] = 'application/xhtml+xml' - if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml': - tag = tag.split(':')[-1] - self.handle_data('</%s>' % tag, escape=0) - - # track xml:base and xml:lang going out of scope - if self.basestack: - self.basestack.pop() - if self.basestack and self.basestack[-1]: - self.baseuri = self.basestack[-1] - if self.langstack: - self.langstack.pop() - if self.langstack: # and (self.langstack[-1] is not None): - self.lang = self.langstack[-1] - - def handle_charref(self, ref): - # called for each character reference, e.g. for ' ', ref will be '160' - if not self.elementstack: return - ref = ref.lower() - if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'): - text = '&#%s;' % ref - else: - if ref[0] == 'x': - c = int(ref[1:], 16) - else: - c = int(ref) - text = unichr(c).encode('utf-8') - self.elementstack[-1][2].append(text) - - def handle_entityref(self, ref): - # called for each entity reference, e.g. for '©', ref will be 'copy' - if not self.elementstack: return - if _debug: sys.stderr.write('entering handle_entityref with %s\n' % ref) - if ref in ('lt', 'gt', 'quot', 'amp', 'apos'): - text = '&%s;' % ref - elif ref in self.entities.keys(): - text = self.entities[ref] - if text.startswith('&#') and text.endswith(';'): - return self.handle_entityref(text) - else: - try: name2codepoint[ref] - except KeyError: text = '&%s;' % ref - else: text = unichr(name2codepoint[ref]).encode('utf-8') - self.elementstack[-1][2].append(text) - - def handle_data(self, text, escape=1): - # called for each block of plain text, i.e. outside of any tag and - # not containing any character or entity references - if not self.elementstack: return - if escape and self.contentparams.get('type') == 'application/xhtml+xml': - text = _xmlescape(text) - self.elementstack[-1][2].append(text) - - def handle_comment(self, text): - # called for each comment, e.g. <!-- insert message here --> - pass - - def handle_pi(self, text): - # called for each processing instruction, e.g. <?instruction> - pass - - def handle_decl(self, text): - pass - - def parse_declaration(self, i): - # override internal declaration handler to handle CDATA blocks - if _debug: sys.stderr.write('entering parse_declaration\n') - if self.rawdata[i:i+9] == '<![CDATA[': - k = self.rawdata.find(']]>', i) - if k == -1: - # CDATA block began but didn't finish - k = len(self.rawdata) - return k - self.handle_data(_xmlescape(self.rawdata[i+9:k]), 0) - return k+3 - else: - k = self.rawdata.find('>', i) - if k >= 0: - return k+1 - else: - # We have an incomplete CDATA block. - return k - - def mapContentType(self, contentType): - contentType = contentType.lower() - if contentType == 'text': - contentType = 'text/plain' - elif contentType == 'html': - contentType = 'text/html' - elif contentType == 'xhtml': - contentType = 'application/xhtml+xml' - return contentType - - def trackNamespace(self, prefix, uri): - loweruri = uri.lower() - if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not self.version: - self.version = 'rss090' - if loweruri == 'http://purl.org/rss/1.0/' and not self.version: - self.version = 'rss10' - if loweruri == 'http://www.w3.org/2005/atom' and not self.version: - self.version = 'atom10' - if loweruri.find('backend.userland.com/rss') <> -1: - # match any backend.userland.com namespace - uri = 'http://backend.userland.com/rss' - loweruri = uri - if self._matchnamespaces.has_key(loweruri): - self.namespacemap[prefix] = self._matchnamespaces[loweruri] - self.namespacesInUse[self._matchnamespaces[loweruri]] = uri - else: - self.namespacesInUse[prefix or ''] = uri - - def resolveURI(self, uri): - return _urljoin(self.baseuri or '', uri) - - def decodeEntities(self, element, data): - return data - - def strattrs(self, attrs): - return ''.join([' %s="%s"' % (t[0],_xmlescape(t[1],{'"':'"'})) for t in attrs]) - - def push(self, element, expectingText): - self.elementstack.append([element, expectingText, []]) - - def pop(self, element, stripWhitespace=1): - if not self.elementstack: return - if self.elementstack[-1][0] != element: return - - element, expectingText, pieces = self.elementstack.pop() - - if self.version == 'atom10' and self.contentparams.get('type','text') == 'application/xhtml+xml': - # remove enclosing child element, but only if it is a <div> and - # only if all the remaining content is nested underneath it. - # This means that the divs would be retained in the following: - # <div>foo</div><div>bar</div> - while pieces and len(pieces)>1 and not pieces[-1].strip(): - del pieces[-1] - while pieces and len(pieces)>1 and not pieces[0].strip(): - del pieces[0] - if pieces and (pieces[0] == '<div>' or pieces[0].startswith('<div ')) and pieces[-1]=='</div>': - depth = 0 - for piece in pieces[:-1]: - if piece.startswith('</'): - depth -= 1 - if depth == 0: break - elif piece.startswith('<') and not piece.endswith('/>'): - depth += 1 - else: - pieces = pieces[1:-1] - - # Ensure each piece is a str for Python 3 - for (i, v) in enumerate(pieces): - if not isinstance(v, basestring): - pieces[i] = v.decode('utf-8') - - output = ''.join(pieces) - if stripWhitespace: - output = output.strip() - if not expectingText: return output - - # decode base64 content - if base64 and self.contentparams.get('base64', 0): - try: - output = _base64decode(output) - except binascii.Error: - pass - except binascii.Incomplete: - pass - except TypeError: - # In Python 3, base64 takes and outputs bytes, not str - # This may not be the most correct way to accomplish this - output = _base64decode(output.encode('utf-8')).decode('utf-8') - - # resolve relative URIs - if (element in self.can_be_relative_uri) and output: - output = self.resolveURI(output) - - # decode entities within embedded markup - if not self.contentparams.get('base64', 0): - output = self.decodeEntities(element, output) - - if self.lookslikehtml(output): - self.contentparams['type']='text/html' - - # remove temporary cruft from contentparams - try: - del self.contentparams['mode'] - except KeyError: - pass - try: - del self.contentparams['base64'] - except KeyError: - pass - - is_htmlish = self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types - # resolve relative URIs within embedded markup - if is_htmlish and RESOLVE_RELATIVE_URIS: - if element in self.can_contain_relative_uris: - output = _resolveRelativeURIs(output, self.baseuri, self.encoding, self.contentparams.get('type', 'text/html')) - - # parse microformats - # (must do this before sanitizing because some microformats - # rely on elements that we sanitize) - if is_htmlish and element in ['content', 'description', 'summary']: - mfresults = _parseMicroformats(output, self.baseuri, self.encoding) - if mfresults: - for tag in mfresults.get('tags', []): - self._addTag(tag['term'], tag['scheme'], tag['label']) - for enclosure in mfresults.get('enclosures', []): - self._start_enclosure(enclosure) - for xfn in mfresults.get('xfn', []): - self._addXFN(xfn['relationships'], xfn['href'], xfn['name']) - vcard = mfresults.get('vcard') - if vcard: - self._getContext()['vcard'] = vcard - - # sanitize embedded markup - if is_htmlish and SANITIZE_HTML: - if element in self.can_contain_dangerous_markup: - output = _sanitizeHTML(output, self.encoding, self.contentparams.get('type', 'text/html')) - - if self.encoding and type(output) != type(u''): - try: - output = unicode(output, self.encoding) - except: - pass - - # address common error where people take data that is already - # utf-8, presume that it is iso-8859-1, and re-encode it. - if self.encoding in ('utf-8', 'utf-8_INVALID_PYTHON_3') and type(output) == type(u''): - try: - output = unicode(output.encode('iso-8859-1'), 'utf-8') - except: - pass - - # map win-1252 extensions to the proper code points - if type(output) == type(u''): - output = u''.join([c in _cp1252.keys() and _cp1252[c] or c for c in output]) - - # categories/tags/keywords/whatever are handled in _end_category - if element == 'category': - return output - - if element == 'title' and self.hasTitle: - return output - - # store output in appropriate place(s) - if self.inentry and not self.insource: - if element == 'content': - self.entries[-1].setdefault(element, []) - contentparams = copy.deepcopy(self.contentparams) - contentparams['value'] = output - self.entries[-1][element].append(contentparams) - elif element == 'link': - if not self.inimage: - # query variables in urls in link elements are improperly - # converted from `?a=1&b=2` to `?a=1&b;=2` as if they're - # unhandled character references. fix this special case. - output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output) - self.entries[-1][element] = output - if output: - self.entries[-1]['links'][-1]['href'] = output - else: - if element == 'description': - element = 'summary' - self.entries[-1][element] = output - if self.incontent: - contentparams = copy.deepcopy(self.contentparams) - contentparams['value'] = output - self.entries[-1][element + '_detail'] = contentparams - elif (self.infeed or self.insource):# and (not self.intextinput) and (not self.inimage): - context = self._getContext() - if element == 'description': - element = 'subtitle' - context[element] = output - if element == 'link': - # fix query variables; see above for the explanation - output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output) - context[element] = output - context['links'][-1]['href'] = output - elif self.incontent: - contentparams = copy.deepcopy(self.contentparams) - contentparams['value'] = output - context[element + '_detail'] = contentparams - return output - - def pushContent(self, tag, attrsD, defaultContentType, expectingText): - self.incontent += 1 - if self.lang: self.lang=self.lang.replace('_','-') - self.contentparams = FeedParserDict({ - 'type': self.mapContentType(attrsD.get('type', defaultContentType)), - 'language': self.lang, - 'base': self.baseuri}) - self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams) - self.push(tag, expectingText) - - def popContent(self, tag): - value = self.pop(tag) - self.incontent -= 1 - self.contentparams.clear() - return value - - # a number of elements in a number of RSS variants are nominally plain - # text, but this is routinely ignored. This is an attempt to detect - # the most common cases. As false positives often result in silent - # data loss, this function errs on the conservative side. - def lookslikehtml(self, s): - if self.version.startswith('atom'): return - if self.contentparams.get('type','text/html') != 'text/plain': return - - # must have a close tag or a entity reference to qualify - if not (re.search(r'</(\w+)>',s) or re.search("&#?\w+;",s)): return - - # all tags must be in a restricted subset of valid HTML tags - if filter(lambda t: t.lower() not in _HTMLSanitizer.acceptable_elements, - re.findall(r'</?(\w+)',s)): return - - # all entities must have been defined as valid HTML entities - from htmlentitydefs import entitydefs - if filter(lambda e: e not in entitydefs.keys(), - re.findall(r'&(\w+);',s)): return - - return 1 - - def _mapToStandardPrefix(self, name): - colonpos = name.find(':') - if colonpos <> -1: - prefix = name[:colonpos] - suffix = name[colonpos+1:] - prefix = self.namespacemap.get(prefix, prefix) - name = prefix + ':' + suffix - return name - - def _getAttribute(self, attrsD, name): - return attrsD.get(self._mapToStandardPrefix(name)) - - def _isBase64(self, attrsD, contentparams): - if attrsD.get('mode', '') == 'base64': - return 1 - if self.contentparams['type'].startswith('text/'): - return 0 - if self.contentparams['type'].endswith('+xml'): - return 0 - if self.contentparams['type'].endswith('/xml'): - return 0 - return 1 - - def _itsAnHrefDamnIt(self, attrsD): - href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None))) - if href: - try: - del attrsD['url'] - except KeyError: - pass - try: - del attrsD['uri'] - except KeyError: - pass - attrsD['href'] = href - return attrsD - - def _save(self, key, value, overwrite=False): - context = self._getContext() - if overwrite: - context[key] = value - else: - context.setdefault(key, value) - - def _start_rss(self, attrsD): - versionmap = {'0.91': 'rss091u', - '0.92': 'rss092', - '0.93': 'rss093', - '0.94': 'rss094'} - #If we're here then this is an RSS feed. - #If we don't have a version or have a version that starts with something - #other than RSS then there's been a mistake. Correct it. - if not self.version or not self.version.startswith('rss'): - attr_version = attrsD.get('version', '') - version = versionmap.get(attr_version) - if version: - self.version = version - elif attr_version.startswith('2.'): - self.version = 'rss20' - else: - self.version = 'rss' - - def _start_dlhottitles(self, attrsD): - self.version = 'hotrss' - - def _start_channel(self, attrsD): - self.infeed = 1 - self._cdf_common(attrsD) - _start_feedinfo = _start_channel - - def _cdf_common(self, attrsD): - if attrsD.has_key('lastmod'): - self._start_modified({}) - self.elementstack[-1][-1] = attrsD['lastmod'] - self._end_modified() - if attrsD.has_key('href'): - self._start_link({}) - self.elementstack[-1][-1] = attrsD['href'] - self._end_link() - - def _start_feed(self, attrsD): - self.infeed = 1 - versionmap = {'0.1': 'atom01', - '0.2': 'atom02', - '0.3': 'atom03'} - if not self.version: - attr_version = attrsD.get('version') - version = versionmap.get(attr_version) - if version: - self.version = version - else: - self.version = 'atom' - - def _end_channel(self): - self.infeed = 0 - _end_feed = _end_channel - - def _start_image(self, attrsD): - context = self._getContext() - if not self.inentry: - context.setdefault('image', FeedParserDict()) - self.inimage = 1 - self.hasTitle = 0 - self.push('image', 0) - - def _end_image(self): - self.pop('image') - self.inimage = 0 - - def _start_textinput(self, attrsD): - context = self._getContext() - context.setdefault('textinput', FeedParserDict()) - self.intextinput = 1 - self.hasTitle = 0 - self.push('textinput', 0) - _start_textInput = _start_textinput - - def _end_textinput(self): - self.pop('textinput') - self.intextinput = 0 - _end_textInput = _end_textinput - - def _start_author(self, attrsD): - self.inauthor = 1 - self.push('author', 1) - # Append a new FeedParserDict when expecting an author - context = self._getContext() - context.setdefault('authors', []) - context['authors'].append(FeedParserDict()) - _start_managingeditor = _start_author - _start_dc_author = _start_author - _start_dc_creator = _start_author - _start_itunes_author = _start_author - - def _end_author(self): - self.pop('author') - self.inauthor = 0 - self._sync_author_detail() - _end_managingeditor = _end_author - _end_dc_author = _end_author - _end_dc_creator = _end_author - _end_itunes_author = _end_author - - def _start_itunes_owner(self, attrsD): - self.inpublisher = 1 - self.push('publisher', 0) - - def _end_itunes_owner(self): - self.pop('publisher') - self.inpublisher = 0 - self._sync_author_detail('publisher') - - def _start_contributor(self, attrsD): - self.incontributor = 1 - context = self._getContext() - context.setdefault('contributors', []) - context['contributors'].append(FeedParserDict()) - self.push('contributor', 0) - - def _end_contributor(self): - self.pop('contributor') - self.incontributor = 0 - - def _start_dc_contributor(self, attrsD): - self.incontributor = 1 - context = self._getContext() - context.setdefault('contributors', []) - context['contributors'].append(FeedParserDict()) - self.push('name', 0) - - def _end_dc_contributor(self): - self._end_name() - self.incontributor = 0 - - def _start_name(self, attrsD): - self.push('name', 0) - _start_itunes_name = _start_name - - def _end_name(self): - value = self.pop('name') - if self.inpublisher: - self._save_author('name', value, 'publisher') - elif self.inauthor: - self._save_author('name', value) - elif self.incontributor: - self._save_contributor('name', value) - elif self.intextinput: - context = self._getContext() - context['name'] = value - _end_itunes_name = _end_name - - def _start_width(self, attrsD): - self.push('width', 0) - - def _end_width(self): - value = self.pop('width') - try: - value = int(value) - except: - value = 0 - if self.inimage: - context = self._getContext() - context['width'] = value - - def _start_height(self, attrsD): - self.push('height', 0) - - def _end_height(self): - value = self.pop('height') - try: - value = int(value) - except: - value = 0 - if self.inimage: - context = self._getContext() - context['height'] = value - - def _start_url(self, attrsD): - self.push('href', 1) - _start_homepage = _start_url - _start_uri = _start_url - - def _end_url(self): - value = self.pop('href') - if self.inauthor: - self._save_author('href', value) - elif self.incontributor: - self._save_contributor('href', value) - _end_homepage = _end_url - _end_uri = _end_url - - def _start_email(self, attrsD): - self.push('email', 0) - _start_itunes_email = _start_email - - def _end_email(self): - value = self.pop('email') - if self.inpublisher: - self._save_author('email', value, 'publisher') - elif self.inauthor: - self._save_author('email', value) - elif self.incontributor: - self._save_contributor('email', value) - _end_itunes_email = _end_email - - def _getContext(self): - if self.insource: - context = self.sourcedata - elif self.inimage and self.feeddata.has_key('image'): - context = self.feeddata['image'] - elif self.intextinput: - context = self.feeddata['textinput'] - elif self.inentry: - context = self.entries[-1] - else: - context = self.feeddata - return context - - def _save_author(self, key, value, prefix='author'): - context = self._getContext() - context.setdefault(prefix + '_detail', FeedParserDict()) - context[prefix + '_detail'][key] = value - self._sync_author_detail() - context.setdefault('authors', [FeedParserDict()]) - context['authors'][-1][key] = value - - def _save_contributor(self, key, value): - context = self._getContext() - context.setdefault('contributors', [FeedParserDict()]) - context['contributors'][-1][key] = value - - def _sync_author_detail(self, key='author'): - context = self._getContext() - detail = context.get('%s_detail' % key) - if detail: - name = detail.get('name') - email = detail.get('email') - if name and email: - context[key] = '%s (%s)' % (name, email) - elif name: - context[key] = name - elif email: - context[key] = email - else: - author, email = context.get(key), None - if not author: return - emailmatch = re.search(r'''(([a-zA-Z0-9\_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))(\?subject=\S+)?''', author) - if emailmatch: - email = emailmatch.group(0) - # probably a better way to do the following, but it passes all the tests - author = author.replace(email, '') - author = author.replace('()', '') - author = author.replace('<>', '') - author = author.replace('<>', '') - author = author.strip() - if author and (author[0] == '('): - author = author[1:] - if author and (author[-1] == ')'): - author = author[:-1] - author = author.strip() - if author or email: - context.setdefault('%s_detail' % key, FeedParserDict()) - if author: - context['%s_detail' % key]['name'] = author - if email: - context['%s_detail' % key]['email'] = email - - def _start_subtitle(self, attrsD): - self.pushContent('subtitle', attrsD, 'text/plain', 1) - _start_tagline = _start_subtitle - _start_itunes_subtitle = _start_subtitle - - def _end_subtitle(self): - self.popContent('subtitle') - _end_tagline = _end_subtitle - _end_itunes_subtitle = _end_subtitle - - def _start_rights(self, attrsD): - self.pushContent('rights', attrsD, 'text/plain', 1) - _start_dc_rights = _start_rights - _start_copyright = _start_rights - - def _end_rights(self): - self.popContent('rights') - _end_dc_rights = _end_rights - _end_copyright = _end_rights - - def _start_item(self, attrsD): - self.entries.append(FeedParserDict()) - self.push('item', 0) - self.inentry = 1 - self.guidislink = 0 - self.hasTitle = 0 - id = self._getAttribute(attrsD, 'rdf:about') - if id: - context = self._getContext() - context['id'] = id - self._cdf_common(attrsD) - _start_entry = _start_item - _start_product = _start_item - - def _end_item(self): - self.pop('item') - self.inentry = 0 - _end_entry = _end_item - - def _start_dc_language(self, attrsD): - self.push('language', 1) - _start_language = _start_dc_language - - def _end_dc_language(self): - self.lang = self.pop('language') - _end_language = _end_dc_language - - def _start_dc_publisher(self, attrsD): - self.push('publisher', 1) - _start_webmaster = _start_dc_publisher - - def _end_dc_publisher(self): - self.pop('publisher') - self._sync_author_detail('publisher') - _end_webmaster = _end_dc_publisher - - def _start_published(self, attrsD): - self.push('published', 1) - _start_dcterms_issued = _start_published - _start_issued = _start_published - - def _end_published(self): - value = self.pop('published') - self._save('published_parsed', _parse_date(value), overwrite=True) - _end_dcterms_issued = _end_published - _end_issued = _end_published - - def _start_updated(self, attrsD): - self.push('updated', 1) - _start_modified = _start_updated - _start_dcterms_modified = _start_updated - _start_pubdate = _start_updated - _start_dc_date = _start_updated - _start_lastbuilddate = _start_updated - - def _end_updated(self): - value = self.pop('updated') - parsed_value = _parse_date(value) - self._save('updated_parsed', parsed_value, overwrite=True) - _end_modified = _end_updated - _end_dcterms_modified = _end_updated - _end_pubdate = _end_updated - _end_dc_date = _end_updated - _end_lastbuilddate = _end_updated - - def _start_created(self, attrsD): - self.push('created', 1) - _start_dcterms_created = _start_created - - def _end_created(self): - value = self.pop('created') - self._save('created_parsed', _parse_date(value), overwrite=True) - _end_dcterms_created = _end_created - - def _start_expirationdate(self, attrsD): - self.push('expired', 1) - - def _end_expirationdate(self): - self._save('expired_parsed', _parse_date(self.pop('expired')), overwrite=True) - - def _start_cc_license(self, attrsD): - context = self._getContext() - value = self._getAttribute(attrsD, 'rdf:resource') - attrsD = FeedParserDict() - attrsD['rel']='license' - if value: attrsD['href']=value - context.setdefault('links', []).append(attrsD) - - def _start_creativecommons_license(self, attrsD): - self.push('license', 1) - _start_creativeCommons_license = _start_creativecommons_license - - def _end_creativecommons_license(self): - value = self.pop('license') - context = self._getContext() - attrsD = FeedParserDict() - attrsD['rel']='license' - if value: attrsD['href']=value - context.setdefault('links', []).append(attrsD) - del context['license'] - _end_creativeCommons_license = _end_creativecommons_license - - def _addXFN(self, relationships, href, name): - context = self._getContext() - xfn = context.setdefault('xfn', []) - value = FeedParserDict({'relationships': relationships, 'href': href, 'name': name}) - if value not in xfn: - xfn.append(value) - - def _addTag(self, term, scheme, label): - context = self._getContext() - tags = context.setdefault('tags', []) - if (not term) and (not scheme) and (not label): return - value = FeedParserDict({'term': term, 'scheme': scheme, 'label': label}) - if value not in tags: - tags.append(value) - - def _start_category(self, attrsD): - if _debug: sys.stderr.write('entering _start_category with %s\n' % repr(attrsD)) - term = attrsD.get('term') - scheme = attrsD.get('scheme', attrsD.get('domain')) - label = attrsD.get('label') - self._addTag(term, scheme, label) - self.push('category', 1) - _start_dc_subject = _start_category - _start_keywords = _start_category - - def _start_media_category(self, attrsD): - attrsD.setdefault('scheme', 'http://search.yahoo.com/mrss/category_schema') - self._start_category(attrsD) - - def _end_itunes_keywords(self): - for term in self.pop('itunes_keywords').split(): - self._addTag(term, 'http://www.itunes.com/', None) - - def _start_itunes_category(self, attrsD): - self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None) - self.push('category', 1) - - def _end_category(self): - value = self.pop('category') - if not value: return - context = self._getContext() - tags = context['tags'] - if value and len(tags) and not tags[-1]['term']: - tags[-1]['term'] = value - else: - self._addTag(value, None, None) - _end_dc_subject = _end_category - _end_keywords = _end_category - _end_itunes_category = _end_category - _end_media_category = _end_category - - def _start_cloud(self, attrsD): - self._getContext()['cloud'] = FeedParserDict(attrsD) - - def _start_link(self, attrsD): - attrsD.setdefault('rel', 'alternate') - if attrsD['rel'] == 'self': - attrsD.setdefault('type', 'application/atom+xml') - else: - attrsD.setdefault('type', 'text/html') - context = self._getContext() - attrsD = self._itsAnHrefDamnIt(attrsD) - if attrsD.has_key('href'): - attrsD['href'] = self.resolveURI(attrsD['href']) - expectingText = self.infeed or self.inentry or self.insource - context.setdefault('links', []) - if not (self.inentry and self.inimage): - context['links'].append(FeedParserDict(attrsD)) - if attrsD.has_key('href'): - expectingText = 0 - if (attrsD.get('rel') == 'alternate') and (self.mapContentType(attrsD.get('type')) in self.html_types): - context['link'] = attrsD['href'] - else: - self.push('link', expectingText) - _start_producturl = _start_link - - def _end_link(self): - value = self.pop('link') - context = self._getContext() - _end_producturl = _end_link - - def _start_guid(self, attrsD): - self.guidislink = (attrsD.get('ispermalink', 'true') == 'true') - self.push('id', 1) - - def _end_guid(self): - value = self.pop('id') - self._save('guidislink', self.guidislink and not self._getContext().has_key('link')) - if self.guidislink: - # guid acts as link, but only if 'ispermalink' is not present or is 'true', - # and only if the item doesn't already have a link element - self._save('link', value) - - def _start_title(self, attrsD): - if self.svgOK: return self.unknown_starttag('title', attrsD.items()) - self.pushContent('title', attrsD, 'text/plain', self.infeed or self.inentry or self.insource) - _start_dc_title = _start_title - _start_media_title = _start_title - - def _end_title(self): - if self.svgOK: return - value = self.popContent('title') - if not value: return - context = self._getContext() - self.hasTitle = 1 - _end_dc_title = _end_title - - def _end_media_title(self): - hasTitle = self.hasTitle - self._end_title() - self.hasTitle = hasTitle - - def _start_description(self, attrsD): - context = self._getContext() - if context.has_key('summary'): - self._summaryKey = 'content' - self._start_content(attrsD) - else: - self.pushContent('description', attrsD, 'text/html', self.infeed or self.inentry or self.insource) - _start_dc_description = _start_description - - def _start_abstract(self, attrsD): - self.pushContent('description', attrsD, 'text/plain', self.infeed or self.inentry or self.insource) - - def _end_description(self): - if self._summaryKey == 'content': - self._end_content() - else: - value = self.popContent('description') - self._summaryKey = None - _end_abstract = _end_description - _end_dc_description = _end_description - - def _start_info(self, attrsD): - self.pushContent('info', attrsD, 'text/plain', 1) - _start_feedburner_browserfriendly = _start_info - - def _end_info(self): - self.popContent('info') - _end_feedburner_browserfriendly = _end_info - - def _start_generator(self, attrsD): - if attrsD: - attrsD = self._itsAnHrefDamnIt(attrsD) - if attrsD.has_key('href'): - attrsD['href'] = self.resolveURI(attrsD['href']) - self._getContext()['generator_detail'] = FeedParserDict(attrsD) - self.push('generator', 1) - - def _end_generator(self): - value = self.pop('generator') - context = self._getContext() - if context.has_key('generator_detail'): - context['generator_detail']['name'] = value - - def _start_admin_generatoragent(self, attrsD): - self.push('generator', 1) - value = self._getAttribute(attrsD, 'rdf:resource') - if value: - self.elementstack[-1][2].append(value) - self.pop('generator') - self._getContext()['generator_detail'] = FeedParserDict({'href': value}) - - def _start_admin_errorreportsto(self, attrsD): - self.push('errorreportsto', 1) - value = self._getAttribute(attrsD, 'rdf:resource') - if value: - self.elementstack[-1][2].append(value) - self.pop('errorreportsto') - - def _start_summary(self, attrsD): - context = self._getContext() - if context.has_key('summary'): - self._summaryKey = 'content' - self._start_content(attrsD) - else: - self._summaryKey = 'summary' - self.pushContent(self._summaryKey, attrsD, 'text/plain', 1) - _start_itunes_summary = _start_summary - - def _end_summary(self): - if self._summaryKey == 'content': - self._end_content() - else: - self.popContent(self._summaryKey or 'summary') - self._summaryKey = None - _end_itunes_summary = _end_summary - - def _start_enclosure(self, attrsD): - attrsD = self._itsAnHrefDamnIt(attrsD) - context = self._getContext() - attrsD['rel']='enclosure' - context.setdefault('links', []).append(FeedParserDict(attrsD)) - - def _start_source(self, attrsD): - if 'url' in attrsD: - # This means that we're processing a source element from an RSS 2.0 feed - self.sourcedata['href'] = attrsD[u'url'] - self.push('source', 1) - self.insource = 1 - self.hasTitle = 0 - - def _end_source(self): - self.insource = 0 - value = self.pop('source') - if value: - self.sourcedata['title'] = value - self._getContext()['source'] = copy.deepcopy(self.sourcedata) - self.sourcedata.clear() - - def _start_content(self, attrsD): - self.pushContent('content', attrsD, 'text/plain', 1) - src = attrsD.get('src') - if src: - self.contentparams['src'] = src - self.push('content', 1) - - def _start_prodlink(self, attrsD): - self.pushContent('content', attrsD, 'text/html', 1) - - def _start_body(self, attrsD): - self.pushContent('content', attrsD, 'application/xhtml+xml', 1) - _start_xhtml_body = _start_body - - def _start_content_encoded(self, attrsD): - self.pushContent('content', attrsD, 'text/html', 1) - _start_fullitem = _start_content_encoded - - def _end_content(self): - copyToSummary = self.mapContentType(self.contentparams.get('type')) in (['text/plain'] + self.html_types) - value = self.popContent('content') - if copyToSummary: - self._save('summary', value) - - _end_body = _end_content - _end_xhtml_body = _end_content - _end_content_encoded = _end_content - _end_fullitem = _end_content - _end_prodlink = _end_content - - def _start_itunes_image(self, attrsD): - self.push('itunes_image', 0) - if attrsD.get('href'): - self._getContext()['image'] = FeedParserDict({'href': attrsD.get('href')}) - _start_itunes_link = _start_itunes_image - - def _end_itunes_block(self): - value = self.pop('itunes_block', 0) - self._getContext()['itunes_block'] = (value == 'yes') and 1 or 0 - - def _end_itunes_explicit(self): - value = self.pop('itunes_explicit', 0) - # Convert 'yes' -> True, 'clean' to False, and any other value to None - # False and None both evaluate as False, so the difference can be ignored - # by applications that only need to know if the content is explicit. - self._getContext()['itunes_explicit'] = (None, False, True)[(value == 'yes' and 2) or value == 'clean' or 0] - - def _start_media_content(self, attrsD): - context = self._getContext() - context.setdefault('media_content', []) - context['media_content'].append(attrsD) - - def _start_media_thumbnail(self, attrsD): - context = self._getContext() - context.setdefault('media_thumbnail', []) - self.push('url', 1) # new - context['media_thumbnail'].append(attrsD) - - def _end_media_thumbnail(self): - url = self.pop('url') - context = self._getContext() - if url is not None and len(url.strip()) != 0: - if not context['media_thumbnail'][-1].has_key('url'): - context['media_thumbnail'][-1]['url'] = url - - def _start_media_player(self, attrsD): - self.push('media_player', 0) - self._getContext()['media_player'] = FeedParserDict(attrsD) - - def _end_media_player(self): - value = self.pop('media_player') - context = self._getContext() - context['media_player']['content'] = value - - def _start_newlocation(self, attrsD): - self.push('newlocation', 1) - - def _end_newlocation(self): - url = self.pop('newlocation') - context = self._getContext() - # don't set newlocation if the context isn't right - if context is not self.feeddata: - return - context['newlocation'] = _makeSafeAbsoluteURI(self.baseuri, url.strip()) - -if _XML_AVAILABLE: - class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler): - def __init__(self, baseuri, baselang, encoding): - if _debug: sys.stderr.write('trying StrictFeedParser\n') - xml.sax.handler.ContentHandler.__init__(self) - _FeedParserMixin.__init__(self, baseuri, baselang, encoding) - self.bozo = 0 - self.exc = None - self.decls = {} - - def startPrefixMapping(self, prefix, uri): - self.trackNamespace(prefix, uri) - if uri == 'http://www.w3.org/1999/xlink': - self.decls['xmlns:'+prefix] = uri - - def startElementNS(self, name, qname, attrs): - namespace, localname = name - lowernamespace = str(namespace or '').lower() - if lowernamespace.find('backend.userland.com/rss') <> -1: - # match any backend.userland.com namespace - namespace = 'http://backend.userland.com/rss' - lowernamespace = namespace - if qname and qname.find(':') > 0: - givenprefix = qname.split(':')[0] - else: - givenprefix = None - prefix = self._matchnamespaces.get(lowernamespace, givenprefix) - if givenprefix and (prefix is None or (prefix == '' and lowernamespace == '')) and not self.namespacesInUse.has_key(givenprefix): - raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix - localname = str(localname).lower() - - # qname implementation is horribly broken in Python 2.1 (it - # doesn't report any), and slightly broken in Python 2.2 (it - # doesn't report the xml: namespace). So we match up namespaces - # with a known list first, and then possibly override them with - # the qnames the SAX parser gives us (if indeed it gives us any - # at all). Thanks to MatejC for helping me test this and - # tirelessly telling me that it didn't work yet. - attrsD, self.decls = self.decls, {} - if localname=='math' and namespace=='http://www.w3.org/1998/Math/MathML': - attrsD['xmlns']=namespace - if localname=='svg' and namespace=='http://www.w3.org/2000/svg': - attrsD['xmlns']=namespace - - if prefix: - localname = prefix.lower() + ':' + localname - elif namespace and not qname: #Expat - for name,value in self.namespacesInUse.items(): - if name and value == namespace: - localname = name + ':' + localname - break - if _debug: sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname)) - - for (namespace, attrlocalname), attrvalue in attrs._attrs.items(): - lowernamespace = (namespace or '').lower() - prefix = self._matchnamespaces.get(lowernamespace, '') - if prefix: - attrlocalname = prefix + ':' + attrlocalname - attrsD[str(attrlocalname).lower()] = attrvalue - for qname in attrs.getQNames(): - attrsD[str(qname).lower()] = attrs.getValueByQName(qname) - self.unknown_starttag(localname, attrsD.items()) - - def characters(self, text): - self.handle_data(text) - - def endElementNS(self, name, qname): - namespace, localname = name - lowernamespace = str(namespace or '').lower() - if qname and qname.find(':') > 0: - givenprefix = qname.split(':')[0] - else: - givenprefix = '' - prefix = self._matchnamespaces.get(lowernamespace, givenprefix) - if prefix: - localname = prefix + ':' + localname - elif namespace and not qname: #Expat - for name,value in self.namespacesInUse.items(): - if name and value == namespace: - localname = name + ':' + localname - break - localname = str(localname).lower() - self.unknown_endtag(localname) - - def error(self, exc): - self.bozo = 1 - self.exc = exc - - def fatalError(self, exc): - self.error(exc) - raise exc - -class _BaseHTMLProcessor(sgmllib.SGMLParser): - special = re.compile('''[<>'"]''') - bare_ampersand = re.compile("&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)") - elements_no_end_tag = [ - 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', - 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', - 'source', 'track', 'wbr' - ] - - def __init__(self, encoding, _type): - self.encoding = encoding - self._type = _type - if _debug: sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding) - sgmllib.SGMLParser.__init__(self) - - def reset(self): - self.pieces = [] - sgmllib.SGMLParser.reset(self) - - def _shorttag_replace(self, match): - tag = match.group(1) - if tag in self.elements_no_end_tag: - return '<' + tag + ' />' - else: - return '<' + tag + '></' + tag + '>' - - def parse_starttag(self,i): - j=sgmllib.SGMLParser.parse_starttag(self, i) - if self._type == 'application/xhtml+xml': - if j>2 and self.rawdata[j-2:j]=='/>': - self.unknown_endtag(self.lasttag) - return j - - def feed(self, data): - data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'<!\1', data) - #data = re.sub(r'<(\S+?)\s*?/>', self._shorttag_replace, data) # bug [ 1399464 ] Bad regexp for _shorttag_replace - data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data) - data = data.replace(''', "'") - data = data.replace('"', '"') - try: - bytes - if bytes is str: - raise NameError - self.encoding = self.encoding + '_INVALID_PYTHON_3' - except NameError: - if self.encoding and type(data) == type(u''): - data = data.encode(self.encoding) - sgmllib.SGMLParser.feed(self, data) - sgmllib.SGMLParser.close(self) - - def normalize_attrs(self, attrs): - if not attrs: return attrs - # utility method to be called by descendants - attrs = dict([(k.lower(), v) for k, v in attrs]).items() - attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs] - attrs.sort() - return attrs - - def unknown_starttag(self, tag, attrs): - # called for each start tag - # attrs is a list of (attr, value) tuples - # e.g. for <pre class='screen'>, tag='pre', attrs=[('class', 'screen')] - if _debug: sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag) - uattrs = [] - strattrs='' - if attrs: - for key, value in attrs: - value=value.replace('>','>').replace('<','<').replace('"','"') - value = self.bare_ampersand.sub("&", value) - # thanks to Kevin Marks for this breathtaking hack to deal with (valid) high-bit attribute values in UTF-8 feeds - if type(value) != type(u''): - try: - value = unicode(value, self.encoding) - except: - value = unicode(value, 'iso-8859-1') - try: - # Currently, in Python 3 the key is already a str, and cannot be decoded again - uattrs.append((unicode(key, self.encoding), value)) - except TypeError: - uattrs.append((key, value)) - strattrs = u''.join([u' %s="%s"' % (key, value) for key, value in uattrs]) - if self.encoding: - try: - strattrs=strattrs.encode(self.encoding) - except: - pass - if tag in self.elements_no_end_tag: - self.pieces.append('<%(tag)s%(strattrs)s />' % locals()) - else: - self.pieces.append('<%(tag)s%(strattrs)s>' % locals()) - - def unknown_endtag(self, tag): - # called for each end tag, e.g. for </pre>, tag will be 'pre' - # Reconstruct the original end tag. - if tag not in self.elements_no_end_tag: - self.pieces.append("</%(tag)s>" % locals()) - - def handle_charref(self, ref): - # called for each character reference, e.g. for ' ', ref will be '160' - # Reconstruct the original character reference. - if ref.startswith('x'): - value = unichr(int(ref[1:],16)) - else: - value = unichr(int(ref)) - - if value in _cp1252.keys(): - self.pieces.append('&#%s;' % hex(ord(_cp1252[value]))[1:]) - else: - self.pieces.append('&#%(ref)s;' % locals()) - - def handle_entityref(self, ref): - # called for each entity reference, e.g. for '©', ref will be 'copy' - # Reconstruct the original entity reference. - if name2codepoint.has_key(ref): - self.pieces.append('&%(ref)s;' % locals()) - else: - self.pieces.append('&%(ref)s' % locals()) - - def handle_data(self, text): - # called for each block of plain text, i.e. outside of any tag and - # not containing any character or entity references - # Store the original text verbatim. - if _debug: sys.stderr.write('_BaseHTMLProcessor, handle_data, text=%s\n' % text) - self.pieces.append(text) - - def handle_comment(self, text): - # called for each HTML comment, e.g. <!-- insert Javascript code here --> - # Reconstruct the original comment. - self.pieces.append('<!--%(text)s-->' % locals()) - - def handle_pi(self, text): - # called for each processing instruction, e.g. <?instruction> - # Reconstruct original processing instruction. - self.pieces.append('<?%(text)s>' % locals()) - - def handle_decl(self, text): - # called for the DOCTYPE, if present, e.g. - # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" - # "http://www.w3.org/TR/html4/loose.dtd"> - # Reconstruct original DOCTYPE - self.pieces.append('<!%(text)s>' % locals()) - - _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match - def _scan_name(self, i, declstartpos): - rawdata = self.rawdata - n = len(rawdata) - if i == n: - return None, -1 - m = self._new_declname_match(rawdata, i) - if m: - s = m.group() - name = s.strip() - if (i + len(s)) == n: - return None, -1 # end of buffer - return name.lower(), m.end() - else: - self.handle_data(rawdata) -# self.updatepos(declstartpos, i) - return None, -1 - - def convert_charref(self, name): - return '&#%s;' % name - - def convert_entityref(self, name): - return '&%s;' % name - - def output(self): - '''Return processed HTML as a single string''' - return ''.join([str(p) for p in self.pieces]) - -class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor): - def __init__(self, baseuri, baselang, encoding, entities): - sgmllib.SGMLParser.__init__(self) - _FeedParserMixin.__init__(self, baseuri, baselang, encoding) - _BaseHTMLProcessor.__init__(self, encoding, 'application/xhtml+xml') - self.entities=entities - - def decodeEntities(self, element, data): - data = data.replace('<', '<') - data = data.replace('<', '<') - data = data.replace('<', '<') - data = data.replace('>', '>') - data = data.replace('>', '>') - data = data.replace('>', '>') - data = data.replace('&', '&') - data = data.replace('&', '&') - data = data.replace('"', '"') - data = data.replace('"', '"') - data = data.replace(''', ''') - data = data.replace(''', ''') - if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'): - data = data.replace('<', '<') - data = data.replace('>', '>') - data = data.replace('&', '&') - data = data.replace('"', '"') - data = data.replace(''', "'") - return data - - def strattrs(self, attrs): - return ''.join([' %s="%s"' % (n,v.replace('"','"')) for n,v in attrs]) - -class _MicroformatsParser: - STRING = 1 - DATE = 2 - URI = 3 - NODE = 4 - EMAIL = 5 - - known_xfn_relationships = ['contact', 'acquaintance', 'friend', 'met', 'co-worker', 'coworker', 'colleague', 'co-resident', 'coresident', 'neighbor', 'child', 'parent', 'sibling', 'brother', 'sister', 'spouse', 'wife', 'husband', 'kin', 'relative', 'muse', 'crush', 'date', 'sweetheart', 'me'] - known_binary_extensions = ['zip','rar','exe','gz','tar','tgz','tbz2','bz2','z','7z','dmg','img','sit','sitx','hqx','deb','rpm','bz2','jar','rar','iso','bin','msi','mp2','mp3','ogg','ogm','mp4','m4v','m4a','avi','wma','wmv'] - - def __init__(self, data, baseuri, encoding): - self.document = BeautifulSoup.BeautifulSoup(data) - self.baseuri = baseuri - self.encoding = encoding - if type(data) == type(u''): - data = data.encode(encoding) - self.tags = [] - self.enclosures = [] - self.xfn = [] - self.vcard = None - - def vcardEscape(self, s): - if type(s) in (type(''), type(u'')): - s = s.replace(',', '\\,').replace(';', '\\;').replace('\n', '\\n') - return s - - def vcardFold(self, s): - s = re.sub(';+$', '', s) - sFolded = '' - iMax = 75 - sPrefix = '' - while len(s) > iMax: - sFolded += sPrefix + s[:iMax] + '\n' - s = s[iMax:] - sPrefix = ' ' - iMax = 74 - sFolded += sPrefix + s - return sFolded - - def normalize(self, s): - return re.sub(r'\s+', ' ', s).strip() - - def unique(self, aList): - results = [] - for element in aList: - if element not in results: - results.append(element) - return results - - def toISO8601(self, dt): - return time.strftime('%Y-%m-%dT%H:%M:%SZ', dt) - - def getPropertyValue(self, elmRoot, sProperty, iPropertyType=4, bAllowMultiple=0, bAutoEscape=0): - all = lambda x: 1 - sProperty = sProperty.lower() - bFound = 0 - bNormalize = 1 - propertyMatch = {'class': re.compile(r'\b%s\b' % sProperty)} - if bAllowMultiple and (iPropertyType != self.NODE): - snapResults = [] - containers = elmRoot(['ul', 'ol'], propertyMatch) - for container in containers: - snapResults.extend(container('li')) - bFound = (len(snapResults) != 0) - if not bFound: - snapResults = elmRoot(all, propertyMatch) - bFound = (len(snapResults) != 0) - if (not bFound) and (sProperty == 'value'): - snapResults = elmRoot('pre') - bFound = (len(snapResults) != 0) - bNormalize = not bFound - if not bFound: - snapResults = [elmRoot] - bFound = (len(snapResults) != 0) - arFilter = [] - if sProperty == 'vcard': - snapFilter = elmRoot(all, propertyMatch) - for node in snapFilter: - if node.findParent(all, propertyMatch): - arFilter.append(node) - arResults = [] - for node in snapResults: - if node not in arFilter: - arResults.append(node) - bFound = (len(arResults) != 0) - if not bFound: - if bAllowMultiple: return [] - elif iPropertyType == self.STRING: return '' - elif iPropertyType == self.DATE: return None - elif iPropertyType == self.URI: return '' - elif iPropertyType == self.NODE: return None - else: return None - arValues = [] - for elmResult in arResults: - sValue = None - if iPropertyType == self.NODE: - if bAllowMultiple: - arValues.append(elmResult) - continue - else: - return elmResult - sNodeName = elmResult.name.lower() - if (iPropertyType == self.EMAIL) and (sNodeName == 'a'): - sValue = (elmResult.get('href') or '').split('mailto:').pop().split('?')[0] - if sValue: - sValue = bNormalize and self.normalize(sValue) or sValue.strip() - if (not sValue) and (sNodeName == 'abbr'): - sValue = elmResult.get('title') - if sValue: - sValue = bNormalize and self.normalize(sValue) or sValue.strip() - if (not sValue) and (iPropertyType == self.URI): - if sNodeName == 'a': sValue = elmResult.get('href') - elif sNodeName == 'img': sValue = elmResult.get('src') - elif sNodeName == 'object': sValue = elmResult.get('data') - if sValue: - sValue = bNormalize and self.normalize(sValue) or sValue.strip() - if (not sValue) and (sNodeName == 'img'): - sValue = elmResult.get('alt') - if sValue: - sValue = bNormalize and self.normalize(sValue) or sValue.strip() - if not sValue: - sValue = elmResult.renderContents() - sValue = re.sub(r'<\S[^>]*>', '', sValue) - sValue = sValue.replace('\r\n', '\n') - sValue = sValue.replace('\r', '\n') - if sValue: - sValue = bNormalize and self.normalize(sValue) or sValue.strip() - if not sValue: continue - if iPropertyType == self.DATE: - sValue = _parse_date_iso8601(sValue) - if bAllowMultiple: - arValues.append(bAutoEscape and self.vcardEscape(sValue) or sValue) - else: - return bAutoEscape and self.vcardEscape(sValue) or sValue - return arValues - - def findVCards(self, elmRoot, bAgentParsing=0): - sVCards = '' - - if not bAgentParsing: - arCards = self.getPropertyValue(elmRoot, 'vcard', bAllowMultiple=1) - else: - arCards = [elmRoot] - - for elmCard in arCards: - arLines = [] - - def processSingleString(sProperty): - sValue = self.getPropertyValue(elmCard, sProperty, self.STRING, bAutoEscape=1).decode(self.encoding) - if sValue: - arLines.append(self.vcardFold(sProperty.upper() + ':' + sValue)) - return sValue or u'' - - def processSingleURI(sProperty): - sValue = self.getPropertyValue(elmCard, sProperty, self.URI) - if sValue: - sContentType = '' - sEncoding = '' - sValueKey = '' - if sValue.startswith('data:'): - sEncoding = ';ENCODING=b' - sContentType = sValue.split(';')[0].split('/').pop() - sValue = sValue.split(',', 1).pop() - else: - elmValue = self.getPropertyValue(elmCard, sProperty) - if elmValue: - if sProperty != 'url': - sValueKey = ';VALUE=uri' - sContentType = elmValue.get('type', '').strip().split('/').pop().strip() - sContentType = sContentType.upper() - if sContentType == 'OCTET-STREAM': - sContentType = '' - if sContentType: - sContentType = ';TYPE=' + sContentType.upper() - arLines.append(self.vcardFold(sProperty.upper() + sEncoding + sContentType + sValueKey + ':' + sValue)) - - def processTypeValue(sProperty, arDefaultType, arForceType=None): - arResults = self.getPropertyValue(elmCard, sProperty, bAllowMultiple=1) - for elmResult in arResults: - arType = self.getPropertyValue(elmResult, 'type', self.STRING, 1, 1) - if arForceType: - arType = self.unique(arForceType + arType) - if not arType: - arType = arDefaultType - sValue = self.getPropertyValue(elmResult, 'value', self.EMAIL, 0) - if sValue: - arLines.append(self.vcardFold(sProperty.upper() + ';TYPE=' + ','.join(arType) + ':' + sValue)) - - # AGENT - # must do this before all other properties because it is destructive - # (removes nested class="vcard" nodes so they don't interfere with - # this vcard's other properties) - arAgent = self.getPropertyValue(elmCard, 'agent', bAllowMultiple=1) - for elmAgent in arAgent: - if re.compile(r'\bvcard\b').search(elmAgent.get('class')): - sAgentValue = self.findVCards(elmAgent, 1) + '\n' - sAgentValue = sAgentValue.replace('\n', '\\n') - sAgentValue = sAgentValue.replace(';', '\\;') - if sAgentValue: - arLines.append(self.vcardFold('AGENT:' + sAgentValue)) - # Completely remove the agent element from the parse tree - elmAgent.extract() - else: - sAgentValue = self.getPropertyValue(elmAgent, 'value', self.URI, bAutoEscape=1); - if sAgentValue: - arLines.append(self.vcardFold('AGENT;VALUE=uri:' + sAgentValue)) - - # FN (full name) - sFN = processSingleString('fn') - - # N (name) - elmName = self.getPropertyValue(elmCard, 'n') - if elmName: - sFamilyName = self.getPropertyValue(elmName, 'family-name', self.STRING, bAutoEscape=1) - sGivenName = self.getPropertyValue(elmName, 'given-name', self.STRING, bAutoEscape=1) - arAdditionalNames = self.getPropertyValue(elmName, 'additional-name', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'additional-names', self.STRING, 1, 1) - arHonorificPrefixes = self.getPropertyValue(elmName, 'honorific-prefix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-prefixes', self.STRING, 1, 1) - arHonorificSuffixes = self.getPropertyValue(elmName, 'honorific-suffix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-suffixes', self.STRING, 1, 1) - arLines.append(self.vcardFold('N:' + sFamilyName + ';' + - sGivenName + ';' + - ','.join(arAdditionalNames) + ';' + - ','.join(arHonorificPrefixes) + ';' + - ','.join(arHonorificSuffixes))) - elif sFN: - # implied "N" optimization - # http://microformats.org/wiki/hcard#Implied_.22N.22_Optimization - arNames = self.normalize(sFN).split() - if len(arNames) == 2: - bFamilyNameFirst = (arNames[0].endswith(',') or - len(arNames[1]) == 1 or - ((len(arNames[1]) == 2) and (arNames[1].endswith('.')))) - if bFamilyNameFirst: - arLines.append(self.vcardFold('N:' + arNames[0] + ';' + arNames[1])) - else: - arLines.append(self.vcardFold('N:' + arNames[1] + ';' + arNames[0])) - - # SORT-STRING - sSortString = self.getPropertyValue(elmCard, 'sort-string', self.STRING, bAutoEscape=1) - if sSortString: - arLines.append(self.vcardFold('SORT-STRING:' + sSortString)) - - # NICKNAME - arNickname = self.getPropertyValue(elmCard, 'nickname', self.STRING, 1, 1) - if arNickname: - arLines.append(self.vcardFold('NICKNAME:' + ','.join(arNickname))) - - # PHOTO - processSingleURI('photo') - - # BDAY - dtBday = self.getPropertyValue(elmCard, 'bday', self.DATE) - if dtBday: - arLines.append(self.vcardFold('BDAY:' + self.toISO8601(dtBday))) - - # ADR (address) - arAdr = self.getPropertyValue(elmCard, 'adr', bAllowMultiple=1) - for elmAdr in arAdr: - arType = self.getPropertyValue(elmAdr, 'type', self.STRING, 1, 1) - if not arType: - arType = ['intl','postal','parcel','work'] # default adr types, see RFC 2426 section 3.2.1 - sPostOfficeBox = self.getPropertyValue(elmAdr, 'post-office-box', self.STRING, 0, 1) - sExtendedAddress = self.getPropertyValue(elmAdr, 'extended-address', self.STRING, 0, 1) - sStreetAddress = self.getPropertyValue(elmAdr, 'street-address', self.STRING, 0, 1) - sLocality = self.getPropertyValue(elmAdr, 'locality', self.STRING, 0, 1) - sRegion = self.getPropertyValue(elmAdr, 'region', self.STRING, 0, 1) - sPostalCode = self.getPropertyValue(elmAdr, 'postal-code', self.STRING, 0, 1) - sCountryName = self.getPropertyValue(elmAdr, 'country-name', self.STRING, 0, 1) - arLines.append(self.vcardFold('ADR;TYPE=' + ','.join(arType) + ':' + - sPostOfficeBox + ';' + - sExtendedAddress + ';' + - sStreetAddress + ';' + - sLocality + ';' + - sRegion + ';' + - sPostalCode + ';' + - sCountryName)) - - # LABEL - processTypeValue('label', ['intl','postal','parcel','work']) - - # TEL (phone number) - processTypeValue('tel', ['voice']) - - # EMAIL - processTypeValue('email', ['internet'], ['internet']) - - # MAILER - processSingleString('mailer') - - # TZ (timezone) - processSingleString('tz') - - # GEO (geographical information) - elmGeo = self.getPropertyValue(elmCard, 'geo') - if elmGeo: - sLatitude = self.getPropertyValue(elmGeo, 'latitude', self.STRING, 0, 1) - sLongitude = self.getPropertyValue(elmGeo, 'longitude', self.STRING, 0, 1) - arLines.append(self.vcardFold('GEO:' + sLatitude + ';' + sLongitude)) - - # TITLE - processSingleString('title') - - # ROLE - processSingleString('role') - - # LOGO - processSingleURI('logo') - - # ORG (organization) - elmOrg = self.getPropertyValue(elmCard, 'org') - if elmOrg: - sOrganizationName = self.getPropertyValue(elmOrg, 'organization-name', self.STRING, 0, 1) - if not sOrganizationName: - # implied "organization-name" optimization - # http://microformats.org/wiki/hcard#Implied_.22organization-name.22_Optimization - sOrganizationName = self.getPropertyValue(elmCard, 'org', self.STRING, 0, 1) - if sOrganizationName: - arLines.append(self.vcardFold('ORG:' + sOrganizationName)) - else: - arOrganizationUnit = self.getPropertyValue(elmOrg, 'organization-unit', self.STRING, 1, 1) - arLines.append(self.vcardFold('ORG:' + sOrganizationName + ';' + ';'.join(arOrganizationUnit))) - - # CATEGORY - arCategory = self.getPropertyValue(elmCard, 'category', self.STRING, 1, 1) + self.getPropertyValue(elmCard, 'categories', self.STRING, 1, 1) - if arCategory: - arLines.append(self.vcardFold('CATEGORIES:' + ','.join(arCategory))) - - # NOTE - processSingleString('note') - - # REV - processSingleString('rev') - - # SOUND - processSingleURI('sound') - - # UID - processSingleString('uid') - - # URL - processSingleURI('url') - - # CLASS - processSingleString('class') - - # KEY - processSingleURI('key') - - if arLines: - arLines = [u'BEGIN:vCard',u'VERSION:3.0'] + arLines + [u'END:vCard'] - sVCards += u'\n'.join(arLines) + u'\n' - - return sVCards.strip() - - def isProbablyDownloadable(self, elm): - attrsD = elm.attrMap - if not attrsD.has_key('href'): return 0 - linktype = attrsD.get('type', '').strip() - if linktype.startswith('audio/') or \ - linktype.startswith('video/') or \ - (linktype.startswith('application/') and not linktype.endswith('xml')): - return 1 - path = urlparse.urlparse(attrsD['href'])[2] - if path.find('.') == -1: return 0 - fileext = path.split('.').pop().lower() - return fileext in self.known_binary_extensions - - def findTags(self): - all = lambda x: 1 - for elm in self.document(all, {'rel': re.compile(r'\btag\b')}): - href = elm.get('href') - if not href: continue - urlscheme, domain, path, params, query, fragment = \ - urlparse.urlparse(_urljoin(self.baseuri, href)) - segments = path.split('/') - tag = segments.pop() - if not tag: - tag = segments.pop() - tagscheme = urlparse.urlunparse((urlscheme, domain, '/'.join(segments), '', '', '')) - if not tagscheme.endswith('/'): - tagscheme += '/' - self.tags.append(FeedParserDict({"term": tag, "scheme": tagscheme, "label": elm.string or ''})) - - def findEnclosures(self): - all = lambda x: 1 - enclosure_match = re.compile(r'\benclosure\b') - for elm in self.document(all, {'href': re.compile(r'.+')}): - if not enclosure_match.search(elm.get('rel', '')) and not self.isProbablyDownloadable(elm): continue - if elm.attrMap not in self.enclosures: - self.enclosures.append(elm.attrMap) - if elm.string and not elm.get('title'): - self.enclosures[-1]['title'] = elm.string - - def findXFN(self): - all = lambda x: 1 - for elm in self.document(all, {'rel': re.compile('.+'), 'href': re.compile('.+')}): - rels = elm.get('rel', '').split() - xfn_rels = [] - for rel in rels: - if rel in self.known_xfn_relationships: - xfn_rels.append(rel) - if xfn_rels: - self.xfn.append({"relationships": xfn_rels, "href": elm.get('href', ''), "name": elm.string}) - -def _parseMicroformats(htmlSource, baseURI, encoding): - if not BeautifulSoup: return - if _debug: sys.stderr.write('entering _parseMicroformats\n') - try: - p = _MicroformatsParser(htmlSource, baseURI, encoding) - except UnicodeEncodeError: - # sgmllib throws this exception when performing lookups of tags - # with non-ASCII characters in them. - return - p.vcard = p.findVCards(p.document) - p.findTags() - p.findEnclosures() - p.findXFN() - return {"tags": p.tags, "enclosures": p.enclosures, "xfn": p.xfn, "vcard": p.vcard} - -class _RelativeURIResolver(_BaseHTMLProcessor): - relative_uris = [('a', 'href'), - ('applet', 'codebase'), - ('area', 'href'), - ('blockquote', 'cite'), - ('body', 'background'), - ('del', 'cite'), - ('form', 'action'), - ('frame', 'longdesc'), - ('frame', 'src'), - ('iframe', 'longdesc'), - ('iframe', 'src'), - ('head', 'profile'), - ('img', 'longdesc'), - ('img', 'src'), - ('img', 'usemap'), - ('input', 'src'), - ('input', 'usemap'), - ('ins', 'cite'), - ('link', 'href'), - ('object', 'classid'), - ('object', 'codebase'), - ('object', 'data'), - ('object', 'usemap'), - ('q', 'cite'), - ('script', 'src')] - - def __init__(self, baseuri, encoding, _type): - _BaseHTMLProcessor.__init__(self, encoding, _type) - self.baseuri = baseuri - - def resolveURI(self, uri): - return _makeSafeAbsoluteURI(_urljoin(self.baseuri, uri.strip())) - - def unknown_starttag(self, tag, attrs): - if _debug: - sys.stderr.write('tag: [%s] with attributes: [%s]\n' % (tag, str(attrs))) - attrs = self.normalize_attrs(attrs) - attrs = [(key, ((tag, key) in self.relative_uris) and self.resolveURI(value) or value) for key, value in attrs] - _BaseHTMLProcessor.unknown_starttag(self, tag, attrs) - -def _resolveRelativeURIs(htmlSource, baseURI, encoding, _type): - if _debug: - sys.stderr.write('entering _resolveRelativeURIs\n') - - p = _RelativeURIResolver(baseURI, encoding, _type) - p.feed(htmlSource) - return p.output() - -def _makeSafeAbsoluteURI(base, rel=None): - # bail if ACCEPTABLE_URI_SCHEMES is empty - if not ACCEPTABLE_URI_SCHEMES: - return _urljoin(base, rel or u'') - if not base: - return rel or u'' - if not rel: - if base.strip().split(':', 1)[0] not in ACCEPTABLE_URI_SCHEMES: - return u'' - return base - uri = _urljoin(base, rel) - if uri.strip().split(':', 1)[0] not in ACCEPTABLE_URI_SCHEMES: - return u'' - return uri - -class _HTMLSanitizer(_BaseHTMLProcessor): - acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', - 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button', - 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', - 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn', - 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset', - 'figcaption', 'figure', 'footer', 'font', 'form', 'header', 'h1', - 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins', - 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter', - 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option', - 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', - 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong', - 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot', - 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video', 'noscript'] - - acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey', - 'action', 'align', 'alt', 'autocomplete', 'autofocus', 'axis', - 'background', 'balance', 'bgcolor', 'bgproperties', 'border', - 'bordercolor', 'bordercolordark', 'bordercolorlight', 'bottompadding', - 'cellpadding', 'cellspacing', 'ch', 'challenge', 'char', 'charoff', - 'choff', 'charset', 'checked', 'cite', 'class', 'clear', 'color', 'cols', - 'colspan', 'compact', 'contenteditable', 'controls', 'coords', 'data', - 'datafld', 'datapagesize', 'datasrc', 'datetime', 'default', 'delay', - 'dir', 'disabled', 'draggable', 'dynsrc', 'enctype', 'end', 'face', 'for', - 'form', 'frame', 'galleryimg', 'gutter', 'headers', 'height', 'hidefocus', - 'hidden', 'high', 'href', 'hreflang', 'hspace', 'icon', 'id', 'inputmode', - 'ismap', 'keytype', 'label', 'leftspacing', 'lang', 'list', 'longdesc', - 'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max', - 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref', - 'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size', - 'prompt', 'pqg', 'radiogroup', 'readonly', 'rel', 'repeat-max', - 'repeat-min', 'replace', 'required', 'rev', 'rightspacing', 'rows', - 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src', - 'start', 'step', 'summary', 'suppress', 'tabindex', 'target', 'template', - 'title', 'toppadding', 'type', 'unselectable', 'usemap', 'urn', 'valign', - 'value', 'variable', 'volume', 'vspace', 'vrml', 'width', 'wrap', - 'xml:lang'] - - unacceptable_elements_with_end_tag = ['script', 'applet', 'style'] - - acceptable_css_properties = ['azimuth', 'background-color', - 'border-bottom-color', 'border-collapse', 'border-color', - 'border-left-color', 'border-right-color', 'border-top-color', 'clear', - 'color', 'cursor', 'direction', 'display', 'elevation', 'float', 'font', - 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight', - 'height', 'letter-spacing', 'line-height', 'overflow', 'pause', - 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness', - 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation', - 'speech-rate', 'stress', 'text-align', 'text-decoration', 'text-indent', - 'unicode-bidi', 'vertical-align', 'voice-family', 'volume', - 'white-space', 'width'] - - # survey of common keywords found in feeds - acceptable_css_keywords = ['auto', 'aqua', 'black', 'block', 'blue', - 'bold', 'both', 'bottom', 'brown', 'center', 'collapse', 'dashed', - 'dotted', 'fuchsia', 'gray', 'green', '!important', 'italic', 'left', - 'lime', 'maroon', 'medium', 'none', 'navy', 'normal', 'nowrap', 'olive', - 'pointer', 'purple', 'red', 'right', 'solid', 'silver', 'teal', 'top', - 'transparent', 'underline', 'white', 'yellow'] - - valid_css_values = re.compile('^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|' + - '\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$') - - mathml_elements = ['annotation', 'annotation-xml', 'maction', 'math', - 'merror', 'mfenced', 'mfrac', 'mi', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', - 'mphantom', 'mprescripts', 'mroot', 'mrow', 'mspace', 'msqrt', 'mstyle', - 'msub', 'msubsup', 'msup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', - 'munderover', 'none', 'semantics'] - - mathml_attributes = ['actiontype', 'align', 'columnalign', 'columnalign', - 'columnalign', 'close', 'columnlines', 'columnspacing', 'columnspan', 'depth', - 'display', 'displaystyle', 'encoding', 'equalcolumns', 'equalrows', - 'fence', 'fontstyle', 'fontweight', 'frame', 'height', 'linethickness', - 'lspace', 'mathbackground', 'mathcolor', 'mathvariant', 'mathvariant', - 'maxsize', 'minsize', 'open', 'other', 'rowalign', 'rowalign', 'rowalign', - 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'scriptlevel', 'selection', - 'separator', 'separators', 'stretchy', 'width', 'width', 'xlink:href', - 'xlink:show', 'xlink:type', 'xmlns', 'xmlns:xlink'] - - # svgtiny - foreignObject + linearGradient + radialGradient + stop - svg_elements = ['a', 'animate', 'animateColor', 'animateMotion', - 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'foreignObject', - 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', - 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', 'mpath', - 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop', - 'svg', 'switch', 'text', 'title', 'tspan', 'use'] - - # svgtiny + class + opacity + offset + xmlns + xmlns:xlink - svg_attributes = ['accent-height', 'accumulate', 'additive', 'alphabetic', - 'arabic-form', 'ascent', 'attributeName', 'attributeType', - 'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height', - 'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx', - 'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-opacity', - 'fill-rule', 'font-family', 'font-size', 'font-stretch', 'font-style', - 'font-variant', 'font-weight', 'from', 'fx', 'fy', 'g1', 'g2', - 'glyph-name', 'gradientUnits', 'hanging', 'height', 'horiz-adv-x', - 'horiz-origin-x', 'id', 'ideographic', 'k', 'keyPoints', 'keySplines', - 'keyTimes', 'lang', 'mathematical', 'marker-end', 'marker-mid', - 'marker-start', 'markerHeight', 'markerUnits', 'markerWidth', 'max', - 'min', 'name', 'offset', 'opacity', 'orient', 'origin', - 'overline-position', 'overline-thickness', 'panose-1', 'path', - 'pathLength', 'points', 'preserveAspectRatio', 'r', 'refX', 'refY', - 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', - 'restart', 'rotate', 'rx', 'ry', 'slope', 'stemh', 'stemv', - 'stop-color', 'stop-opacity', 'strikethrough-position', - 'strikethrough-thickness', 'stroke', 'stroke-dasharray', - 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', - 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', - 'target', 'text-anchor', 'to', 'transform', 'type', 'u1', 'u2', - 'underline-position', 'underline-thickness', 'unicode', 'unicode-range', - 'units-per-em', 'values', 'version', 'viewBox', 'visibility', 'width', - 'widths', 'x', 'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole', - 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type', - 'xml:base', 'xml:lang', 'xml:space', 'xmlns', 'xmlns:xlink', 'y', 'y1', - 'y2', 'zoomAndPan'] - - svg_attr_map = None - svg_elem_map = None - - acceptable_svg_properties = [ 'fill', 'fill-opacity', 'fill-rule', - 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', - 'stroke-opacity'] - - def reset(self): - _BaseHTMLProcessor.reset(self) - self.unacceptablestack = 0 - self.mathmlOK = 0 - self.svgOK = 0 - - def unknown_starttag(self, tag, attrs): - acceptable_attributes = self.acceptable_attributes - keymap = {} - if not tag in self.acceptable_elements or self.svgOK: - if tag in self.unacceptable_elements_with_end_tag: - self.unacceptablestack += 1 - - # add implicit namespaces to html5 inline svg/mathml - if self._type.endswith('html'): - if not dict(attrs).get('xmlns'): - if tag=='svg': - attrs.append( ('xmlns','http://www.w3.org/2000/svg') ) - if tag=='math': - attrs.append( ('xmlns','http://www.w3.org/1998/Math/MathML') ) - - # not otherwise acceptable, perhaps it is MathML or SVG? - if tag=='math' and ('xmlns','http://www.w3.org/1998/Math/MathML') in attrs: - self.mathmlOK += 1 - if tag=='svg' and ('xmlns','http://www.w3.org/2000/svg') in attrs: - self.svgOK += 1 - - # chose acceptable attributes based on tag class, else bail - if self.mathmlOK and tag in self.mathml_elements: - acceptable_attributes = self.mathml_attributes - elif self.svgOK and tag in self.svg_elements: - # for most vocabularies, lowercasing is a good idea. Many - # svg elements, however, are camel case - if not self.svg_attr_map: - lower=[attr.lower() for attr in self.svg_attributes] - mix=[a for a in self.svg_attributes if a not in lower] - self.svg_attributes = lower - self.svg_attr_map = dict([(a.lower(),a) for a in mix]) - - lower=[attr.lower() for attr in self.svg_elements] - mix=[a for a in self.svg_elements if a not in lower] - self.svg_elements = lower - self.svg_elem_map = dict([(a.lower(),a) for a in mix]) - acceptable_attributes = self.svg_attributes - tag = self.svg_elem_map.get(tag,tag) - keymap = self.svg_attr_map - elif not tag in self.acceptable_elements: - return - - # declare xlink namespace, if needed - if self.mathmlOK or self.svgOK: - if filter(lambda (n,v): n.startswith('xlink:'),attrs): - if not ('xmlns:xlink','http://www.w3.org/1999/xlink') in attrs: - attrs.append(('xmlns:xlink','http://www.w3.org/1999/xlink')) - - clean_attrs = [] - for key, value in self.normalize_attrs(attrs): - if key in acceptable_attributes: - key=keymap.get(key,key) - clean_attrs.append((key,value)) - elif key=='style': - clean_value = self.sanitize_style(value) - if clean_value: clean_attrs.append((key,clean_value)) - _BaseHTMLProcessor.unknown_starttag(self, tag, clean_attrs) - - def unknown_endtag(self, tag): - if not tag in self.acceptable_elements: - if tag in self.unacceptable_elements_with_end_tag: - self.unacceptablestack -= 1 - if self.mathmlOK and tag in self.mathml_elements: - if tag == 'math' and self.mathmlOK: self.mathmlOK -= 1 - elif self.svgOK and tag in self.svg_elements: - tag = self.svg_elem_map.get(tag,tag) - if tag == 'svg' and self.svgOK: self.svgOK -= 1 - else: - return - _BaseHTMLProcessor.unknown_endtag(self, tag) - - def handle_pi(self, text): - pass - - def handle_decl(self, text): - pass - - def handle_data(self, text): - if not self.unacceptablestack: - _BaseHTMLProcessor.handle_data(self, text) - - def sanitize_style(self, style): - # disallow urls - style=re.compile('url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ',style) - - # gauntlet - if not re.match("""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style): return '' - # This replaced a regexp that used re.match and was prone to pathological back-tracking. - if re.sub("\s*[-\w]+\s*:\s*[^:;]*;?", '', style).strip(): return '' - - clean = [] - for prop,value in re.findall("([-\w]+)\s*:\s*([^:;]*)",style): - if not value: continue - if prop.lower() in self.acceptable_css_properties: - clean.append(prop + ': ' + value + ';') - elif prop.split('-')[0].lower() in ['background','border','margin','padding']: - for keyword in value.split(): - if not keyword in self.acceptable_css_keywords and \ - not self.valid_css_values.match(keyword): - break - else: - clean.append(prop + ': ' + value + ';') - elif self.svgOK and prop.lower() in self.acceptable_svg_properties: - clean.append(prop + ': ' + value + ';') - - return ' '.join(clean) - - -def _sanitizeHTML(htmlSource, encoding, _type): - p = _HTMLSanitizer(encoding, _type) - htmlSource = htmlSource.replace('<![CDATA[', '<![CDATA[') - p.feed(htmlSource) - data = p.output() - if TIDY_MARKUP: - # loop through list of preferred Tidy interfaces looking for one that's installed, - # then set up a common _tidy function to wrap the interface-specific API. - _tidy = None - for tidy_interface in PREFERRED_TIDY_INTERFACES: - try: - if tidy_interface == "uTidy": - from tidy import parseString as _utidy - def _tidy(data, **kwargs): - return str(_utidy(data, **kwargs)) - break - elif tidy_interface == "mxTidy": - from mx.Tidy import Tidy as _mxtidy - def _tidy(data, **kwargs): - nerrors, nwarnings, data, errordata = _mxtidy.tidy(data, **kwargs) - return data - break - except: - pass - if _tidy: - utf8 = type(data) == type(u'') - if utf8: - data = data.encode('utf-8') - data = _tidy(data, output_xhtml=1, numeric_entities=1, wrap=0, char_encoding="utf8") - if utf8: - data = unicode(data, 'utf-8') - if data.count('<body'): - data = data.split('<body', 1)[1] - if data.count('>'): - data = data.split('>', 1)[1] - if data.count('</body'): - data = data.split('</body', 1)[0] - data = data.strip().replace('\r\n', '\n') - return data - -class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler): - def http_error_default(self, req, fp, code, msg, headers): - if ((code / 100) == 3) and (code != 304): - return self.http_error_302(req, fp, code, msg, headers) - infourl = urllib.addinfourl(fp, headers, req.get_full_url()) - infourl.status = code - return infourl - - def http_error_302(self, req, fp, code, msg, headers): - if headers.dict.has_key('location'): - infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) - else: - infourl = urllib.addinfourl(fp, headers, req.get_full_url()) - if not hasattr(infourl, 'status'): - infourl.status = code - return infourl - - def http_error_301(self, req, fp, code, msg, headers): - if headers.dict.has_key('location'): - infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) - else: - infourl = urllib.addinfourl(fp, headers, req.get_full_url()) - if not hasattr(infourl, 'status'): - infourl.status = code - return infourl - - http_error_300 = http_error_302 - http_error_303 = http_error_302 - http_error_307 = http_error_302 - - def http_error_401(self, req, fp, code, msg, headers): - # Check if - # - server requires digest auth, AND - # - we tried (unsuccessfully) with basic auth, AND - # - we're using Python 2.3.3 or later (digest auth is irreparably broken in earlier versions) - # If all conditions hold, parse authentication information - # out of the Authorization header we sent the first time - # (for the username and password) and the WWW-Authenticate - # header the server sent back (for the realm) and retry - # the request with the appropriate digest auth headers instead. - # This evil genius hack has been brought to you by Aaron Swartz. - host = urlparse.urlparse(req.get_full_url())[1] - try: - assert sys.version.split()[0] >= '2.3.3' - assert base64 is not None - user, passw = _base64decode(req.headers['Authorization'].split(' ')[1]).split(':') - realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0] - self.add_password(realm, host, user, passw) - retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) - self.reset_retry_count() - return retry - except: - return self.http_error_default(req, fp, code, msg, headers) - -def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers): - """URL, filename, or string --> stream - - This function lets you define parsers that take any input source - (URL, pathname to local or network file, or actual data as a string) - and deal with it in a uniform manner. Returned object is guaranteed - to have all the basic stdio read methods (read, readline, readlines). - Just .close() the object when you're done with it. - - If the etag argument is supplied, it will be used as the value of an - If-None-Match request header. - - If the modified argument is supplied, it can be a tuple of 9 integers - (as returned by gmtime() in the standard Python time module) or a date - string in any format supported by feedparser. Regardless, it MUST - be in GMT (Greenwich Mean Time). It will be reformatted into an - RFC 1123-compliant date and used as the value of an If-Modified-Since - request header. - - If the agent argument is supplied, it will be used as the value of a - User-Agent request header. - - If the referrer argument is supplied, it will be used as the value of a - Referer[sic] request header. - - If handlers is supplied, it is a list of handlers used to build a - urllib2 opener. - - if request_headers is supplied it is a dictionary of HTTP request headers - that will override the values generated by FeedParser. - """ - - if hasattr(url_file_stream_or_string, 'read'): - return url_file_stream_or_string - - if url_file_stream_or_string == '-': - return sys.stdin - - if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp', 'file', 'feed'): - # Deal with the feed URI scheme - if url_file_stream_or_string.startswith('feed:http'): - url_file_stream_or_string = url_file_stream_or_string[5:] - elif url_file_stream_or_string.startswith('feed:'): - url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:] - if not agent: - agent = USER_AGENT - # test for inline user:password for basic auth - auth = None - if base64: - urltype, rest = urllib.splittype(url_file_stream_or_string) - realhost, rest = urllib.splithost(rest) - if realhost: - user_passwd, realhost = urllib.splituser(realhost) - if user_passwd: - url_file_stream_or_string = '%s://%s%s' % (urltype, realhost, rest) - auth = base64.standard_b64encode(user_passwd).strip() - - # iri support - try: - if isinstance(url_file_stream_or_string,unicode): - url_file_stream_or_string = url_file_stream_or_string.encode('idna').decode('utf-8') - else: - url_file_stream_or_string = url_file_stream_or_string.decode('utf-8').encode('idna').decode('utf-8') - except: - pass - - # try to open with urllib2 (to use optional headers) - request = _build_urllib2_request(url_file_stream_or_string, agent, etag, modified, referrer, auth, request_headers) - opener = apply(urllib2.build_opener, tuple(handlers + [_FeedURLHandler()])) - opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent - try: - return opener.open(request) - finally: - opener.close() # JohnD - - # try to open with native open function (if url_file_stream_or_string is a filename) - try: - return open(url_file_stream_or_string, 'rb') - except: - pass - - # treat url_file_stream_or_string as string - return _StringIO(str(url_file_stream_or_string)) - -def _build_urllib2_request(url, agent, etag, modified, referrer, auth, request_headers): - request = urllib2.Request(url) - request.add_header('User-Agent', agent) - if etag: - request.add_header('If-None-Match', etag) - if type(modified) == type(''): - modified = _parse_date(modified) - elif isinstance(modified, datetime.datetime): - modified = modified.utctimetuple() - if modified: - # format into an RFC 1123-compliant timestamp. We can't use - # time.strftime() since the %a and %b directives can be affected - # by the current locale, but RFC 2616 states that dates must be - # in English. - short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5])) - if referrer: - request.add_header('Referer', referrer) - if gzip and zlib: - request.add_header('Accept-encoding', 'gzip, deflate') - elif gzip: - request.add_header('Accept-encoding', 'gzip') - elif zlib: - request.add_header('Accept-encoding', 'deflate') - else: - request.add_header('Accept-encoding', '') - if auth: - request.add_header('Authorization', 'Basic %s' % auth) - if ACCEPT_HEADER: - request.add_header('Accept', ACCEPT_HEADER) - # use this for whatever -- cookies, special headers, etc - # [('Cookie','Something'),('x-special-header','Another Value')] - for header_name, header_value in request_headers.items(): - request.add_header(header_name, header_value) - request.add_header('A-IM', 'feed') # RFC 3229 support - return request - -_date_handlers = [] -def registerDateHandler(func): - '''Register a date handler function (takes string, returns 9-tuple date in GMT)''' - _date_handlers.insert(0, func) - -# ISO-8601 date parsing routines written by Fazal Majid. -# The ISO 8601 standard is very convoluted and irregular - a full ISO 8601 -# parser is beyond the scope of feedparser and would be a worthwhile addition -# to the Python library. -# A single regular expression cannot parse ISO 8601 date formats into groups -# as the standard is highly irregular (for instance is 030104 2003-01-04 or -# 0301-04-01), so we use templates instead. -# Please note the order in templates is significant because we need a -# greedy match. -_iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-0MM?-?DD', 'YYYY-MM', 'YYYY-?OOO', - 'YY-?MM-?DD', 'YY-?OOO', 'YYYY', - '-YY-?MM', '-OOO', '-YY', - '--MM-?DD', '--MM', - '---DD', - 'CC', ''] -_iso8601_re = [ - tmpl.replace( - 'YYYY', r'(?P<year>\d{4})').replace( - 'YY', r'(?P<year>\d\d)').replace( - 'MM', r'(?P<month>[01]\d)').replace( - 'DD', r'(?P<day>[0123]\d)').replace( - 'OOO', r'(?P<ordinal>[0123]\d\d)').replace( - 'CC', r'(?P<century>\d\d$)') - + r'(T?(?P<hour>\d{2}):(?P<minute>\d{2})' - + r'(:(?P<second>\d{2}))?' - + r'(\.(?P<fracsecond>\d+))?' - + r'(?P<tz>[+-](?P<tzhour>\d{2})(:(?P<tzmin>\d{2}))?|Z)?)?' - for tmpl in _iso8601_tmpl] -try: - del tmpl -except NameError: - pass -_iso8601_matches = [re.compile(regex).match for regex in _iso8601_re] -try: - del regex -except NameError: - pass -def _parse_date_iso8601(dateString): - '''Parse a variety of ISO-8601-compatible formats like 20040105''' - m = None - for _iso8601_match in _iso8601_matches: - m = _iso8601_match(dateString) - if m: break - if not m: return - if m.span() == (0, 0): return - params = m.groupdict() - ordinal = params.get('ordinal', 0) - if ordinal: - ordinal = int(ordinal) - else: - ordinal = 0 - year = params.get('year', '--') - if not year or year == '--': - year = time.gmtime()[0] - elif len(year) == 2: - # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993 - year = 100 * int(time.gmtime()[0] / 100) + int(year) - else: - year = int(year) - month = params.get('month', '-') - if not month or month == '-': - # ordinals are NOT normalized by mktime, we simulate them - # by setting month=1, day=ordinal - if ordinal: - month = 1 - else: - month = time.gmtime()[1] - month = int(month) - day = params.get('day', 0) - if not day: - # see above - if ordinal: - day = ordinal - elif params.get('century', 0) or \ - params.get('year', 0) or params.get('month', 0): - day = 1 - else: - day = time.gmtime()[2] - else: - day = int(day) - # special case of the century - is the first year of the 21st century - # 2000 or 2001 ? The debate goes on... - if 'century' in params.keys(): - year = (int(params['century']) - 1) * 100 + 1 - # in ISO 8601 most fields are optional - for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']: - if not params.get(field, None): - params[field] = 0 - hour = int(params.get('hour', 0)) - minute = int(params.get('minute', 0)) - second = int(float(params.get('second', 0))) - # weekday is normalized by mktime(), we can ignore it - weekday = 0 - daylight_savings_flag = -1 - tm = [year, month, day, hour, minute, second, weekday, - ordinal, daylight_savings_flag] - # ISO 8601 time zone adjustments - tz = params.get('tz') - if tz and tz != 'Z': - if tz[0] == '-': - tm[3] += int(params.get('tzhour', 0)) - tm[4] += int(params.get('tzmin', 0)) - elif tz[0] == '+': - tm[3] -= int(params.get('tzhour', 0)) - tm[4] -= int(params.get('tzmin', 0)) - else: - return None - # Python's time.mktime() is a wrapper around the ANSI C mktime(3c) - # which is guaranteed to normalize d/m/y/h/m/s. - # Many implementations have bugs, but we'll pretend they don't. - return time.localtime(time.mktime(tuple(tm))) -registerDateHandler(_parse_date_iso8601) - -# 8-bit date handling routines written by ytrewq1. -_korean_year = u'\ub144' # b3e2 in euc-kr -_korean_month = u'\uc6d4' # bff9 in euc-kr -_korean_day = u'\uc77c' # c0cf in euc-kr -_korean_am = u'\uc624\uc804' # bfc0 c0fc in euc-kr -_korean_pm = u'\uc624\ud6c4' # bfc0 c8c4 in euc-kr - -_korean_onblog_date_re = \ - re.compile('(\d{4})%s\s+(\d{2})%s\s+(\d{2})%s\s+(\d{2}):(\d{2}):(\d{2})' % \ - (_korean_year, _korean_month, _korean_day)) -_korean_nate_date_re = \ - re.compile(u'(\d{4})-(\d{2})-(\d{2})\s+(%s|%s)\s+(\d{,2}):(\d{,2}):(\d{,2})' % \ - (_korean_am, _korean_pm)) -def _parse_date_onblog(dateString): - '''Parse a string according to the OnBlog 8-bit date format''' - m = _korean_onblog_date_re.match(dateString) - if not m: return - w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ - {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ - 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\ - 'zonediff': '+09:00'} - if _debug: sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate) - return _parse_date_w3dtf(w3dtfdate) -registerDateHandler(_parse_date_onblog) - -def _parse_date_nate(dateString): - '''Parse a string according to the Nate 8-bit date format''' - m = _korean_nate_date_re.match(dateString) - if not m: return - hour = int(m.group(5)) - ampm = m.group(4) - if (ampm == _korean_pm): - hour += 12 - hour = str(hour) - if len(hour) == 1: - hour = '0' + hour - w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ - {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ - 'hour': hour, 'minute': m.group(6), 'second': m.group(7),\ - 'zonediff': '+09:00'} - if _debug: sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate) - return _parse_date_w3dtf(w3dtfdate) -registerDateHandler(_parse_date_nate) - -_mssql_date_re = \ - re.compile('(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(\.\d+)?') -def _parse_date_mssql(dateString): - '''Parse a string according to the MS SQL date format''' - m = _mssql_date_re.match(dateString) - if not m: return - w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ - {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ - 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\ - 'zonediff': '+09:00'} - if _debug: sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate) - return _parse_date_w3dtf(w3dtfdate) -registerDateHandler(_parse_date_mssql) - -# Unicode strings for Greek date strings -_greek_months = \ - { \ - u'\u0399\u03b1\u03bd': u'Jan', # c9e1ed in iso-8859-7 - u'\u03a6\u03b5\u03b2': u'Feb', # d6e5e2 in iso-8859-7 - u'\u039c\u03ac\u03ce': u'Mar', # ccdcfe in iso-8859-7 - u'\u039c\u03b1\u03ce': u'Mar', # cce1fe in iso-8859-7 - u'\u0391\u03c0\u03c1': u'Apr', # c1f0f1 in iso-8859-7 - u'\u039c\u03ac\u03b9': u'May', # ccdce9 in iso-8859-7 - u'\u039c\u03b1\u03ca': u'May', # cce1fa in iso-8859-7 - u'\u039c\u03b1\u03b9': u'May', # cce1e9 in iso-8859-7 - u'\u0399\u03bf\u03cd\u03bd': u'Jun', # c9effded in iso-8859-7 - u'\u0399\u03bf\u03bd': u'Jun', # c9efed in iso-8859-7 - u'\u0399\u03bf\u03cd\u03bb': u'Jul', # c9effdeb in iso-8859-7 - u'\u0399\u03bf\u03bb': u'Jul', # c9f9eb in iso-8859-7 - u'\u0391\u03cd\u03b3': u'Aug', # c1fde3 in iso-8859-7 - u'\u0391\u03c5\u03b3': u'Aug', # c1f5e3 in iso-8859-7 - u'\u03a3\u03b5\u03c0': u'Sep', # d3e5f0 in iso-8859-7 - u'\u039f\u03ba\u03c4': u'Oct', # cfeaf4 in iso-8859-7 - u'\u039d\u03bf\u03ad': u'Nov', # cdefdd in iso-8859-7 - u'\u039d\u03bf\u03b5': u'Nov', # cdefe5 in iso-8859-7 - u'\u0394\u03b5\u03ba': u'Dec', # c4e5ea in iso-8859-7 - } - -_greek_wdays = \ - { \ - u'\u039a\u03c5\u03c1': u'Sun', # caf5f1 in iso-8859-7 - u'\u0394\u03b5\u03c5': u'Mon', # c4e5f5 in iso-8859-7 - u'\u03a4\u03c1\u03b9': u'Tue', # d4f1e9 in iso-8859-7 - u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7 - u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7 - u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7 - u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7 - } - -_greek_date_format_re = \ - re.compile(u'([^,]+),\s+(\d{2})\s+([^\s]+)\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+([^\s]+)') - -def _parse_date_greek(dateString): - '''Parse a string according to a Greek 8-bit date format.''' - m = _greek_date_format_re.match(dateString) - if not m: return - try: - wday = _greek_wdays[m.group(1)] - month = _greek_months[m.group(3)] - except: - return - rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % \ - {'wday': wday, 'day': m.group(2), 'month': month, 'year': m.group(4),\ - 'hour': m.group(5), 'minute': m.group(6), 'second': m.group(7),\ - 'zonediff': m.group(8)} - if _debug: sys.stderr.write('Greek date parsed as: %s\n' % rfc822date) - return _parse_date_rfc822(rfc822date) -registerDateHandler(_parse_date_greek) - -# Unicode strings for Hungarian date strings -_hungarian_months = \ - { \ - u'janu\u00e1r': u'01', # e1 in iso-8859-2 - u'febru\u00e1ri': u'02', # e1 in iso-8859-2 - u'm\u00e1rcius': u'03', # e1 in iso-8859-2 - u'\u00e1prilis': u'04', # e1 in iso-8859-2 - u'm\u00e1ujus': u'05', # e1 in iso-8859-2 - u'j\u00fanius': u'06', # fa in iso-8859-2 - u'j\u00falius': u'07', # fa in iso-8859-2 - u'augusztus': u'08', - u'szeptember': u'09', - u'okt\u00f3ber': u'10', # f3 in iso-8859-2 - u'november': u'11', - u'december': u'12', - } - -_hungarian_date_format_re = \ - re.compile(u'(\d{4})-([^-]+)-(\d{,2})T(\d{,2}):(\d{2})((\+|-)(\d{,2}:\d{2}))') - -def _parse_date_hungarian(dateString): - '''Parse a string according to a Hungarian 8-bit date format.''' - m = _hungarian_date_format_re.match(dateString) - if not m: return - try: - month = _hungarian_months[m.group(2)] - day = m.group(3) - if len(day) == 1: - day = '0' + day - hour = m.group(4) - if len(hour) == 1: - hour = '0' + hour - except: - return - w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % \ - {'year': m.group(1), 'month': month, 'day': day,\ - 'hour': hour, 'minute': m.group(5),\ - 'zonediff': m.group(6)} - if _debug: sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate) - return _parse_date_w3dtf(w3dtfdate) -registerDateHandler(_parse_date_hungarian) - -# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by -# Drake and licensed under the Python license. Removed all range checking -# for month, day, hour, minute, and second, since mktime will normalize -# these later -def _parse_date_w3dtf(dateString): - def __extract_date(m): - year = int(m.group('year')) - if year < 100: - year = 100 * int(time.gmtime()[0] / 100) + int(year) - if year < 1000: - return 0, 0, 0 - julian = m.group('julian') - if julian: - julian = int(julian) - month = julian / 30 + 1 - day = julian % 30 + 1 - jday = None - while jday != julian: - t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0)) - jday = time.gmtime(t)[-2] - diff = abs(jday - julian) - if jday > julian: - if diff < day: - day = day - diff - else: - month = month - 1 - day = 31 - elif jday < julian: - if day + diff < 28: - day = day + diff - else: - month = month + 1 - return year, month, day - month = m.group('month') - day = 1 - if month is None: - month = 1 - else: - month = int(month) - day = m.group('day') - if day: - day = int(day) - else: - day = 1 - return year, month, day - - def __extract_time(m): - if not m: - return 0, 0, 0 - hours = m.group('hours') - if not hours: - return 0, 0, 0 - hours = int(hours) - minutes = int(m.group('minutes')) - seconds = m.group('seconds') - if seconds: - seconds = int(seconds) - else: - seconds = 0 - return hours, minutes, seconds - - def __extract_tzd(m): - '''Return the Time Zone Designator as an offset in seconds from UTC.''' - if not m: - return 0 - tzd = m.group('tzd') - if not tzd: - return 0 - if tzd == 'Z': - return 0 - hours = int(m.group('tzdhours')) - minutes = m.group('tzdminutes') - if minutes: - minutes = int(minutes) - else: - minutes = 0 - offset = (hours*60 + minutes) * 60 - if tzd[0] == '+': - return -offset - return offset - - __date_re = ('(?P<year>\d\d\d\d)' - '(?:(?P<dsep>-|)' - '(?:(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?' - '|(?P<julian>\d\d\d)))?') - __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)' - __tzd_rx = re.compile(__tzd_re) - __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)' - '(?:(?P=tsep)(?P<seconds>\d\d)(?:[.,]\d+)?)?' - + __tzd_re) - __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) - __datetime_rx = re.compile(__datetime_re) - m = __datetime_rx.match(dateString) - if (m is None) or (m.group() != dateString): return - gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0) - if gmt[0] == 0: return - return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone) -registerDateHandler(_parse_date_w3dtf) - -def _parse_date_rfc822(dateString): - '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date''' - data = dateString.split() - if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames: - del data[0] - if len(data) == 4: - s = data[3] - i = s.find('+') - if i > 0: - data[3:] = [s[:i], s[i+1:]] - else: - data.append('') - dateString = " ".join(data) - # Account for the Etc/GMT timezone by stripping 'Etc/' - elif len(data) == 5 and data[4].lower().startswith('etc/'): - data[4] = data[4][4:] - dateString = " ".join(data) - if len(data) < 5: - dateString += ' 00:00:00 GMT' - tm = rfc822.parsedate_tz(dateString) - if tm: - return time.gmtime(rfc822.mktime_tz(tm)) -# rfc822.py defines several time zones, but we define some extra ones. -# 'ET' is equivalent to 'EST', etc. -_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800} -rfc822._timezones.update(_additional_timezones) -registerDateHandler(_parse_date_rfc822) - -def _parse_date_perforce(aDateString): - """parse a date in yyyy/mm/dd hh:mm:ss TTT format""" - # Fri, 2006/09/15 08:19:53 EDT - _my_date_pattern = re.compile( \ - r'(\w{,3}), (\d{,4})/(\d{,2})/(\d{2}) (\d{,2}):(\d{2}):(\d{2}) (\w{,3})') - - dow, year, month, day, hour, minute, second, tz = \ - _my_date_pattern.search(aDateString).groups() - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - dateString = "%s, %s %s %s %s:%s:%s %s" % (dow, day, months[int(month) - 1], year, hour, minute, second, tz) - tm = rfc822.parsedate_tz(dateString) - if tm: - return time.gmtime(rfc822.mktime_tz(tm)) -registerDateHandler(_parse_date_perforce) - -def _parse_date(dateString): - '''Parses a variety of date formats into a 9-tuple in GMT''' - for handler in _date_handlers: - try: - date9tuple = handler(dateString) - if not date9tuple: continue - if len(date9tuple) != 9: - if _debug: sys.stderr.write('date handler function must return 9-tuple\n') - raise ValueError - map(int, date9tuple) - return date9tuple - except Exception, e: - if _debug: sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e))) - pass - return None - -def _getCharacterEncoding(http_headers, xml_data): - '''Get the character encoding of the XML document - - http_headers is a dictionary - xml_data is a raw string (not Unicode) - - This is so much trickier than it sounds, it's not even funny. - According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type - is application/xml, application/*+xml, - application/xml-external-parsed-entity, or application/xml-dtd, - the encoding given in the charset parameter of the HTTP Content-Type - takes precedence over the encoding given in the XML prefix within the - document, and defaults to 'utf-8' if neither are specified. But, if - the HTTP Content-Type is text/xml, text/*+xml, or - text/xml-external-parsed-entity, the encoding given in the XML prefix - within the document is ALWAYS IGNORED and only the encoding given in - the charset parameter of the HTTP Content-Type header should be - respected, and it defaults to 'us-ascii' if not specified. - - Furthermore, discussion on the atom-syntax mailing list with the - author of RFC 3023 leads me to the conclusion that any document - served with a Content-Type of text/* and no charset parameter - must be treated as us-ascii. (We now do this.) And also that it - must always be flagged as non-well-formed. (We now do this too.) - - If Content-Type is unspecified (input was local file or non-HTTP source) - or unrecognized (server just got it totally wrong), then go by the - encoding given in the XML prefix of the document and default to - 'iso-8859-1' as per the HTTP specification (RFC 2616). - - Then, assuming we didn't find a character encoding in the HTTP headers - (and the HTTP Content-type allowed us to look in the body), we need - to sniff the first few bytes of the XML data and try to determine - whether the encoding is ASCII-compatible. Section F of the XML - specification shows the way here: - http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info - - If the sniffed encoding is not ASCII-compatible, we need to make it - ASCII compatible so that we can sniff further into the XML declaration - to find the encoding attribute, which will tell us the true encoding. - - Of course, none of this guarantees that we will be able to parse the - feed in the declared character encoding (assuming it was declared - correctly, which many are not). CJKCodecs and iconv_codec help a lot; - you should definitely install them if you can. - http://cjkpython.i18n.org/ - ''' - - def _parseHTTPContentType(content_type): - '''takes HTTP Content-Type header and returns (content type, charset) - - If no charset is specified, returns (content type, '') - If no content type is specified, returns ('', '') - Both return parameters are guaranteed to be lowercase strings - ''' - content_type = content_type or '' - content_type, params = cgi.parse_header(content_type) - return content_type, params.get('charset', '').replace("'", '') - - sniffed_xml_encoding = '' - xml_encoding = '' - true_encoding = '' - http_content_type, http_encoding = _parseHTTPContentType(http_headers.get('content-type', http_headers.get('Content-type'))) - # Must sniff for non-ASCII-compatible character encodings before - # searching for XML declaration. This heuristic is defined in - # section F of the XML specification: - # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info - try: - if xml_data[:4] == _l2bytes([0x4c, 0x6f, 0xa7, 0x94]): - # EBCDIC - xml_data = _ebcdic_to_ascii(xml_data) - elif xml_data[:4] == _l2bytes([0x00, 0x3c, 0x00, 0x3f]): - # UTF-16BE - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data, 'utf-16be').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == _l2bytes([0xfe, 0xff])) and (xml_data[2:4] != _l2bytes([0x00, 0x00])): - # UTF-16BE with BOM - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8') - elif xml_data[:4] == _l2bytes([0x3c, 0x00, 0x3f, 0x00]): - # UTF-16LE - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data, 'utf-16le').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == _l2bytes([0xff, 0xfe])) and (xml_data[2:4] != _l2bytes([0x00, 0x00])): - # UTF-16LE with BOM - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8') - elif xml_data[:4] == _l2bytes([0x00, 0x00, 0x00, 0x3c]): - # UTF-32BE - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data, 'utf-32be').encode('utf-8') - elif xml_data[:4] == _l2bytes([0x3c, 0x00, 0x00, 0x00]): - # UTF-32LE - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data, 'utf-32le').encode('utf-8') - elif xml_data[:4] == _l2bytes([0x00, 0x00, 0xfe, 0xff]): - # UTF-32BE with BOM - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8') - elif xml_data[:4] == _l2bytes([0xff, 0xfe, 0x00, 0x00]): - # UTF-32LE with BOM - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8') - elif xml_data[:3] == _l2bytes([0xef, 0xbb, 0xbf]): - # UTF-8 with BOM - sniffed_xml_encoding = 'utf-8' - xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8') - else: - # ASCII-compatible - pass - xml_encoding_match = re.compile(_s2bytes('^<\?.*encoding=[\'"](.*?)[\'"].*\?>')).match(xml_data) - except: - xml_encoding_match = None - if xml_encoding_match: - xml_encoding = xml_encoding_match.groups()[0].decode('utf-8').lower() - if sniffed_xml_encoding and (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16')): - xml_encoding = sniffed_xml_encoding - acceptable_content_type = 0 - application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity') - text_content_types = ('text/xml', 'text/xml-external-parsed-entity') - if (http_content_type in application_content_types) or \ - (http_content_type.startswith('application/') and http_content_type.endswith('+xml')): - acceptable_content_type = 1 - true_encoding = http_encoding or xml_encoding or 'utf-8' - elif (http_content_type in text_content_types) or \ - (http_content_type.startswith('text/')) and http_content_type.endswith('+xml'): - acceptable_content_type = 1 - true_encoding = http_encoding or 'us-ascii' - elif http_content_type.startswith('text/'): - true_encoding = http_encoding or 'us-ascii' - elif http_headers and (not (http_headers.has_key('content-type') or http_headers.has_key('Content-type'))): - true_encoding = xml_encoding or 'iso-8859-1' - else: - true_encoding = xml_encoding or 'utf-8' - # some feeds claim to be gb2312 but are actually gb18030. - # apparently MSIE and Firefox both do the following switch: - if true_encoding.lower() == 'gb2312': - true_encoding = 'gb18030' - return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type - -def _toUTF8(data, encoding): - '''Changes an XML data stream on the fly to specify a new encoding - - data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already - encoding is a string recognized by encodings.aliases - ''' - if _debug: sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding) - # strip Byte Order Mark (if present) - if (len(data) >= 4) and (data[:2] == _l2bytes([0xfe, 0xff])) and (data[2:4] != _l2bytes([0x00, 0x00])): - if _debug: - sys.stderr.write('stripping BOM\n') - if encoding != 'utf-16be': - sys.stderr.write('trying utf-16be instead\n') - encoding = 'utf-16be' - data = data[2:] - elif (len(data) >= 4) and (data[:2] == _l2bytes([0xff, 0xfe])) and (data[2:4] != _l2bytes([0x00, 0x00])): - if _debug: - sys.stderr.write('stripping BOM\n') - if encoding != 'utf-16le': - sys.stderr.write('trying utf-16le instead\n') - encoding = 'utf-16le' - data = data[2:] - elif data[:3] == _l2bytes([0xef, 0xbb, 0xbf]): - if _debug: - sys.stderr.write('stripping BOM\n') - if encoding != 'utf-8': - sys.stderr.write('trying utf-8 instead\n') - encoding = 'utf-8' - data = data[3:] - elif data[:4] == _l2bytes([0x00, 0x00, 0xfe, 0xff]): - if _debug: - sys.stderr.write('stripping BOM\n') - if encoding != 'utf-32be': - sys.stderr.write('trying utf-32be instead\n') - encoding = 'utf-32be' - data = data[4:] - elif data[:4] == _l2bytes([0xff, 0xfe, 0x00, 0x00]): - if _debug: - sys.stderr.write('stripping BOM\n') - if encoding != 'utf-32le': - sys.stderr.write('trying utf-32le instead\n') - encoding = 'utf-32le' - data = data[4:] - newdata = unicode(data, encoding) - if _debug: sys.stderr.write('successfully converted %s data to unicode\n' % encoding) - declmatch = re.compile('^<\?xml[^>]*?>') - newdecl = '''<?xml version='1.0' encoding='utf-8'?>''' - if declmatch.search(newdata): - newdata = declmatch.sub(newdecl, newdata) - else: - newdata = newdecl + u'\n' + newdata - return newdata.encode('utf-8') - -def _stripDoctype(data): - '''Strips DOCTYPE from XML document, returns (rss_version, stripped_data) - - rss_version may be 'rss091n' or None - stripped_data is the same XML document, minus the DOCTYPE - ''' - start = re.search(_s2bytes('<\w'), data) - start = start and start.start() or -1 - head,data = data[:start+1], data[start+1:] - - entity_pattern = re.compile(_s2bytes(r'^\s*<!ENTITY([^>]*?)>'), re.MULTILINE) - entity_results=entity_pattern.findall(head) - head = entity_pattern.sub(_s2bytes(''), head) - doctype_pattern = re.compile(_s2bytes(r'^\s*<!DOCTYPE([^>]*?)>'), re.MULTILINE) - doctype_results = doctype_pattern.findall(head) - doctype = doctype_results and doctype_results[0] or _s2bytes('') - if doctype.lower().count(_s2bytes('netscape')): - version = 'rss091n' - else: - version = None - - # only allow in 'safe' inline entity definitions - replacement=_s2bytes('') - if len(doctype_results)==1 and entity_results: - safe_pattern=re.compile(_s2bytes('\s+(\w+)\s+"(&#\w+;|[^&"]*)"')) - safe_entities=filter(lambda e: safe_pattern.match(e),entity_results) - if safe_entities: - replacement=_s2bytes('<!DOCTYPE feed [\n <!ENTITY') + _s2bytes('>\n <!ENTITY ').join(safe_entities) + _s2bytes('>\n]>') - data = doctype_pattern.sub(replacement, head) + data - - return version, data, dict(replacement and [(k.decode('utf-8'), v.decode('utf-8')) for k, v in safe_pattern.findall(replacement)]) - -def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[], request_headers={}, response_headers={}): - '''Parse a feed from a URL, file, stream, or string. - - request_headers, if given, is a dict from http header name to value to add - to the request; this overrides internally generated values. - ''' - result = FeedParserDict() - result['feed'] = FeedParserDict() - result['entries'] = [] - if _XML_AVAILABLE: - result['bozo'] = 0 - if not isinstance(handlers, list): - handlers = [handlers] - try: - f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers) - data = f.read() - except Exception, e: - result['bozo'] = 1 - result['bozo_exception'] = e - data = None - f = None - - if hasattr(f, 'headers'): - result['headers'] = dict(f.headers) - # overwrite existing headers using response_headers - if 'headers' in result: - result['headers'].update(response_headers) - elif response_headers: - result['headers'] = copy.deepcopy(response_headers) - - # if feed is gzip-compressed, decompress it - if f and data and 'headers' in result: - if gzip and result['headers'].get('content-encoding') == 'gzip': - try: - data = gzip.GzipFile(fileobj=_StringIO(data)).read() - except Exception, e: - # Some feeds claim to be gzipped but they're not, so - # we get garbage. Ideally, we should re-request the - # feed without the 'Accept-encoding: gzip' header, - # but we don't. - result['bozo'] = 1 - result['bozo_exception'] = e - data = '' - elif zlib and result['headers'].get('content-encoding') == 'deflate': - try: - data = zlib.decompress(data, -zlib.MAX_WBITS) - except Exception, e: - result['bozo'] = 1 - result['bozo_exception'] = e - data = '' - - # save HTTP headers - if 'headers' in result: - if 'etag' in result['headers'] or 'ETag' in result['headers']: - etag = result['headers'].get('etag', result['headers'].get('ETag')) - if etag: - result['etag'] = etag - if 'last-modified' in result['headers'] or 'Last-Modified' in result['headers']: - modified = result['headers'].get('last-modified', result['headers'].get('Last-Modified')) - if modified: - result['modified'] = _parse_date(modified) - if hasattr(f, 'url'): - result['href'] = f.url - result['status'] = 200 - if hasattr(f, 'status'): - result['status'] = f.status - if hasattr(f, 'close'): - f.close() - - # there are four encodings to keep track of: - # - http_encoding is the encoding declared in the Content-Type HTTP header - # - xml_encoding is the encoding declared in the <?xml declaration - # - sniffed_encoding is the encoding sniffed from the first 4 bytes of the XML data - # - result['encoding'] is the actual encoding, as per RFC 3023 and a variety of other conflicting specifications - http_headers = result.get('headers', {}) - result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type = \ - _getCharacterEncoding(http_headers, data) - if http_headers and (not acceptable_content_type): - if http_headers.has_key('content-type') or http_headers.has_key('Content-type'): - bozo_message = '%s is not an XML media type' % http_headers.get('content-type', http_headers.get('Content-type')) - else: - bozo_message = 'no Content-type specified' - result['bozo'] = 1 - result['bozo_exception'] = NonXMLContentType(bozo_message) - - if data is not None: - result['version'], data, entities = _stripDoctype(data) - - # ensure that baseuri is an absolute uri using an acceptable URI scheme - contentloc = http_headers.get('content-location', http_headers.get('Content-Location', '')) - href = result.get('href', '') - baseuri = _makeSafeAbsoluteURI(href, contentloc) or _makeSafeAbsoluteURI(contentloc) or href - - baselang = http_headers.get('content-language', http_headers.get('Content-Language', None)) - - # if server sent 304, we're done - if result.get('status', 0) == 304: - result['version'] = '' - result['debug_message'] = 'The feed has not changed since you last checked, ' + \ - 'so the server sent no data. This is a feature, not a bug!' - return result - - # if there was a problem downloading, we're done - if data is None: - return result - - # determine character encoding - use_strict_parser = 0 - known_encoding = 0 - tried_encodings = [] - # try: HTTP encoding, declared XML encoding, encoding sniffed from BOM - for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding): - if not proposed_encoding: continue - if proposed_encoding in tried_encodings: continue - tried_encodings.append(proposed_encoding) - try: - data = _toUTF8(data, proposed_encoding) - known_encoding = use_strict_parser = 1 - break - except: - pass - # if no luck and we have auto-detection library, try that - if (not known_encoding) and chardet: - try: - proposed_encoding = chardet.detect(data)['encoding'] - if proposed_encoding and (proposed_encoding not in tried_encodings): - tried_encodings.append(proposed_encoding) - data = _toUTF8(data, proposed_encoding) - known_encoding = use_strict_parser = 1 - except: - pass - # if still no luck and we haven't tried utf-8 yet, try that - if (not known_encoding) and ('utf-8' not in tried_encodings): - try: - proposed_encoding = 'utf-8' - tried_encodings.append(proposed_encoding) - data = _toUTF8(data, proposed_encoding) - known_encoding = use_strict_parser = 1 - except: - pass - # if still no luck and we haven't tried windows-1252 yet, try that - if (not known_encoding) and ('windows-1252' not in tried_encodings): - try: - proposed_encoding = 'windows-1252' - tried_encodings.append(proposed_encoding) - data = _toUTF8(data, proposed_encoding) - known_encoding = use_strict_parser = 1 - except: - pass - # if still no luck and we haven't tried iso-8859-2 yet, try that. - if (not known_encoding) and ('iso-8859-2' not in tried_encodings): - try: - proposed_encoding = 'iso-8859-2' - tried_encodings.append(proposed_encoding) - data = _toUTF8(data, proposed_encoding) - known_encoding = use_strict_parser = 1 - except: - pass - # if still no luck, give up - if not known_encoding: - result['bozo'] = 1 - result['bozo_exception'] = CharacterEncodingUnknown( \ - 'document encoding unknown, I tried ' + \ - '%s, %s, utf-8, windows-1252, and iso-8859-2 but nothing worked' % \ - (result['encoding'], xml_encoding)) - result['encoding'] = '' - elif proposed_encoding != result['encoding']: - result['bozo'] = 1 - result['bozo_exception'] = CharacterEncodingOverride( \ - 'document declared as %s, but parsed as %s' % \ - (result['encoding'], proposed_encoding)) - result['encoding'] = proposed_encoding - - if not _XML_AVAILABLE: - use_strict_parser = 0 - if use_strict_parser: - # initialize the SAX parser - feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8') - saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS) - saxparser.setFeature(xml.sax.handler.feature_namespaces, 1) - saxparser.setContentHandler(feedparser) - saxparser.setErrorHandler(feedparser) - source = xml.sax.xmlreader.InputSource() - source.setByteStream(_StringIO(data)) - if hasattr(saxparser, '_ns_stack'): - # work around bug in built-in SAX parser (doesn't recognize xml: namespace) - # PyXML doesn't have this problem, and it doesn't have _ns_stack either - saxparser._ns_stack.append({'http://www.w3.org/XML/1998/namespace':'xml'}) - try: - saxparser.parse(source) - except Exception, e: - if _debug: - import traceback - traceback.print_stack() - traceback.print_exc() - sys.stderr.write('xml parsing failed\n') - result['bozo'] = 1 - result['bozo_exception'] = feedparser.exc or e - use_strict_parser = 0 - if not use_strict_parser: - feedparser = _LooseFeedParser(baseuri, baselang, 'utf-8', entities) - feedparser.feed(data.decode('utf-8', 'replace')) - result['feed'] = feedparser.feeddata - result['entries'] = feedparser.entries - result['version'] = result['version'] or feedparser.version - result['namespaces'] = feedparser.namespacesInUse - return result - -class Serializer: - def __init__(self, results): - self.results = results - -class TextSerializer(Serializer): - def write(self, stream=sys.stdout): - self._writer(stream, self.results, '') - - def _writer(self, stream, node, prefix): - if not node: return - if hasattr(node, 'keys'): - keys = node.keys() - keys.sort() - for k in keys: - if k in ('description', 'link'): continue - if node.has_key(k + '_detail'): continue - if node.has_key(k + '_parsed'): continue - self._writer(stream, node[k], prefix + k + '.') - elif type(node) == types.ListType: - index = 0 - for n in node: - self._writer(stream, n, prefix[:-1] + '[' + str(index) + '].') - index += 1 - else: - try: - s = str(node).encode('utf-8') - s = s.replace('\\', '\\\\') - s = s.replace('\r', '') - s = s.replace('\n', r'\n') - stream.write(prefix[:-1]) - stream.write('=') - stream.write(s) - stream.write('\n') - except: - pass - -class PprintSerializer(Serializer): - def write(self, stream=sys.stdout): - if self.results.has_key('href'): - stream.write(self.results['href'] + '\n\n') - from pprint import pprint - pprint(self.results, stream) - stream.write('\n') - -if __name__ == '__main__': - try: - from optparse import OptionParser - except: - OptionParser = None - - if OptionParser: - optionParser = OptionParser(version=__version__, usage="%prog [options] url_or_filename_or_-") - optionParser.set_defaults(format="pprint") - optionParser.add_option("-A", "--user-agent", dest="agent", metavar="AGENT", help="User-Agent for HTTP URLs") - optionParser.add_option("-e", "--referer", "--referrer", dest="referrer", metavar="URL", help="Referrer for HTTP URLs") - optionParser.add_option("-t", "--etag", dest="etag", metavar="TAG", help="ETag/If-None-Match for HTTP URLs") - optionParser.add_option("-m", "--last-modified", dest="modified", metavar="DATE", help="Last-modified/If-Modified-Since for HTTP URLs (any supported date format)") - optionParser.add_option("-f", "--format", dest="format", metavar="FORMAT", help="output results in FORMAT (text, pprint)") - optionParser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="write debugging information to stderr") - (options, urls) = optionParser.parse_args() - if options.verbose: - _debug = 1 - if not urls: - optionParser.print_help() - sys.exit(0) - else: - if not sys.argv[1:]: - print __doc__ - sys.exit(0) - class _Options: - etag = modified = agent = referrer = None - format = 'pprint' - options = _Options() - urls = sys.argv[1:] - - zopeCompatibilityHack() - - serializer = globals().get(options.format.capitalize() + 'Serializer', Serializer) - for url in urls: - results = parse(url, etag=options.etag, modified=options.modified, agent=options.agent, referrer=options.referrer) - serializer(results).write(sys.stdout) diff --git a/module/lib/jinja2/__init__.py b/module/lib/jinja2/__init__.py deleted file mode 100644 index f944e11b6..000000000 --- a/module/lib/jinja2/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2 - ~~~~~~ - - Jinja2 is a template engine written in pure Python. It provides a - Django inspired non-XML syntax but supports inline expressions and - an optional sandboxed environment. - - Nutshell - -------- - - Here a small example of a Jinja2 template:: - - {% extends 'base.html' %} - {% block title %}Memberlist{% endblock %} - {% block content %} - <ul> - {% for user in users %} - <li><a href="{{ user.url }}">{{ user.username }}</a></li> - {% endfor %} - </ul> - {% endblock %} - - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -__docformat__ = 'restructuredtext en' -try: - __version__ = __import__('pkg_resources') \ - .get_distribution('Jinja2').version -except: - __version__ = 'unknown' - -# high level interface -from jinja2.environment import Environment, Template - -# loaders -from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \ - DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \ - ModuleLoader - -# bytecode caches -from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \ - MemcachedBytecodeCache - -# undefined types -from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined - -# exceptions -from jinja2.exceptions import TemplateError, UndefinedError, \ - TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \ - TemplateAssertionError - -# decorators and public utilities -from jinja2.filters import environmentfilter, contextfilter, \ - evalcontextfilter -from jinja2.utils import Markup, escape, clear_caches, \ - environmentfunction, evalcontextfunction, contextfunction, \ - is_undefined - -__all__ = [ - 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader', - 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader', - 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache', - 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined', - 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound', - 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', - 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape', - 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined', - 'evalcontextfilter', 'evalcontextfunction' -] diff --git a/module/lib/jinja2/_markupsafe/__init__.py b/module/lib/jinja2/_markupsafe/__init__.py deleted file mode 100644 index ec7bd572d..000000000 --- a/module/lib/jinja2/_markupsafe/__init__.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- -""" - markupsafe - ~~~~~~~~~~ - - Implements a Markup string. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -import re -from itertools import imap - - -__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent'] - - -_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') -_entity_re = re.compile(r'&([^;]+);') - - -class Markup(unicode): - r"""Marks a string as being safe for inclusion in HTML/XML output without - needing to be escaped. This implements the `__html__` interface a couple - of frameworks and web applications use. :class:`Markup` is a direct - subclass of `unicode` and provides all the methods of `unicode` just that - it escapes arguments passed and always returns `Markup`. - - The `escape` function returns markup objects so that double escaping can't - happen. - - The constructor of the :class:`Markup` class can be used for three - different things: When passed an unicode object it's assumed to be safe, - when passed an object with an HTML representation (has an `__html__` - method) that representation is used, otherwise the object passed is - converted into a unicode string and then assumed to be safe: - - >>> Markup("Hello <em>World</em>!") - Markup(u'Hello <em>World</em>!') - >>> class Foo(object): - ... def __html__(self): - ... return '<a href="#">foo</a>' - ... - >>> Markup(Foo()) - Markup(u'<a href="#">foo</a>') - - If you want object passed being always treated as unsafe you can use the - :meth:`escape` classmethod to create a :class:`Markup` object: - - >>> Markup.escape("Hello <em>World</em>!") - Markup(u'Hello <em>World</em>!') - - Operations on a markup string are markup aware which means that all - arguments are passed through the :func:`escape` function: - - >>> em = Markup("<em>%s</em>") - >>> em % "foo & bar" - Markup(u'<em>foo & bar</em>') - >>> strong = Markup("<strong>%(text)s</strong>") - >>> strong % {'text': '<blink>hacker here</blink>'} - Markup(u'<strong><blink>hacker here</blink></strong>') - >>> Markup("<em>Hello</em> ") + "<foo>" - Markup(u'<em>Hello</em> <foo>') - """ - __slots__ = () - - def __new__(cls, base=u'', encoding=None, errors='strict'): - if hasattr(base, '__html__'): - base = base.__html__() - if encoding is None: - return unicode.__new__(cls, base) - return unicode.__new__(cls, base, encoding, errors) - - def __html__(self): - return self - - def __add__(self, other): - if hasattr(other, '__html__') or isinstance(other, basestring): - return self.__class__(unicode(self) + unicode(escape(other))) - return NotImplemented - - def __radd__(self, other): - if hasattr(other, '__html__') or isinstance(other, basestring): - return self.__class__(unicode(escape(other)) + unicode(self)) - return NotImplemented - - def __mul__(self, num): - if isinstance(num, (int, long)): - return self.__class__(unicode.__mul__(self, num)) - return NotImplemented - __rmul__ = __mul__ - - def __mod__(self, arg): - if isinstance(arg, tuple): - arg = tuple(imap(_MarkupEscapeHelper, arg)) - else: - arg = _MarkupEscapeHelper(arg) - return self.__class__(unicode.__mod__(self, arg)) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - unicode.__repr__(self) - ) - - def join(self, seq): - return self.__class__(unicode.join(self, imap(escape, seq))) - join.__doc__ = unicode.join.__doc__ - - def split(self, *args, **kwargs): - return map(self.__class__, unicode.split(self, *args, **kwargs)) - split.__doc__ = unicode.split.__doc__ - - def rsplit(self, *args, **kwargs): - return map(self.__class__, unicode.rsplit(self, *args, **kwargs)) - rsplit.__doc__ = unicode.rsplit.__doc__ - - def splitlines(self, *args, **kwargs): - return map(self.__class__, unicode.splitlines(self, *args, **kwargs)) - splitlines.__doc__ = unicode.splitlines.__doc__ - - def unescape(self): - r"""Unescape markup again into an unicode string. This also resolves - known HTML4 and XHTML entities: - - >>> Markup("Main » <em>About</em>").unescape() - u'Main \xbb <em>About</em>' - """ - from jinja2._markupsafe._constants import HTML_ENTITIES - def handle_match(m): - name = m.group(1) - if name in HTML_ENTITIES: - return unichr(HTML_ENTITIES[name]) - try: - if name[:2] in ('#x', '#X'): - return unichr(int(name[2:], 16)) - elif name.startswith('#'): - return unichr(int(name[1:])) - except ValueError: - pass - return u'' - return _entity_re.sub(handle_match, unicode(self)) - - def striptags(self): - r"""Unescape markup into an unicode string and strip all tags. This - also resolves known HTML4 and XHTML entities. Whitespace is - normalized to one: - - >>> Markup("Main » <em>About</em>").striptags() - u'Main \xbb About' - """ - stripped = u' '.join(_striptags_re.sub('', self).split()) - return Markup(stripped).unescape() - - @classmethod - def escape(cls, s): - """Escape the string. Works like :func:`escape` with the difference - that for subclasses of :class:`Markup` this function would return the - correct subclass. - """ - rv = escape(s) - if rv.__class__ is not cls: - return cls(rv) - return rv - - def make_wrapper(name): - orig = getattr(unicode, name) - def func(self, *args, **kwargs): - args = _escape_argspec(list(args), enumerate(args)) - _escape_argspec(kwargs, kwargs.iteritems()) - return self.__class__(orig(self, *args, **kwargs)) - func.__name__ = orig.__name__ - func.__doc__ = orig.__doc__ - return func - - for method in '__getitem__', 'capitalize', \ - 'title', 'lower', 'upper', 'replace', 'ljust', \ - 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \ - 'translate', 'expandtabs', 'swapcase', 'zfill': - locals()[method] = make_wrapper(method) - - # new in python 2.5 - if hasattr(unicode, 'partition'): - partition = make_wrapper('partition'), - rpartition = make_wrapper('rpartition') - - # new in python 2.6 - if hasattr(unicode, 'format'): - format = make_wrapper('format') - - # not in python 3 - if hasattr(unicode, '__getslice__'): - __getslice__ = make_wrapper('__getslice__') - - del method, make_wrapper - - -def _escape_argspec(obj, iterable): - """Helper for various string-wrapped functions.""" - for key, value in iterable: - if hasattr(value, '__html__') or isinstance(value, basestring): - obj[key] = escape(value) - return obj - - -class _MarkupEscapeHelper(object): - """Helper for Markup.__mod__""" - - def __init__(self, obj): - self.obj = obj - - __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x]) - __str__ = lambda s: str(escape(s.obj)) - __unicode__ = lambda s: unicode(escape(s.obj)) - __repr__ = lambda s: str(escape(repr(s.obj))) - __int__ = lambda s: int(s.obj) - __float__ = lambda s: float(s.obj) - - -# we have to import it down here as the speedups and native -# modules imports the markup type which is define above. -try: - from jinja2._markupsafe._speedups import escape, escape_silent, soft_unicode -except ImportError: - from jinja2._markupsafe._native import escape, escape_silent, soft_unicode diff --git a/module/lib/jinja2/_markupsafe/_bundle.py b/module/lib/jinja2/_markupsafe/_bundle.py deleted file mode 100644 index e694faf23..000000000 --- a/module/lib/jinja2/_markupsafe/_bundle.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2._markupsafe._bundle - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This script pulls in markupsafe from a source folder and - bundles it with Jinja2. It does not pull in the speedups - module though. - - :copyright: Copyright 2010 by the Jinja team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" -import sys -import os -import re - - -def rewrite_imports(lines): - for idx, line in enumerate(lines): - new_line = re.sub(r'(import|from)\s+markupsafe\b', - r'\1 jinja2._markupsafe', line) - if new_line != line: - lines[idx] = new_line - - -def main(): - if len(sys.argv) != 2: - print 'error: only argument is path to markupsafe' - sys.exit(1) - basedir = os.path.dirname(__file__) - markupdir = sys.argv[1] - for filename in os.listdir(markupdir): - if filename.endswith('.py'): - f = open(os.path.join(markupdir, filename)) - try: - lines = list(f) - finally: - f.close() - rewrite_imports(lines) - f = open(os.path.join(basedir, filename), 'w') - try: - for line in lines: - f.write(line) - finally: - f.close() - - -if __name__ == '__main__': - main() diff --git a/module/lib/jinja2/_markupsafe/_constants.py b/module/lib/jinja2/_markupsafe/_constants.py deleted file mode 100644 index 919bf03c5..000000000 --- a/module/lib/jinja2/_markupsafe/_constants.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -""" - markupsafe._constants - ~~~~~~~~~~~~~~~~~~~~~ - - Highlevel implementation of the Markup string. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" - - -HTML_ENTITIES = { - 'AElig': 198, - 'Aacute': 193, - 'Acirc': 194, - 'Agrave': 192, - 'Alpha': 913, - 'Aring': 197, - 'Atilde': 195, - 'Auml': 196, - 'Beta': 914, - 'Ccedil': 199, - 'Chi': 935, - 'Dagger': 8225, - 'Delta': 916, - 'ETH': 208, - 'Eacute': 201, - 'Ecirc': 202, - 'Egrave': 200, - 'Epsilon': 917, - 'Eta': 919, - 'Euml': 203, - 'Gamma': 915, - 'Iacute': 205, - 'Icirc': 206, - 'Igrave': 204, - 'Iota': 921, - 'Iuml': 207, - 'Kappa': 922, - 'Lambda': 923, - 'Mu': 924, - 'Ntilde': 209, - 'Nu': 925, - 'OElig': 338, - 'Oacute': 211, - 'Ocirc': 212, - 'Ograve': 210, - 'Omega': 937, - 'Omicron': 927, - 'Oslash': 216, - 'Otilde': 213, - 'Ouml': 214, - 'Phi': 934, - 'Pi': 928, - 'Prime': 8243, - 'Psi': 936, - 'Rho': 929, - 'Scaron': 352, - 'Sigma': 931, - 'THORN': 222, - 'Tau': 932, - 'Theta': 920, - 'Uacute': 218, - 'Ucirc': 219, - 'Ugrave': 217, - 'Upsilon': 933, - 'Uuml': 220, - 'Xi': 926, - 'Yacute': 221, - 'Yuml': 376, - 'Zeta': 918, - 'aacute': 225, - 'acirc': 226, - 'acute': 180, - 'aelig': 230, - 'agrave': 224, - 'alefsym': 8501, - 'alpha': 945, - 'amp': 38, - 'and': 8743, - 'ang': 8736, - 'apos': 39, - 'aring': 229, - 'asymp': 8776, - 'atilde': 227, - 'auml': 228, - 'bdquo': 8222, - 'beta': 946, - 'brvbar': 166, - 'bull': 8226, - 'cap': 8745, - 'ccedil': 231, - 'cedil': 184, - 'cent': 162, - 'chi': 967, - 'circ': 710, - 'clubs': 9827, - 'cong': 8773, - 'copy': 169, - 'crarr': 8629, - 'cup': 8746, - 'curren': 164, - 'dArr': 8659, - 'dagger': 8224, - 'darr': 8595, - 'deg': 176, - 'delta': 948, - 'diams': 9830, - 'divide': 247, - 'eacute': 233, - 'ecirc': 234, - 'egrave': 232, - 'empty': 8709, - 'emsp': 8195, - 'ensp': 8194, - 'epsilon': 949, - 'equiv': 8801, - 'eta': 951, - 'eth': 240, - 'euml': 235, - 'euro': 8364, - 'exist': 8707, - 'fnof': 402, - 'forall': 8704, - 'frac12': 189, - 'frac14': 188, - 'frac34': 190, - 'frasl': 8260, - 'gamma': 947, - 'ge': 8805, - 'gt': 62, - 'hArr': 8660, - 'harr': 8596, - 'hearts': 9829, - 'hellip': 8230, - 'iacute': 237, - 'icirc': 238, - 'iexcl': 161, - 'igrave': 236, - 'image': 8465, - 'infin': 8734, - 'int': 8747, - 'iota': 953, - 'iquest': 191, - 'isin': 8712, - 'iuml': 239, - 'kappa': 954, - 'lArr': 8656, - 'lambda': 955, - 'lang': 9001, - 'laquo': 171, - 'larr': 8592, - 'lceil': 8968, - 'ldquo': 8220, - 'le': 8804, - 'lfloor': 8970, - 'lowast': 8727, - 'loz': 9674, - 'lrm': 8206, - 'lsaquo': 8249, - 'lsquo': 8216, - 'lt': 60, - 'macr': 175, - 'mdash': 8212, - 'micro': 181, - 'middot': 183, - 'minus': 8722, - 'mu': 956, - 'nabla': 8711, - 'nbsp': 160, - 'ndash': 8211, - 'ne': 8800, - 'ni': 8715, - 'not': 172, - 'notin': 8713, - 'nsub': 8836, - 'ntilde': 241, - 'nu': 957, - 'oacute': 243, - 'ocirc': 244, - 'oelig': 339, - 'ograve': 242, - 'oline': 8254, - 'omega': 969, - 'omicron': 959, - 'oplus': 8853, - 'or': 8744, - 'ordf': 170, - 'ordm': 186, - 'oslash': 248, - 'otilde': 245, - 'otimes': 8855, - 'ouml': 246, - 'para': 182, - 'part': 8706, - 'permil': 8240, - 'perp': 8869, - 'phi': 966, - 'pi': 960, - 'piv': 982, - 'plusmn': 177, - 'pound': 163, - 'prime': 8242, - 'prod': 8719, - 'prop': 8733, - 'psi': 968, - 'quot': 34, - 'rArr': 8658, - 'radic': 8730, - 'rang': 9002, - 'raquo': 187, - 'rarr': 8594, - 'rceil': 8969, - 'rdquo': 8221, - 'real': 8476, - 'reg': 174, - 'rfloor': 8971, - 'rho': 961, - 'rlm': 8207, - 'rsaquo': 8250, - 'rsquo': 8217, - 'sbquo': 8218, - 'scaron': 353, - 'sdot': 8901, - 'sect': 167, - 'shy': 173, - 'sigma': 963, - 'sigmaf': 962, - 'sim': 8764, - 'spades': 9824, - 'sub': 8834, - 'sube': 8838, - 'sum': 8721, - 'sup': 8835, - 'sup1': 185, - 'sup2': 178, - 'sup3': 179, - 'supe': 8839, - 'szlig': 223, - 'tau': 964, - 'there4': 8756, - 'theta': 952, - 'thetasym': 977, - 'thinsp': 8201, - 'thorn': 254, - 'tilde': 732, - 'times': 215, - 'trade': 8482, - 'uArr': 8657, - 'uacute': 250, - 'uarr': 8593, - 'ucirc': 251, - 'ugrave': 249, - 'uml': 168, - 'upsih': 978, - 'upsilon': 965, - 'uuml': 252, - 'weierp': 8472, - 'xi': 958, - 'yacute': 253, - 'yen': 165, - 'yuml': 255, - 'zeta': 950, - 'zwj': 8205, - 'zwnj': 8204 -} diff --git a/module/lib/jinja2/_markupsafe/_native.py b/module/lib/jinja2/_markupsafe/_native.py deleted file mode 100644 index 7b95828ec..000000000 --- a/module/lib/jinja2/_markupsafe/_native.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -""" - markupsafe._native - ~~~~~~~~~~~~~~~~~~ - - Native Python implementation the C module is not compiled. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from jinja2._markupsafe import Markup - - -def escape(s): - """Convert the characters &, <, >, ' and " in string s to HTML-safe - sequences. Use this if you need to display text that might contain - such characters in HTML. Marks return value as markup string. - """ - if hasattr(s, '__html__'): - return s.__html__() - return Markup(unicode(s) - .replace('&', '&') - .replace('>', '>') - .replace('<', '<') - .replace("'", ''') - .replace('"', '"') - ) - - -def escape_silent(s): - """Like :func:`escape` but converts `None` into an empty - markup string. - """ - if s is None: - return Markup() - return escape(s) - - -def soft_unicode(s): - """Make a string unicode if it isn't already. That way a markup - string is not converted back to unicode. - """ - if not isinstance(s, unicode): - s = unicode(s) - return s diff --git a/module/lib/jinja2/_markupsafe/tests.py b/module/lib/jinja2/_markupsafe/tests.py deleted file mode 100644 index c1ce3943a..000000000 --- a/module/lib/jinja2/_markupsafe/tests.py +++ /dev/null @@ -1,80 +0,0 @@ -import gc -import unittest -from jinja2._markupsafe import Markup, escape, escape_silent - - -class MarkupTestCase(unittest.TestCase): - - def test_markup_operations(self): - # adding two strings should escape the unsafe one - unsafe = '<script type="application/x-some-script">alert("foo");</script>' - safe = Markup('<em>username</em>') - assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe) - - # string interpolations are safe to use too - assert Markup('<em>%s</em>') % '<bad user>' == \ - '<em><bad user></em>' - assert Markup('<em>%(username)s</em>') % { - 'username': '<bad user>' - } == '<em><bad user></em>' - - # an escaped object is markup too - assert type(Markup('foo') + 'bar') is Markup - - # and it implements __html__ by returning itself - x = Markup("foo") - assert x.__html__() is x - - # it also knows how to treat __html__ objects - class Foo(object): - def __html__(self): - return '<em>awesome</em>' - def __unicode__(self): - return 'awesome' - assert Markup(Foo()) == '<em>awesome</em>' - assert Markup('<strong>%s</strong>') % Foo() == \ - '<strong><em>awesome</em></strong>' - - # escaping and unescaping - assert escape('"<>&\'') == '"<>&'' - assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar" - assert Markup("<test>").unescape() == "<test>" - - def test_all_set(self): - import jinja2._markupsafe as markup - for item in markup.__all__: - getattr(markup, item) - - def test_escape_silent(self): - assert escape_silent(None) == Markup() - assert escape(None) == Markup(None) - assert escape_silent('<foo>') == Markup(u'<foo>') - - -class MarkupLeakTestCase(unittest.TestCase): - - def test_markup_leaks(self): - counts = set() - for count in xrange(20): - for item in xrange(1000): - escape("foo") - escape("<foo>") - escape(u"foo") - escape(u"<foo>") - counts.add(len(gc.get_objects())) - assert len(counts) == 1, 'ouch, c extension seems to leak objects' - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(MarkupTestCase)) - - # this test only tests the c extension - if not hasattr(escape, 'func_code'): - suite.addTest(unittest.makeSuite(MarkupLeakTestCase)) - - return suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/module/lib/jinja2/_stringdefs.py b/module/lib/jinja2/_stringdefs.py deleted file mode 100644 index 1161b7f4a..000000000 --- a/module/lib/jinja2/_stringdefs.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2._stringdefs - ~~~~~~~~~~~~~~~~~~ - - Strings of all Unicode characters of a certain category. - Used for matching in Unicode-aware languages. Run to regenerate. - - Inspired by chartypes_create.py from the MoinMoin project, original - implementation from Pygments. - - :copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f' - -Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb' - -Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a\u0d3b\u0d3c\u0d3d\u0d44\u0d45\u0d49\u0d4e\u0d4f\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d62\u0d63\u0d64\u0d65\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d84\u0d97\u0d98\u0d99\u0db2\u0dbc\u0dbe\u0dbf\u0dc7\u0dc8\u0dc9\u0dcb\u0dcc\u0dcd\u0dce\u0dd5\u0dd7\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e3b\u0e3c\u0e3d\u0e3e\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e83\u0e85\u0e86\u0e89\u0e8b\u0e8c\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8\u0ea9\u0eac\u0eba\u0ebe\u0ebf\u0ec5\u0ec7\u0ece\u0ecf\u0eda\u0edb\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f48\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f8c\u0f8d\u0f8e\u0f8f\u0f98\u0fbd\u0fcd\u0fce\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1022\u1028\u102b\u1033\u1034\u1035\u103a\u103b\u103c\u103d\u103e\u103f\u105a\u105b\u105c\u105d\u105e\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u10c6\u10c7\u10c8\u10c9\u10ca\u10cb\u10cc\u10cd\u10ce\u10cf\u10fd\u10fe\u10ff\u115a\u115b\u115c\u115d\u115e\u11a3\u11a4\u11a5\u11a6\u11a7\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\u1249\u124e\u124f\u1257\u1259\u125e\u125f\u1289\u128e\u128f\u12b1\u12b6\u12b7\u12bf\u12c1\u12c6\u12c7\u12d7\u1311\u1316\u1317\u135b\u135c\u135d\u135e\u137d\u137e\u137f\u139a\u139b\u139c\u139d\u139e\u139f\u13f5\u13f6\u13f7\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u169d\u169e\u169f\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u16f9\u16fa\u16fb\u16fc\u16fd\u16fe\u16ff\u170d\u1715\u1716\u1717\u1718\u1719\u171a\u171b\u171c\u171d\u171e\u171f\u1737\u1738\u1739\u173a\u173b\u173c\u173d\u173e\u173f\u1754\u1755\u1756\u1757\u1758\u1759\u175a\u175b\u175c\u175d\u175e\u175f\u176d\u1771\u1774\u1775\u1776\u1777\u1778\u1779\u177a\u177b\u177c\u177d\u177e\u177f\u17de\u17df\u17ea\u17eb\u17ec\u17ed\u17ee\u17ef\u17fa\u17fb\u17fc\u17fd\u17fe\u17ff\u180f\u181a\u181b\u181c\u181d\u181e\u181f\u1878\u1879\u187a\u187b\u187c\u187d\u187e\u187f\u18aa\u18ab\u18ac\u18ad\u18ae\u18af\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u18f6\u18f7\u18f8\u18f9\u18fa\u18fb\u18fc\u18fd\u18fe\u18ff\u191d\u191e\u191f\u192c\u192d\u192e\u192f\u193c\u193d\u193e\u193f\u1941\u1942\u1943\u196e\u196f\u1975\u1976\u1977\u1978\u1979\u197a\u197b\u197c\u197d\u197e\u197f\u19aa\u19ab\u19ac\u19ad\u19ae\u19af\u19ca\u19cb\u19cc\u19cd\u19ce\u19cf\u19da\u19db\u19dc\u19dd\u1a1c\u1a1d\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a53\u1a54\u1a55\u1a56\u1a57\u1a58\u1a59\u1a5a\u1a5b\u1a5c\u1a5d\u1a5e\u1a5f\u1a60\u1a61\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6b\u1a6c\u1a6d\u1a6e\u1a6f\u1a70\u1a71\u1a72\u1a73\u1a74\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1a7d\u1a7e\u1a7f\u1a80\u1a81\u1a82\u1a83\u1a84\u1a85\u1a86\u1a87\u1a88\u1a89\u1a8a\u1a8b\u1a8c\u1a8d\u1a8e\u1a8f\u1a90\u1a91\u1a92\u1a93\u1a94\u1a95\u1a96\u1a97\u1a98\u1a99\u1a9a\u1a9b\u1a9c\u1a9d\u1a9e\u1a9f\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aa7\u1aa8\u1aa9\u1aaa\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abb\u1abc\u1abd\u1abe\u1abf\u1ac0\u1ac1\u1ac2\u1ac3\u1ac4\u1ac5\u1ac6\u1ac7\u1ac8\u1ac9\u1aca\u1acb\u1acc\u1acd\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u1ade\u1adf\u1ae0\u1ae1\u1ae2\u1ae3\u1ae4\u1ae5\u1ae6\u1ae7\u1ae8\u1ae9\u1aea\u1aeb\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u1af7\u1af8\u1af9\u1afa\u1afb\u1afc\u1afd\u1afe\u1aff\u1b00\u1b01\u1b02\u1b03\u1b04\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b23\u1b24\u1b25\u1b26\u1b27\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b30\u1b31\u1b32\u1b33\u1b34\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u1b4c\u1b4d\u1b4e\u1b4f\u1b50\u1b51\u1b52\u1b53\u1b54\u1b55\u1b56\u1b57\u1b58\u1b59\u1b5a\u1b5b\u1b5c\u1b5d\u1b5e\u1b5f\u1b60\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b6b\u1b6c\u1b6d\u1b6e\u1b6f\u1b70\u1b71\u1b72\u1b73\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u1b7d\u1b7e\u1b7f\u1b80\u1b81\u1b82\u1b83\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1b99\u1b9a\u1b9b\u1b9c\u1b9d\u1b9e\u1b9f\u1ba0\u1ba1\u1ba2\u1ba3\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u1bac\u1bad\u1bae\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u1bb6\u1bb7\u1bb8\u1bb9\u1bba\u1bbb\u1bbc\u1bbd\u1bbe\u1bbf\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be6\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\u1bf4\u1bf5\u1bf6\u1bf7\u1bf8\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c1b\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c24\u1c25\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\u1c36\u1c37\u1c38\u1c39\u1c3a\u1c3b\u1c3c\u1c3d\u1c3e\u1c3f\u1c40\u1c41\u1c42\u1c43\u1c44\u1c45\u1c46\u1c47\u1c48\u1c49\u1c4a\u1c4b\u1c4c\u1c4d\u1c4e\u1c4f\u1c50\u1c51\u1c52\u1c53\u1c54\u1c55\u1c56\u1c57\u1c58\u1c59\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\u1c7e\u1c7f\u1c80\u1c81\u1c82\u1c83\u1c84\u1c85\u1c86\u1c87\u1c88\u1c89\u1c8a\u1c8b\u1c8c\u1c8d\u1c8e\u1c8f\u1c90\u1c91\u1c92\u1c93\u1c94\u1c95\u1c96\u1c97\u1c98\u1c99\u1c9a\u1c9b\u1c9c\u1c9d\u1c9e\u1c9f\u1ca0\u1ca1\u1ca2\u1ca3\u1ca4\u1ca5\u1ca6\u1ca7\u1ca8\u1ca9\u1caa\u1cab\u1cac\u1cad\u1cae\u1caf\u1cb0\u1cb1\u1cb2\u1cb3\u1cb4\u1cb5\u1cb6\u1cb7\u1cb8\u1cb9\u1cba\u1cbb\u1cbc\u1cbd\u1cbe\u1cbf\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u1cc8\u1cc9\u1cca\u1ccb\u1ccc\u1ccd\u1cce\u1ccf\u1cd0\u1cd1\u1cd2\u1cd3\u1cd4\u1cd5\u1cd6\u1cd7\u1cd8\u1cd9\u1cda\u1cdb\u1cdc\u1cdd\u1cde\u1cdf\u1ce0\u1ce1\u1ce2\u1ce3\u1ce4\u1ce5\u1ce6\u1ce7\u1ce8\u1ce9\u1cea\u1ceb\u1cec\u1ced\u1cee\u1cef\u1cf0\u1cf1\u1cf2\u1cf3\u1cf4\u1cf5\u1cf6\u1cf7\u1cf8\u1cf9\u1cfa\u1cfb\u1cfc\u1cfd\u1cfe\u1cff\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dca\u1dcb\u1dcc\u1dcd\u1dce\u1dcf\u1dd0\u1dd1\u1dd2\u1dd3\u1dd4\u1dd5\u1dd6\u1dd7\u1dd8\u1dd9\u1dda\u1ddb\u1ddc\u1ddd\u1dde\u1ddf\u1de0\u1de1\u1de2\u1de3\u1de4\u1de5\u1de6\u1de7\u1de8\u1de9\u1dea\u1deb\u1dec\u1ded\u1dee\u1def\u1df0\u1df1\u1df2\u1df3\u1df4\u1df5\u1df6\u1df7\u1df8\u1df9\u1dfa\u1dfb\u1dfc\u1dfd\u1dfe\u1dff\u1e9c\u1e9d\u1e9e\u1e9f\u1efa\u1efb\u1efc\u1efd\u1efe\u1eff\u1f16\u1f17\u1f1e\u1f1f\u1f46\u1f47\u1f4e\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e\u1f7f\u1fb5\u1fc5\u1fd4\u1fd5\u1fdc\u1ff0\u1ff1\u1ff5\u1fff\u2064\u2065\u2066\u2067\u2068\u2069\u2072\u2073\u208f\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u209d\u209e\u209f\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u20c0\u20c1\u20c2\u20c3\u20c4\u20c5\u20c6\u20c7\u20c8\u20c9\u20ca\u20cb\u20cc\u20cd\u20ce\u20cf\u20ec\u20ed\u20ee\u20ef\u20f0\u20f1\u20f2\u20f3\u20f4\u20f5\u20f6\u20f7\u20f8\u20f9\u20fa\u20fb\u20fc\u20fd\u20fe\u20ff\u214d\u214e\u214f\u2150\u2151\u2152\u2184\u2185\u2186\u2187\u2188\u2189\u218a\u218b\u218c\u218d\u218e\u218f\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2427\u2428\u2429\u242a\u242b\u242c\u242d\u242e\u242f\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u2437\u2438\u2439\u243a\u243b\u243c\u243d\u243e\u243f\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u2453\u2454\u2455\u2456\u2457\u2458\u2459\u245a\u245b\u245c\u245d\u245e\u245f\u269d\u269e\u269f\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2705\u270a\u270b\u2728\u274c\u274e\u2753\u2754\u2755\u2757\u275f\u2760\u2795\u2796\u2797\u27b0\u27bf\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27ec\u27ed\u27ee\u27ef\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2c2f\u2c5f\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf5\u2cf6\u2cf7\u2cf8\u2d26\u2d27\u2d28\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d6b\u2d6c\u2d6d\u2d6e\u2d70\u2d71\u2d72\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2d9c\u2d9d\u2d9e\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de7\u2de8\u2de9\u2dea\u2deb\u2dec\u2ded\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dfe\u2dff\u2e18\u2e19\u2e1a\u2e1b\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u2e2f\u2e30\u2e31\u2e32\u2e33\u2e34\u2e35\u2e36\u2e37\u2e38\u2e39\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e41\u2e42\u2e43\u2e44\u2e45\u2e46\u2e47\u2e48\u2e49\u2e4a\u2e4b\u2e4c\u2e4d\u2e4e\u2e4f\u2e50\u2e51\u2e52\u2e53\u2e54\u2e55\u2e56\u2e57\u2e58\u2e59\u2e5a\u2e5b\u2e5c\u2e5d\u2e5e\u2e5f\u2e60\u2e61\u2e62\u2e63\u2e64\u2e65\u2e66\u2e67\u2e68\u2e69\u2e6a\u2e6b\u2e6c\u2e6d\u2e6e\u2e6f\u2e70\u2e71\u2e72\u2e73\u2e74\u2e75\u2e76\u2e77\u2e78\u2e79\u2e7a\u2e7b\u2e7c\u2e7d\u2e7e\u2e7f\u2e9a\u2ef4\u2ef5\u2ef6\u2ef7\u2ef8\u2ef9\u2efa\u2efb\u2efc\u2efd\u2efe\u2eff\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ffc\u2ffd\u2ffe\u2fff\u3040\u3097\u3098\u3100\u3101\u3102\u3103\u3104\u312d\u312e\u312f\u3130\u318f\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u321f\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u32ff\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dbf\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcc\u9fcd\u9fce\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe4\u9fe5\u9fe6\u9fe7\u9fe8\u9fe9\u9fea\u9feb\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff1\u9ff2\u9ff3\u9ff4\u9ff5\u9ff6\u9ff7\u9ff8\u9ff9\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua48d\ua48e\ua48f\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua630\ua631\ua632\ua633\ua634\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua63c\ua63d\ua63e\ua63f\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua64e\ua64f\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua658\ua659\ua65a\ua65b\ua65c\ua65d\ua65e\ua65f\ua660\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua668\ua669\ua66a\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua676\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68d\ua68e\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua698\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f1\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6f9\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua722\ua723\ua724\ua725\ua726\ua727\ua728\ua729\ua72a\ua72b\ua72c\ua72d\ua72e\ua72f\ua730\ua731\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua740\ua741\ua742\ua743\ua744\ua745\ua746\ua747\ua748\ua749\ua74a\ua74b\ua74c\ua74d\ua74e\ua74f\ua750\ua751\ua752\ua753\ua754\ua755\ua756\ua757\ua758\ua759\ua75a\ua75b\ua75c\ua75d\ua75e\ua75f\ua760\ua761\ua762\ua763\ua764\ua765\ua766\ua767\ua768\ua769\ua76a\ua76b\ua76c\ua76d\ua76e\ua76f\ua770\ua771\ua772\ua773\ua774\ua775\ua776\ua777\ua778\ua779\ua77a\ua77b\ua77c\ua77d\ua77e\ua77f\ua780\ua781\ua782\ua783\ua784\ua785\ua786\ua787\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua78f\ua790\ua791\ua792\ua793\ua794\ua795\ua796\ua797\ua798\ua799\ua79a\ua79b\ua79c\ua79d\ua79e\ua79f\ua7a0\ua7a1\ua7a2\ua7a3\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7ae\ua7af\ua7b0\ua7b1\ua7b2\ua7b3\ua7b4\ua7b5\ua7b6\ua7b7\ua7b8\ua7b9\ua7ba\ua7bb\ua7bc\ua7bd\ua7be\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7c7\ua7c8\ua7c9\ua7ca\ua7cb\ua7cc\ua7cd\ua7ce\ua7cf\ua7d0\ua7d1\ua7d2\ua7d3\ua7d4\ua7d5\ua7d6\ua7d7\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7de\ua7df\ua7e0\ua7e1\ua7e2\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7eb\ua7ec\ua7ed\ua7ee\ua7ef\ua7f0\ua7f1\ua7f2\ua7f3\ua7f4\ua7f5\ua7f6\ua7f7\ua7f8\ua7f9\ua7fa\ua7fb\ua7fc\ua7fd\ua7fe\ua7ff\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua837\ua838\ua839\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua854\ua855\ua856\ua857\ua858\ua859\ua85a\ua85b\ua85c\ua85d\ua85e\ua85f\ua860\ua861\ua862\ua863\ua864\ua865\ua866\ua867\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\ua878\ua879\ua87a\ua87b\ua87c\ua87d\ua87e\ua87f\ua880\ua881\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\ua8c5\ua8c6\ua8c7\ua8c8\ua8c9\ua8ca\ua8cb\ua8cc\ua8cd\ua8ce\ua8cf\ua8d0\ua8d1\ua8d2\ua8d3\ua8d4\ua8d5\ua8d6\ua8d7\ua8d8\ua8d9\ua8da\ua8db\ua8dc\ua8dd\ua8de\ua8df\ua8e0\ua8e1\ua8e2\ua8e3\ua8e4\ua8e5\ua8e6\ua8e7\ua8e8\ua8e9\ua8ea\ua8eb\ua8ec\ua8ed\ua8ee\ua8ef\ua8f0\ua8f1\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8f8\ua8f9\ua8fa\ua8fb\ua8fc\ua8fd\ua8fe\ua8ff\ua900\ua901\ua902\ua903\ua904\ua905\ua906\ua907\ua908\ua909\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\ua92b\ua92c\ua92d\ua92e\ua92f\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua954\ua955\ua956\ua957\ua958\ua959\ua95a\ua95b\ua95c\ua95d\ua95e\ua95f\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\ua97d\ua97e\ua97f\ua980\ua981\ua982\ua983\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9ab\ua9ac\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b3\ua9b4\ua9b5\ua9b6\ua9b7\ua9b8\ua9b9\ua9ba\ua9bb\ua9bc\ua9bd\ua9be\ua9bf\ua9c0\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9c7\ua9c8\ua9c9\ua9ca\ua9cb\ua9cc\ua9cd\ua9ce\ua9cf\ua9d0\ua9d1\ua9d2\ua9d3\ua9d4\ua9d5\ua9d6\ua9d7\ua9d8\ua9d9\ua9da\ua9db\ua9dc\ua9dd\ua9de\ua9df\ua9e0\ua9e1\ua9e2\ua9e3\ua9e4\ua9e5\ua9e6\ua9e7\ua9e8\ua9e9\ua9ea\ua9eb\ua9ec\ua9ed\ua9ee\ua9ef\ua9f0\ua9f1\ua9f2\ua9f3\ua9f4\ua9f5\ua9f6\ua9f7\ua9f8\ua9f9\ua9fa\ua9fb\ua9fc\ua9fd\ua9fe\ua9ff\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa33\uaa34\uaa35\uaa36\uaa37\uaa38\uaa39\uaa3a\uaa3b\uaa3c\uaa3d\uaa3e\uaa3f\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\uaa4e\uaa4f\uaa50\uaa51\uaa52\uaa53\uaa54\uaa55\uaa56\uaa57\uaa58\uaa59\uaa5a\uaa5b\uaa5c\uaa5d\uaa5e\uaa5f\uaa60\uaa61\uaa62\uaa63\uaa64\uaa65\uaa66\uaa67\uaa68\uaa69\uaa6a\uaa6b\uaa6c\uaa6d\uaa6e\uaa6f\uaa70\uaa71\uaa72\uaa73\uaa74\uaa75\uaa76\uaa77\uaa78\uaa79\uaa7a\uaa7b\uaa7c\uaa7d\uaa7e\uaa7f\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaabf\uaac0\uaac1\uaac2\uaac3\uaac4\uaac5\uaac6\uaac7\uaac8\uaac9\uaaca\uaacb\uaacc\uaacd\uaace\uaacf\uaad0\uaad1\uaad2\uaad3\uaad4\uaad5\uaad6\uaad7\uaad8\uaad9\uaada\uaadb\uaadc\uaadd\uaade\uaadf\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf0\uaaf1\uaaf2\uaaf3\uaaf4\uaaf5\uaaf6\uaaf7\uaaf8\uaaf9\uaafa\uaafb\uaafc\uaafd\uaafe\uaaff\uab00\uab01\uab02\uab03\uab04\uab05\uab06\uab07\uab08\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\uab0f\uab10\uab11\uab12\uab13\uab14\uab15\uab16\uab17\uab18\uab19\uab1a\uab1b\uab1c\uab1d\uab1e\uab1f\uab20\uab21\uab22\uab23\uab24\uab25\uab26\uab27\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\uab2f\uab30\uab31\uab32\uab33\uab34\uab35\uab36\uab37\uab38\uab39\uab3a\uab3b\uab3c\uab3d\uab3e\uab3f\uab40\uab41\uab42\uab43\uab44\uab45\uab46\uab47\uab48\uab49\uab4a\uab4b\uab4c\uab4d\uab4e\uab4f\uab50\uab51\uab52\uab53\uab54\uab55\uab56\uab57\uab58\uab59\uab5a\uab5b\uab5c\uab5d\uab5e\uab5f\uab60\uab61\uab62\uab63\uab64\uab65\uab66\uab67\uab68\uab69\uab6a\uab6b\uab6c\uab6d\uab6e\uab6f\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79\uab7a\uab7b\uab7c\uab7d\uab7e\uab7f\uab80\uab81\uab82\uab83\uab84\uab85\uab86\uab87\uab88\uab89\uab8a\uab8b\uab8c\uab8d\uab8e\uab8f\uab90\uab91\uab92\uab93\uab94\uab95\uab96\uab97\uab98\uab99\uab9a\uab9b\uab9c\uab9d\uab9e\uab9f\uaba0\uaba1\uaba2\uaba3\uaba4\uaba5\uaba6\uaba7\uaba8\uaba9\uabaa\uabab\uabac\uabad\uabae\uabaf\uabb0\uabb1\uabb2\uabb3\uabb4\uabb5\uabb6\uabb7\uabb8\uabb9\uabba\uabbb\uabbc\uabbd\uabbe\uabbf\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uabeb\uabec\uabed\uabee\uabef\uabf0\uabf1\uabf2\uabf3\uabf4\uabf5\uabf6\uabf7\uabf8\uabf9\uabfa\uabfb\uabfc\uabfd\uabfe\uabff\ud7a4\ud7a5\ud7a6\ud7a7\ud7a8\ud7a9\ud7aa\ud7ab\ud7ac\ud7ad\ud7ae\ud7af\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\ud7c7\ud7c8\ud7c9\ud7ca\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\ud7fc\ud7fd\ud7fe\ud7ff\ufa2e\ufa2f\ufa6b\ufa6c\ufa6d\ufa6e\ufa6f\ufada\ufadb\ufadc\ufadd\ufade\ufadf\ufae0\ufae1\ufae2\ufae3\ufae4\ufae5\ufae6\ufae7\ufae8\ufae9\ufaea\ufaeb\ufaec\ufaed\ufaee\ufaef\ufaf0\ufaf1\ufaf2\ufaf3\ufaf4\ufaf5\ufaf6\ufaf7\ufaf8\ufaf9\ufafa\ufafb\ufafc\ufafd\ufafe\ufaff\ufb07\ufb08\ufb09\ufb0a\ufb0b\ufb0c\ufb0d\ufb0e\ufb0f\ufb10\ufb11\ufb12\ufb18\ufb19\ufb1a\ufb1b\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\ufbc2\ufbc3\ufbc4\ufbc5\ufbc6\ufbc7\ufbc8\ufbc9\ufbca\ufbcb\ufbcc\ufbcd\ufbce\ufbcf\ufbd0\ufbd1\ufbd2\ufd40\ufd41\ufd42\ufd43\ufd44\ufd45\ufd46\ufd47\ufd48\ufd49\ufd4a\ufd4b\ufd4c\ufd4d\ufd4e\ufd4f\ufd90\ufd91\ufdc8\ufdc9\ufdca\ufdcb\ufdcc\ufdcd\ufdce\ufdcf\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea\ufdeb\ufdec\ufded\ufdee\ufdef\ufdfe\ufdff\ufe1a\ufe1b\ufe1c\ufe1d\ufe1e\ufe1f\ufe24\ufe25\ufe26\ufe27\ufe28\ufe29\ufe2a\ufe2b\ufe2c\ufe2d\ufe2e\ufe2f\ufe53\ufe67\ufe6c\ufe6d\ufe6e\ufe6f\ufe75\ufefd\ufefe\uff00\uffbf\uffc0\uffc1\uffc8\uffc9\uffd0\uffd1\uffd8\uffd9\uffdd\uffde\uffdf\uffe7\uffef\ufff0\ufff1\ufff2\ufff3\ufff4\ufff5\ufff6\ufff7\ufff8\ufffe' - -Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33f\ue340\ue341\ue342\ue343\ue344\ue345\ue346\ue347\ue348\ue349\ue34a\ue34b\ue34c\ue34d\ue34e\ue34f\ue350\ue351\ue352\ue353\ue354\ue355\ue356\ue357\ue358\ue359\ue35a\ue35b\ue35c\ue35d\ue35e\ue35f\ue360\ue361\ue362\ue363\ue364\ue365\ue366\ue367\ue368\ue369\ue36a\ue36b\ue36c\ue36d\ue36e\ue36f\ue370\ue371\ue372\ue373\ue374\ue375\ue376\ue377\ue378\ue379\ue37a\ue37b\ue37c\ue37d\ue37e\ue37f\ue380\ue381\ue382\ue383\ue384\ue385\ue386\ue387\ue388\ue389\ue38a\ue38b\ue38c\ue38d\ue38e\ue38f\ue390\ue391\ue392\ue393\ue394\ue395\ue396\ue397\ue398\ue399\ue39a\ue39b\ue39c\ue39d\ue39e\ue39f\ue3a0\ue3a1\ue3a2\ue3a3\ue3a4\ue3a5\ue3a6\ue3a7\ue3a8\ue3a9\ue3aa\ue3ab\ue3ac\ue3ad\ue3ae\ue3af\ue3b0\ue3b1\ue3b2\ue3b3\ue3b4\ue3b5\ue3b6\ue3b7\ue3b8\ue3b9\ue3ba\ue3bb\ue3bc\ue3bd\ue3be\ue3bf\ue3c0\ue3c1\ue3c2\ue3c3\ue3c4\ue3c5\ue3c6\ue3c7\ue3c8\ue3c9\ue3ca\ue3cb\ue3cc\ue3cd\ue3ce\ue3cf\ue3d0\ue3d1\ue3d2\ue3d3\ue3d4\ue3d5\ue3d6\ue3d7\ue3d8\ue3d9\ue3da\ue3db\ue3dc\ue3dd\ue3de\ue3df\ue3e0\ue3e1\ue3e2\ue3e3\ue3e4\ue3e5\ue3e6\ue3e7\ue3e8\ue3e9\ue3ea\ue3eb\ue3ec\ue3ed\ue3ee\ue3ef\ue3f0\ue3f1\ue3f2\ue3f3\ue3f4\ue3f5\ue3f6\ue3f7\ue3f8\ue3f9\ue3fa\ue3fb\ue3fc\ue3fd\ue3fe\ue3ff\ue400\ue401\ue402\ue403\ue404\ue405\ue406\ue407\ue408\ue409\ue40a\ue40b\ue40c\ue40d\ue40e\ue40f\ue410\ue411\ue412\ue413\ue414\ue415\ue416\ue417\ue418\ue419\ue41a\ue41b\ue41c\ue41d\ue41e\ue41f\ue420\ue421\ue422\ue423\ue424\ue425\ue426\ue427\ue428\ue429\ue42a\ue42b\ue42c\ue42d\ue42e\ue42f\ue430\ue431\ue432\ue433\ue434\ue435\ue436\ue437\ue438\ue439\ue43a\ue43b\ue43c\ue43d\ue43e\ue43f\ue440\ue441\ue442\ue443\ue444\ue445\ue446\ue447\ue448\ue449\ue44a\ue44b\ue44c\ue44d\ue44e\ue44f\ue450\ue451\ue452\ue453\ue454\ue455\ue456\ue457\ue458\ue459\ue45a\ue45b\ue45c\ue45d\ue45e\ue45f\ue460\ue461\ue462\ue463\ue464\ue465\ue466\ue467\ue468\ue469\ue46a\ue46b\ue46c\ue46d\ue46e\ue46f\ue470\ue471\ue472\ue473\ue474\ue475\ue476\ue477\ue478\ue479\ue47a\ue47b\ue47c\ue47d\ue47e\ue47f\ue480\ue481\ue482\ue483\ue484\ue485\ue486\ue487\ue488\ue489\ue48a\ue48b\ue48c\ue48d\ue48e\ue48f\ue490\ue491\ue492\ue493\ue494\ue495\ue496\ue497\ue498\ue499\ue49a\ue49b\ue49c\ue49d\ue49e\ue49f\ue4a0\ue4a1\ue4a2\ue4a3\ue4a4\ue4a5\ue4a6\ue4a7\ue4a8\ue4a9\ue4aa\ue4ab\ue4ac\ue4ad\ue4ae\ue4af\ue4b0\ue4b1\ue4b2\ue4b3\ue4b4\ue4b5\ue4b6\ue4b7\ue4b8\ue4b9\ue4ba\ue4bb\ue4bc\ue4bd\ue4be\ue4bf\ue4c0\ue4c1\ue4c2\ue4c3\ue4c4\ue4c5\ue4c6\ue4c7\ue4c8\ue4c9\ue4ca\ue4cb\ue4cc\ue4cd\ue4ce\ue4cf\ue4d0\ue4d1\ue4d2\ue4d3\ue4d4\ue4d5\ue4d6\ue4d7\ue4d8\ue4d9\ue4da\ue4db\ue4dc\ue4dd\ue4de\ue4df\ue4e0\ue4e1\ue4e2\ue4e3\ue4e4\ue4e5\ue4e6\ue4e7\ue4e8\ue4e9\ue4ea\ue4eb\ue4ec\ue4ed\ue4ee\ue4ef\ue4f0\ue4f1\ue4f2\ue4f3\ue4f4\ue4f5\ue4f6\ue4f7\ue4f8\ue4f9\ue4fa\ue4fb\ue4fc\ue4fd\ue4fe\ue4ff\ue500\ue501\ue502\ue503\ue504\ue505\ue506\ue507\ue508\ue509\ue50a\ue50b\ue50c\ue50d\ue50e\ue50f\ue510\ue511\ue512\ue513\ue514\ue515\ue516\ue517\ue518\ue519\ue51a\ue51b\ue51c\ue51d\ue51e\ue51f\ue520\ue521\ue522\ue523\ue524\ue525\ue526\ue527\ue528\ue529\ue52a\ue52b\ue52c\ue52d\ue52e\ue52f\ue530\ue531\ue532\ue533\ue534\ue535\ue536\ue537\ue538\ue539\ue53a\ue53b\ue53c\ue53d\ue53e\ue53f\ue540\ue541\ue542\ue543\ue544\ue545\ue546\ue547\ue548\ue549\ue54a\ue54b\ue54c\ue54d\ue54e\ue54f\ue550\ue551\ue552\ue553\ue554\ue555\ue556\ue557\ue558\ue559\ue55a\ue55b\ue55c\ue55d\ue55e\ue55f\ue560\ue561\ue562\ue563\ue564\ue565\ue566\ue567\ue568\ue569\ue56a\ue56b\ue56c\ue56d\ue56e\ue56f\ue570\ue571\ue572\ue573\ue574\ue575\ue576\ue577\ue578\ue579\ue57a\ue57b\ue57c\ue57d\ue57e\ue57f\ue580\ue581\ue582\ue583\ue584\ue585\ue586\ue587\ue588\ue589\ue58a\ue58b\ue58c\ue58d\ue58e\ue58f\ue590\ue591\ue592\ue593\ue594\ue595\ue596\ue597\ue598\ue599\ue59a\ue59b\ue59c\ue59d\ue59e\ue59f\ue5a0\ue5a1\ue5a2\ue5a3\ue5a4\ue5a5\ue5a6\ue5a7\ue5a8\ue5a9\ue5aa\ue5ab\ue5ac\ue5ad\ue5ae\ue5af\ue5b0\ue5b1\ue5b2\ue5b3\ue5b4\ue5b5\ue5b6\ue5b7\ue5b8\ue5b9\ue5ba\ue5bb\ue5bc\ue5bd\ue5be\ue5bf\ue5c0\ue5c1\ue5c2\ue5c3\ue5c4\ue5c5\ue5c6\ue5c7\ue5c8\ue5c9\ue5ca\ue5cb\ue5cc\ue5cd\ue5ce\ue5cf\ue5d0\ue5d1\ue5d2\ue5d3\ue5d4\ue5d5\ue5d6\ue5d7\ue5d8\ue5d9\ue5da\ue5db\ue5dc\ue5dd\ue5de\ue5df\ue5e0\ue5e1\ue5e2\ue5e3\ue5e4\ue5e5\ue5e6\ue5e7\ue5e8\ue5e9\ue5ea\ue5eb\ue5ec\ue5ed\ue5ee\ue5ef\ue5f0\ue5f1\ue5f2\ue5f3\ue5f4\ue5f5\ue5f6\ue5f7\ue5f8\ue5f9\ue5fa\ue5fb\ue5fc\ue5fd\ue5fe\ue5ff\ue600\ue601\ue602\ue603\ue604\ue605\ue606\ue607\ue608\ue609\ue60a\ue60b\ue60c\ue60d\ue60e\ue60f\ue610\ue611\ue612\ue613\ue614\ue615\ue616\ue617\ue618\ue619\ue61a\ue61b\ue61c\ue61d\ue61e\ue61f\ue620\ue621\ue622\ue623\ue624\ue625\ue626\ue627\ue628\ue629\ue62a\ue62b\ue62c\ue62d\ue62e\ue62f\ue630\ue631\ue632\ue633\ue634\ue635\ue636\ue637\ue638\ue639\ue63a\ue63b\ue63c\ue63d\ue63e\ue63f\ue640\ue641\ue642\ue643\ue644\ue645\ue646\ue647\ue648\ue649\ue64a\ue64b\ue64c\ue64d\ue64e\ue64f\ue650\ue651\ue652\ue653\ue654\ue655\ue656\ue657\ue658\ue659\ue65a\ue65b\ue65c\ue65d\ue65e\ue65f\ue660\ue661\ue662\ue663\ue664\ue665\ue666\ue667\ue668\ue669\ue66a\ue66b\ue66c\ue66d\ue66e\ue66f\ue670\ue671\ue672\ue673\ue674\ue675\ue676\ue677\ue678\ue679\ue67a\ue67b\ue67c\ue67d\ue67e\ue67f\ue680\ue681\ue682\ue683\ue684\ue685\ue686\ue687\ue688\ue689\ue68a\ue68b\ue68c\ue68d\ue68e\ue68f\ue690\ue691\ue692\ue693\ue694\ue695\ue696\ue697\ue698\ue699\ue69a\ue69b\ue69c\ue69d\ue69e\ue69f\ue6a0\ue6a1\ue6a2\ue6a3\ue6a4\ue6a5\ue6a6\ue6a7\ue6a8\ue6a9\ue6aa\ue6ab\ue6ac\ue6ad\ue6ae\ue6af\ue6b0\ue6b1\ue6b2\ue6b3\ue6b4\ue6b5\ue6b6\ue6b7\ue6b8\ue6b9\ue6ba\ue6bb\ue6bc\ue6bd\ue6be\ue6bf\ue6c0\ue6c1\ue6c2\ue6c3\ue6c4\ue6c5\ue6c6\ue6c7\ue6c8\ue6c9\ue6ca\ue6cb\ue6cc\ue6cd\ue6ce\ue6cf\ue6d0\ue6d1\ue6d2\ue6d3\ue6d4\ue6d5\ue6d6\ue6d7\ue6d8\ue6d9\ue6da\ue6db\ue6dc\ue6dd\ue6de\ue6df\ue6e0\ue6e1\ue6e2\ue6e3\ue6e4\ue6e5\ue6e6\ue6e7\ue6e8\ue6e9\ue6ea\ue6eb\ue6ec\ue6ed\ue6ee\ue6ef\ue6f0\ue6f1\ue6f2\ue6f3\ue6f4\ue6f5\ue6f6\ue6f7\ue6f8\ue6f9\ue6fa\ue6fb\ue6fc\ue6fd\ue6fe\ue6ff\ue700\ue701\ue702\ue703\ue704\ue705\ue706\ue707\ue708\ue709\ue70a\ue70b\ue70c\ue70d\ue70e\ue70f\ue710\ue711\ue712\ue713\ue714\ue715\ue716\ue717\ue718\ue719\ue71a\ue71b\ue71c\ue71d\ue71e\ue71f\ue720\ue721\ue722\ue723\ue724\ue725\ue726\ue727\ue728\ue729\ue72a\ue72b\ue72c\ue72d\ue72e\ue72f\ue730\ue731\ue732\ue733\ue734\ue735\ue736\ue737\ue738\ue739\ue73a\ue73b\ue73c\ue73d\ue73e\ue73f\ue740\ue741\ue742\ue743\ue744\ue745\ue746\ue747\ue748\ue749\ue74a\ue74b\ue74c\ue74d\ue74e\ue74f\ue750\ue751\ue752\ue753\ue754\ue755\ue756\ue757\ue758\ue759\ue75a\ue75b\ue75c\ue75d\ue75e\ue75f\ue760\ue761\ue762\ue763\ue764\ue765\ue766\ue767\ue768\ue769\ue76a\ue76b\ue76c\ue76d\ue76e\ue76f\ue770\ue771\ue772\ue773\ue774\ue775\ue776\ue777\ue778\ue779\ue77a\ue77b\ue77c\ue77d\ue77e\ue77f\ue780\ue781\ue782\ue783\ue784\ue785\ue786\ue787\ue788\ue789\ue78a\ue78b\ue78c\ue78d\ue78e\ue78f\ue790\ue791\ue792\ue793\ue794\ue795\ue796\ue797\ue798\ue799\ue79a\ue79b\ue79c\ue79d\ue79e\ue79f\ue7a0\ue7a1\ue7a2\ue7a3\ue7a4\ue7a5\ue7a6\ue7a7\ue7a8\ue7a9\ue7aa\ue7ab\ue7ac\ue7ad\ue7ae\ue7af\ue7b0\ue7b1\ue7b2\ue7b3\ue7b4\ue7b5\ue7b6\ue7b7\ue7b8\ue7b9\ue7ba\ue7bb\ue7bc\ue7bd\ue7be\ue7bf\ue7c0\ue7c1\ue7c2\ue7c3\ue7c4\ue7c5\ue7c6\ue7c7\ue7c8\ue7c9\ue7ca\ue7cb\ue7cc\ue7cd\ue7ce\ue7cf\ue7d0\ue7d1\ue7d2\ue7d3\ue7d4\ue7d5\ue7d6\ue7d7\ue7d8\ue7d9\ue7da\ue7db\ue7dc\ue7dd\ue7de\ue7df\ue7e0\ue7e1\ue7e2\ue7e3\ue7e4\ue7e5\ue7e6\ue7e7\ue7e8\ue7e9\ue7ea\ue7eb\ue7ec\ue7ed\ue7ee\ue7ef\ue7f0\ue7f1\ue7f2\ue7f3\ue7f4\ue7f5\ue7f6\ue7f7\ue7f8\ue7f9\ue7fa\ue7fb\ue7fc\ue7fd\ue7fe\ue7ff\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\ue8ff\ue900\ue901\ue902\ue903\ue904\ue905\ue906\ue907\ue908\ue909\ue90a\ue90b\ue90c\ue90d\ue90e\ue90f\ue910\ue911\ue912\ue913\ue914\ue915\ue916\ue917\ue918\ue919\ue91a\ue91b\ue91c\ue91d\ue91e\ue91f\ue920\ue921\ue922\ue923\ue924\ue925\ue926\ue927\ue928\ue929\ue92a\ue92b\ue92c\ue92d\ue92e\ue92f\ue930\ue931\ue932\ue933\ue934\ue935\ue936\ue937\ue938\ue939\ue93a\ue93b\ue93c\ue93d\ue93e\ue93f\ue940\ue941\ue942\ue943\ue944\ue945\ue946\ue947\ue948\ue949\ue94a\ue94b\ue94c\ue94d\ue94e\ue94f\ue950\ue951\ue952\ue953\ue954\ue955\ue956\ue957\ue958\ue959\ue95a\ue95b\ue95c\ue95d\ue95e\ue95f\ue960\ue961\ue962\ue963\ue964\ue965\ue966\ue967\ue968\ue969\ue96a\ue96b\ue96c\ue96d\ue96e\ue96f\ue970\ue971\ue972\ue973\ue974\ue975\ue976\ue977\ue978\ue979\ue97a\ue97b\ue97c\ue97d\ue97e\ue97f\ue980\ue981\ue982\ue983\ue984\ue985\ue986\ue987\ue988\ue989\ue98a\ue98b\ue98c\ue98d\ue98e\ue98f\ue990\ue991\ue992\ue993\ue994\ue995\ue996\ue997\ue998\ue999\ue99a\ue99b\ue99c\ue99d\ue99e\ue99f\ue9a0\ue9a1\ue9a2\ue9a3\ue9a4\ue9a5\ue9a6\ue9a7\ue9a8\ue9a9\ue9aa\ue9ab\ue9ac\ue9ad\ue9ae\ue9af\ue9b0\ue9b1\ue9b2\ue9b3\ue9b4\ue9b5\ue9b6\ue9b7\ue9b8\ue9b9\ue9ba\ue9bb\ue9bc\ue9bd\ue9be\ue9bf\ue9c0\ue9c1\ue9c2\ue9c3\ue9c4\ue9c5\ue9c6\ue9c7\ue9c8\ue9c9\ue9ca\ue9cb\ue9cc\ue9cd\ue9ce\ue9cf\ue9d0\ue9d1\ue9d2\ue9d3\ue9d4\ue9d5\ue9d6\ue9d7\ue9d8\ue9d9\ue9da\ue9db\ue9dc\ue9dd\ue9de\ue9df\ue9e0\ue9e1\ue9e2\ue9e3\ue9e4\ue9e5\ue9e6\ue9e7\ue9e8\ue9e9\ue9ea\ue9eb\ue9ec\ue9ed\ue9ee\ue9ef\ue9f0\ue9f1\ue9f2\ue9f3\ue9f4\ue9f5\ue9f6\ue9f7\ue9f8\ue9f9\ue9fa\ue9fb\ue9fc\ue9fd\ue9fe\ue9ff\uea00\uea01\uea02\uea03\uea04\uea05\uea06\uea07\uea08\uea09\uea0a\uea0b\uea0c\uea0d\uea0e\uea0f\uea10\uea11\uea12\uea13\uea14\uea15\uea16\uea17\uea18\uea19\uea1a\uea1b\uea1c\uea1d\uea1e\uea1f\uea20\uea21\uea22\uea23\uea24\uea25\uea26\uea27\uea28\uea29\uea2a\uea2b\uea2c\uea2d\uea2e\uea2f\uea30\uea31\uea32\uea33\uea34\uea35\uea36\uea37\uea38\uea39\uea3a\uea3b\uea3c\uea3d\uea3e\uea3f\uea40\uea41\uea42\uea43\uea44\uea45\uea46\uea47\uea48\uea49\uea4a\uea4b\uea4c\uea4d\uea4e\uea4f\uea50\uea51\uea52\uea53\uea54\uea55\uea56\uea57\uea58\uea59\uea5a\uea5b\uea5c\uea5d\uea5e\uea5f\uea60\uea61\uea62\uea63\uea64\uea65\uea66\uea67\uea68\uea69\uea6a\uea6b\uea6c\uea6d\uea6e\uea6f\uea70\uea71\uea72\uea73\uea74\uea75\uea76\uea77\uea78\uea79\uea7a\uea7b\uea7c\uea7d\uea7e\uea7f\uea80\uea81\uea82\uea83\uea84\uea85\uea86\uea87\uea88\uea89\uea8a\uea8b\uea8c\uea8d\uea8e\uea8f\uea90\uea91\uea92\uea93\uea94\uea95\uea96\uea97\uea98\uea99\uea9a\uea9b\uea9c\uea9d\uea9e\uea9f\ueaa0\ueaa1\ueaa2\ueaa3\ueaa4\ueaa5\ueaa6\ueaa7\ueaa8\ueaa9\ueaaa\ueaab\ueaac\ueaad\ueaae\ueaaf\ueab0\ueab1\ueab2\ueab3\ueab4\ueab5\ueab6\ueab7\ueab8\ueab9\ueaba\ueabb\ueabc\ueabd\ueabe\ueabf\ueac0\ueac1\ueac2\ueac3\ueac4\ueac5\ueac6\ueac7\ueac8\ueac9\ueaca\ueacb\ueacc\ueacd\ueace\ueacf\uead0\uead1\uead2\uead3\uead4\uead5\uead6\uead7\uead8\uead9\ueada\ueadb\ueadc\ueadd\ueade\ueadf\ueae0\ueae1\ueae2\ueae3\ueae4\ueae5\ueae6\ueae7\ueae8\ueae9\ueaea\ueaeb\ueaec\ueaed\ueaee\ueaef\ueaf0\ueaf1\ueaf2\ueaf3\ueaf4\ueaf5\ueaf6\ueaf7\ueaf8\ueaf9\ueafa\ueafb\ueafc\ueafd\ueafe\ueaff\ueb00\ueb01\ueb02\ueb03\ueb04\ueb05\ueb06\ueb07\ueb08\ueb09\ueb0a\ueb0b\ueb0c\ueb0d\ueb0e\ueb0f\ueb10\ueb11\ueb12\ueb13\ueb14\ueb15\ueb16\ueb17\ueb18\ueb19\ueb1a\ueb1b\ueb1c\ueb1d\ueb1e\ueb1f\ueb20\ueb21\ueb22\ueb23\ueb24\ueb25\ueb26\ueb27\ueb28\ueb29\ueb2a\ueb2b\ueb2c\ueb2d\ueb2e\ueb2f\ueb30\ueb31\ueb32\ueb33\ueb34\ueb35\ueb36\ueb37\ueb38\ueb39\ueb3a\ueb3b\ueb3c\ueb3d\ueb3e\ueb3f\ueb40\ueb41\ueb42\ueb43\ueb44\ueb45\ueb46\ueb47\ueb48\ueb49\ueb4a\ueb4b\ueb4c\ueb4d\ueb4e\ueb4f\ueb50\ueb51\ueb52\ueb53\ueb54\ueb55\ueb56\ueb57\ueb58\ueb59\ueb5a\ueb5b\ueb5c\ueb5d\ueb5e\ueb5f\ueb60\ueb61\ueb62\ueb63\ueb64\ueb65\ueb66\ueb67\ueb68\ueb69\ueb6a\ueb6b\ueb6c\ueb6d\ueb6e\ueb6f\ueb70\ueb71\ueb72\ueb73\ueb74\ueb75\ueb76\ueb77\ueb78\ueb79\ueb7a\ueb7b\ueb7c\ueb7d\ueb7e\ueb7f\ueb80\ueb81\ueb82\ueb83\ueb84\ueb85\ueb86\ueb87\ueb88\ueb89\ueb8a\ueb8b\ueb8c\ueb8d\ueb8e\ueb8f\ueb90\ueb91\ueb92\ueb93\ueb94\ueb95\ueb96\ueb97\ueb98\ueb99\ueb9a\ueb9b\ueb9c\ueb9d\ueb9e\ueb9f\ueba0\ueba1\ueba2\ueba3\ueba4\ueba5\ueba6\ueba7\ueba8\ueba9\uebaa\uebab\uebac\uebad\uebae\uebaf\uebb0\uebb1\uebb2\uebb3\uebb4\uebb5\uebb6\uebb7\uebb8\uebb9\uebba\uebbb\uebbc\uebbd\uebbe\uebbf\uebc0\uebc1\uebc2\uebc3\uebc4\uebc5\uebc6\uebc7\uebc8\uebc9\uebca\uebcb\uebcc\uebcd\uebce\uebcf\uebd0\uebd1\uebd2\uebd3\uebd4\uebd5\uebd6\uebd7\uebd8\uebd9\uebda\uebdb\uebdc\uebdd\uebde\uebdf\uebe0\uebe1\uebe2\uebe3\uebe4\uebe5\uebe6\uebe7\uebe8\uebe9\uebea\uebeb\uebec\uebed\uebee\uebef\uebf0\uebf1\uebf2\uebf3\uebf4\uebf5\uebf6\uebf7\uebf8\uebf9\uebfa\uebfb\uebfc\uebfd\uebfe\uebff\uec00\uec01\uec02\uec03\uec04\uec05\uec06\uec07\uec08\uec09\uec0a\uec0b\uec0c\uec0d\uec0e\uec0f\uec10\uec11\uec12\uec13\uec14\uec15\uec16\uec17\uec18\uec19\uec1a\uec1b\uec1c\uec1d\uec1e\uec1f\uec20\uec21\uec22\uec23\uec24\uec25\uec26\uec27\uec28\uec29\uec2a\uec2b\uec2c\uec2d\uec2e\uec2f\uec30\uec31\uec32\uec33\uec34\uec35\uec36\uec37\uec38\uec39\uec3a\uec3b\uec3c\uec3d\uec3e\uec3f\uec40\uec41\uec42\uec43\uec44\uec45\uec46\uec47\uec48\uec49\uec4a\uec4b\uec4c\uec4d\uec4e\uec4f\uec50\uec51\uec52\uec53\uec54\uec55\uec56\uec57\uec58\uec59\uec5a\uec5b\uec5c\uec5d\uec5e\uec5f\uec60\uec61\uec62\uec63\uec64\uec65\uec66\uec67\uec68\uec69\uec6a\uec6b\uec6c\uec6d\uec6e\uec6f\uec70\uec71\uec72\uec73\uec74\uec75\uec76\uec77\uec78\uec79\uec7a\uec7b\uec7c\uec7d\uec7e\uec7f\uec80\uec81\uec82\uec83\uec84\uec85\uec86\uec87\uec88\uec89\uec8a\uec8b\uec8c\uec8d\uec8e\uec8f\uec90\uec91\uec92\uec93\uec94\uec95\uec96\uec97\uec98\uec99\uec9a\uec9b\uec9c\uec9d\uec9e\uec9f\ueca0\ueca1\ueca2\ueca3\ueca4\ueca5\ueca6\ueca7\ueca8\ueca9\uecaa\uecab\uecac\uecad\uecae\uecaf\uecb0\uecb1\uecb2\uecb3\uecb4\uecb5\uecb6\uecb7\uecb8\uecb9\uecba\uecbb\uecbc\uecbd\uecbe\uecbf\uecc0\uecc1\uecc2\uecc3\uecc4\uecc5\uecc6\uecc7\uecc8\uecc9\uecca\ueccb\ueccc\ueccd\uecce\ueccf\uecd0\uecd1\uecd2\uecd3\uecd4\uecd5\uecd6\uecd7\uecd8\uecd9\uecda\uecdb\uecdc\uecdd\uecde\uecdf\uece0\uece1\uece2\uece3\uece4\uece5\uece6\uece7\uece8\uece9\uecea\ueceb\uecec\ueced\uecee\uecef\uecf0\uecf1\uecf2\uecf3\uecf4\uecf5\uecf6\uecf7\uecf8\uecf9\uecfa\uecfb\uecfc\uecfd\uecfe\uecff\ued00\ued01\ued02\ued03\ued04\ued05\ued06\ued07\ued08\ued09\ued0a\ued0b\ued0c\ued0d\ued0e\ued0f\ued10\ued11\ued12\ued13\ued14\ued15\ued16\ued17\ued18\ued19\ued1a\ued1b\ued1c\ued1d\ued1e\ued1f\ued20\ued21\ued22\ued23\ued24\ued25\ued26\ued27\ued28\ued29\ued2a\ued2b\ued2c\ued2d\ued2e\ued2f\ued30\ued31\ued32\ued33\ued34\ued35\ued36\ued37\ued38\ued39\ued3a\ued3b\ued3c\ued3d\ued3e\ued3f\ued40\ued41\ued42\ued43\ued44\ued45\ued46\ued47\ued48\ued49\ued4a\ued4b\ued4c\ued4d\ued4e\ued4f\ued50\ued51\ued52\ued53\ued54\ued55\ued56\ued57\ued58\ued59\ued5a\ued5b\ued5c\ued5d\ued5e\ued5f\ued60\ued61\ued62\ued63\ued64\ued65\ued66\ued67\ued68\ued69\ued6a\ued6b\ued6c\ued6d\ued6e\ued6f\ued70\ued71\ued72\ued73\ued74\ued75\ued76\ued77\ued78\ued79\ued7a\ued7b\ued7c\ued7d\ued7e\ued7f\ued80\ued81\ued82\ued83\ued84\ued85\ued86\ued87\ued88\ued89\ued8a\ued8b\ued8c\ued8d\ued8e\ued8f\ued90\ued91\ued92\ued93\ued94\ued95\ued96\ued97\ued98\ued99\ued9a\ued9b\ued9c\ued9d\ued9e\ued9f\ueda0\ueda1\ueda2\ueda3\ueda4\ueda5\ueda6\ueda7\ueda8\ueda9\uedaa\uedab\uedac\uedad\uedae\uedaf\uedb0\uedb1\uedb2\uedb3\uedb4\uedb5\uedb6\uedb7\uedb8\uedb9\uedba\uedbb\uedbc\uedbd\uedbe\uedbf\uedc0\uedc1\uedc2\uedc3\uedc4\uedc5\uedc6\uedc7\uedc8\uedc9\uedca\uedcb\uedcc\uedcd\uedce\uedcf\uedd0\uedd1\uedd2\uedd3\uedd4\uedd5\uedd6\uedd7\uedd8\uedd9\uedda\ueddb\ueddc\ueddd\uedde\ueddf\uede0\uede1\uede2\uede3\uede4\uede5\uede6\uede7\uede8\uede9\uedea\uedeb\uedec\ueded\uedee\uedef\uedf0\uedf1\uedf2\uedf3\uedf4\uedf5\uedf6\uedf7\uedf8\uedf9\uedfa\uedfb\uedfc\uedfd\uedfe\uedff\uee00\uee01\uee02\uee03\uee04\uee05\uee06\uee07\uee08\uee09\uee0a\uee0b\uee0c\uee0d\uee0e\uee0f\uee10\uee11\uee12\uee13\uee14\uee15\uee16\uee17\uee18\uee19\uee1a\uee1b\uee1c\uee1d\uee1e\uee1f\uee20\uee21\uee22\uee23\uee24\uee25\uee26\uee27\uee28\uee29\uee2a\uee2b\uee2c\uee2d\uee2e\uee2f\uee30\uee31\uee32\uee33\uee34\uee35\uee36\uee37\uee38\uee39\uee3a\uee3b\uee3c\uee3d\uee3e\uee3f\uee40\uee41\uee42\uee43\uee44\uee45\uee46\uee47\uee48\uee49\uee4a\uee4b\uee4c\uee4d\uee4e\uee4f\uee50\uee51\uee52\uee53\uee54\uee55\uee56\uee57\uee58\uee59\uee5a\uee5b\uee5c\uee5d\uee5e\uee5f\uee60\uee61\uee62\uee63\uee64\uee65\uee66\uee67\uee68\uee69\uee6a\uee6b\uee6c\uee6d\uee6e\uee6f\uee70\uee71\uee72\uee73\uee74\uee75\uee76\uee77\uee78\uee79\uee7a\uee7b\uee7c\uee7d\uee7e\uee7f\uee80\uee81\uee82\uee83\uee84\uee85\uee86\uee87\uee88\uee89\uee8a\uee8b\uee8c\uee8d\uee8e\uee8f\uee90\uee91\uee92\uee93\uee94\uee95\uee96\uee97\uee98\uee99\uee9a\uee9b\uee9c\uee9d\uee9e\uee9f\ueea0\ueea1\ueea2\ueea3\ueea4\ueea5\ueea6\ueea7\ueea8\ueea9\ueeaa\ueeab\ueeac\ueead\ueeae\ueeaf\ueeb0\ueeb1\ueeb2\ueeb3\ueeb4\ueeb5\ueeb6\ueeb7\ueeb8\ueeb9\ueeba\ueebb\ueebc\ueebd\ueebe\ueebf\ueec0\ueec1\ueec2\ueec3\ueec4\ueec5\ueec6\ueec7\ueec8\ueec9\ueeca\ueecb\ueecc\ueecd\ueece\ueecf\ueed0\ueed1\ueed2\ueed3\ueed4\ueed5\ueed6\ueed7\ueed8\ueed9\ueeda\ueedb\ueedc\ueedd\ueede\ueedf\ueee0\ueee1\ueee2\ueee3\ueee4\ueee5\ueee6\ueee7\ueee8\ueee9\ueeea\ueeeb\ueeec\ueeed\ueeee\ueeef\ueef0\ueef1\ueef2\ueef3\ueef4\ueef5\ueef6\ueef7\ueef8\ueef9\ueefa\ueefb\ueefc\ueefd\ueefe\ueeff\uef00\uef01\uef02\uef03\uef04\uef05\uef06\uef07\uef08\uef09\uef0a\uef0b\uef0c\uef0d\uef0e\uef0f\uef10\uef11\uef12\uef13\uef14\uef15\uef16\uef17\uef18\uef19\uef1a\uef1b\uef1c\uef1d\uef1e\uef1f\uef20\uef21\uef22\uef23\uef24\uef25\uef26\uef27\uef28\uef29\uef2a\uef2b\uef2c\uef2d\uef2e\uef2f\uef30\uef31\uef32\uef33\uef34\uef35\uef36\uef37\uef38\uef39\uef3a\uef3b\uef3c\uef3d\uef3e\uef3f\uef40\uef41\uef42\uef43\uef44\uef45\uef46\uef47\uef48\uef49\uef4a\uef4b\uef4c\uef4d\uef4e\uef4f\uef50\uef51\uef52\uef53\uef54\uef55\uef56\uef57\uef58\uef59\uef5a\uef5b\uef5c\uef5d\uef5e\uef5f\uef60\uef61\uef62\uef63\uef64\uef65\uef66\uef67\uef68\uef69\uef6a\uef6b\uef6c\uef6d\uef6e\uef6f\uef70\uef71\uef72\uef73\uef74\uef75\uef76\uef77\uef78\uef79\uef7a\uef7b\uef7c\uef7d\uef7e\uef7f\uef80\uef81\uef82\uef83\uef84\uef85\uef86\uef87\uef88\uef89\uef8a\uef8b\uef8c\uef8d\uef8e\uef8f\uef90\uef91\uef92\uef93\uef94\uef95\uef96\uef97\uef98\uef99\uef9a\uef9b\uef9c\uef9d\uef9e\uef9f\uefa0\uefa1\uefa2\uefa3\uefa4\uefa5\uefa6\uefa7\uefa8\uefa9\uefaa\uefab\uefac\uefad\uefae\uefaf\uefb0\uefb1\uefb2\uefb3\uefb4\uefb5\uefb6\uefb7\uefb8\uefb9\uefba\uefbb\uefbc\uefbd\uefbe\uefbf\uefc0\uefc1\uefc2\uefc3\uefc4\uefc5\uefc6\uefc7\uefc8\uefc9\uefca\uefcb\uefcc\uefcd\uefce\uefcf\uefd0\uefd1\uefd2\uefd3\uefd4\uefd5\uefd6\uefd7\uefd8\uefd9\uefda\uefdb\uefdc\uefdd\uefde\uefdf\uefe0\uefe1\uefe2\uefe3\uefe4\uefe5\uefe6\uefe7\uefe8\uefe9\uefea\uefeb\uefec\uefed\uefee\uefef\ueff0\ueff1\ueff2\ueff3\ueff4\ueff5\ueff6\ueff7\ueff8\ueff9\ueffa\ueffb\ueffc\ueffd\ueffe\uefff\uf000\uf001\uf002\uf003\uf004\uf005\uf006\uf007\uf008\uf009\uf00a\uf00b\uf00c\uf00d\uf00e\uf00f\uf010\uf011\uf012\uf013\uf014\uf015\uf016\uf017\uf018\uf019\uf01a\uf01b\uf01c\uf01d\uf01e\uf01f\uf020\uf021\uf022\uf023\uf024\uf025\uf026\uf027\uf028\uf029\uf02a\uf02b\uf02c\uf02d\uf02e\uf02f\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf03a\uf03b\uf03c\uf03d\uf03e\uf03f\uf040\uf041\uf042\uf043\uf044\uf045\uf046\uf047\uf048\uf049\uf04a\uf04b\uf04c\uf04d\uf04e\uf04f\uf050\uf051\uf052\uf053\uf054\uf055\uf056\uf057\uf058\uf059\uf05a\uf05b\uf05c\uf05d\uf05e\uf05f\uf060\uf061\uf062\uf063\uf064\uf065\uf066\uf067\uf068\uf069\uf06a\uf06b\uf06c\uf06d\uf06e\uf06f\uf070\uf071\uf072\uf073\uf074\uf075\uf076\uf077\uf078\uf079\uf07a\uf07b\uf07c\uf07d\uf07e\uf07f\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf09e\uf09f\uf0a0\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a6\uf0a7\uf0a8\uf0a9\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0c7\uf0c8\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1\uf0d2\uf0d3\uf0d4\uf0d5\uf0d6\uf0d7\uf0d8\uf0d9\uf0da\uf0db\uf0dc\uf0dd\uf0de\uf0df\uf0e0\uf0e1\uf0e2\uf0e3\uf0e4\uf0e5\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0ec\uf0ed\uf0ee\uf0ef\uf0f0\uf0f1\uf0f2\uf0f3\uf0f4\uf0f5\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb\uf0fc\uf0fd\uf0fe\uf0ff\uf100\uf101\uf102\uf103\uf104\uf105\uf106\uf107\uf108\uf109\uf10a\uf10b\uf10c\uf10d\uf10e\uf10f\uf110\uf111\uf112\uf113\uf114\uf115\uf116\uf117\uf118\uf119\uf11a\uf11b\uf11c\uf11d\uf11e\uf11f\uf120\uf121\uf122\uf123\uf124\uf125\uf126\uf127\uf128\uf129\uf12a\uf12b\uf12c\uf12d\uf12e\uf12f\uf130\uf131\uf132\uf133\uf134\uf135\uf136\uf137\uf138\uf139\uf13a\uf13b\uf13c\uf13d\uf13e\uf13f\uf140\uf141\uf142\uf143\uf144\uf145\uf146\uf147\uf148\uf149\uf14a\uf14b\uf14c\uf14d\uf14e\uf14f\uf150\uf151\uf152\uf153\uf154\uf155\uf156\uf157\uf158\uf159\uf15a\uf15b\uf15c\uf15d\uf15e\uf15f\uf160\uf161\uf162\uf163\uf164\uf165\uf166\uf167\uf168\uf169\uf16a\uf16b\uf16c\uf16d\uf16e\uf16f\uf170\uf171\uf172\uf173\uf174\uf175\uf176\uf177\uf178\uf179\uf17a\uf17b\uf17c\uf17d\uf17e\uf17f\uf180\uf181\uf182\uf183\uf184\uf185\uf186\uf187\uf188\uf189\uf18a\uf18b\uf18c\uf18d\uf18e\uf18f\uf190\uf191\uf192\uf193\uf194\uf195\uf196\uf197\uf198\uf199\uf19a\uf19b\uf19c\uf19d\uf19e\uf19f\uf1a0\uf1a1\uf1a2\uf1a3\uf1a4\uf1a5\uf1a6\uf1a7\uf1a8\uf1a9\uf1aa\uf1ab\uf1ac\uf1ad\uf1ae\uf1af\uf1b0\uf1b1\uf1b2\uf1b3\uf1b4\uf1b5\uf1b6\uf1b7\uf1b8\uf1b9\uf1ba\uf1bb\uf1bc\uf1bd\uf1be\uf1bf\uf1c0\uf1c1\uf1c2\uf1c3\uf1c4\uf1c5\uf1c6\uf1c7\uf1c8\uf1c9\uf1ca\uf1cb\uf1cc\uf1cd\uf1ce\uf1cf\uf1d0\uf1d1\uf1d2\uf1d3\uf1d4\uf1d5\uf1d6\uf1d7\uf1d8\uf1d9\uf1da\uf1db\uf1dc\uf1dd\uf1de\uf1df\uf1e0\uf1e1\uf1e2\uf1e3\uf1e4\uf1e5\uf1e6\uf1e7\uf1e8\uf1e9\uf1ea\uf1eb\uf1ec\uf1ed\uf1ee\uf1ef\uf1f0\uf1f1\uf1f2\uf1f3\uf1f4\uf1f5\uf1f6\uf1f7\uf1f8\uf1f9\uf1fa\uf1fb\uf1fc\uf1fd\uf1fe\uf1ff\uf200\uf201\uf202\uf203\uf204\uf205\uf206\uf207\uf208\uf209\uf20a\uf20b\uf20c\uf20d\uf20e\uf20f\uf210\uf211\uf212\uf213\uf214\uf215\uf216\uf217\uf218\uf219\uf21a\uf21b\uf21c\uf21d\uf21e\uf21f\uf220\uf221\uf222\uf223\uf224\uf225\uf226\uf227\uf228\uf229\uf22a\uf22b\uf22c\uf22d\uf22e\uf22f\uf230\uf231\uf232\uf233\uf234\uf235\uf236\uf237\uf238\uf239\uf23a\uf23b\uf23c\uf23d\uf23e\uf23f\uf240\uf241\uf242\uf243\uf244\uf245\uf246\uf247\uf248\uf249\uf24a\uf24b\uf24c\uf24d\uf24e\uf24f\uf250\uf251\uf252\uf253\uf254\uf255\uf256\uf257\uf258\uf259\uf25a\uf25b\uf25c\uf25d\uf25e\uf25f\uf260\uf261\uf262\uf263\uf264\uf265\uf266\uf267\uf268\uf269\uf26a\uf26b\uf26c\uf26d\uf26e\uf26f\uf270\uf271\uf272\uf273\uf274\uf275\uf276\uf277\uf278\uf279\uf27a\uf27b\uf27c\uf27d\uf27e\uf27f\uf280\uf281\uf282\uf283\uf284\uf285\uf286\uf287\uf288\uf289\uf28a\uf28b\uf28c\uf28d\uf28e\uf28f\uf290\uf291\uf292\uf293\uf294\uf295\uf296\uf297\uf298\uf299\uf29a\uf29b\uf29c\uf29d\uf29e\uf29f\uf2a0\uf2a1\uf2a2\uf2a3\uf2a4\uf2a5\uf2a6\uf2a7\uf2a8\uf2a9\uf2aa\uf2ab\uf2ac\uf2ad\uf2ae\uf2af\uf2b0\uf2b1\uf2b2\uf2b3\uf2b4\uf2b5\uf2b6\uf2b7\uf2b8\uf2b9\uf2ba\uf2bb\uf2bc\uf2bd\uf2be\uf2bf\uf2c0\uf2c1\uf2c2\uf2c3\uf2c4\uf2c5\uf2c6\uf2c7\uf2c8\uf2c9\uf2ca\uf2cb\uf2cc\uf2cd\uf2ce\uf2cf\uf2d0\uf2d1\uf2d2\uf2d3\uf2d4\uf2d5\uf2d6\uf2d7\uf2d8\uf2d9\uf2da\uf2db\uf2dc\uf2dd\uf2de\uf2df\uf2e0\uf2e1\uf2e2\uf2e3\uf2e4\uf2e5\uf2e6\uf2e7\uf2e8\uf2e9\uf2ea\uf2eb\uf2ec\uf2ed\uf2ee\uf2ef\uf2f0\uf2f1\uf2f2\uf2f3\uf2f4\uf2f5\uf2f6\uf2f7\uf2f8\uf2f9\uf2fa\uf2fb\uf2fc\uf2fd\uf2fe\uf2ff\uf300\uf301\uf302\uf303\uf304\uf305\uf306\uf307\uf308\uf309\uf30a\uf30b\uf30c\uf30d\uf30e\uf30f\uf310\uf311\uf312\uf313\uf314\uf315\uf316\uf317\uf318\uf319\uf31a\uf31b\uf31c\uf31d\uf31e\uf31f\uf320\uf321\uf322\uf323\uf324\uf325\uf326\uf327\uf328\uf329\uf32a\uf32b\uf32c\uf32d\uf32e\uf32f\uf330\uf331\uf332\uf333\uf334\uf335\uf336\uf337\uf338\uf339\uf33a\uf33b\uf33c\uf33d\uf33e\uf33f\uf340\uf341\uf342\uf343\uf344\uf345\uf346\uf347\uf348\uf349\uf34a\uf34b\uf34c\uf34d\uf34e\uf34f\uf350\uf351\uf352\uf353\uf354\uf355\uf356\uf357\uf358\uf359\uf35a\uf35b\uf35c\uf35d\uf35e\uf35f\uf360\uf361\uf362\uf363\uf364\uf365\uf366\uf367\uf368\uf369\uf36a\uf36b\uf36c\uf36d\uf36e\uf36f\uf370\uf371\uf372\uf373\uf374\uf375\uf376\uf377\uf378\uf379\uf37a\uf37b\uf37c\uf37d\uf37e\uf37f\uf380\uf381\uf382\uf383\uf384\uf385\uf386\uf387\uf388\uf389\uf38a\uf38b\uf38c\uf38d\uf38e\uf38f\uf390\uf391\uf392\uf393\uf394\uf395\uf396\uf397\uf398\uf399\uf39a\uf39b\uf39c\uf39d\uf39e\uf39f\uf3a0\uf3a1\uf3a2\uf3a3\uf3a4\uf3a5\uf3a6\uf3a7\uf3a8\uf3a9\uf3aa\uf3ab\uf3ac\uf3ad\uf3ae\uf3af\uf3b0\uf3b1\uf3b2\uf3b3\uf3b4\uf3b5\uf3b6\uf3b7\uf3b8\uf3b9\uf3ba\uf3bb\uf3bc\uf3bd\uf3be\uf3bf\uf3c0\uf3c1\uf3c2\uf3c3\uf3c4\uf3c5\uf3c6\uf3c7\uf3c8\uf3c9\uf3ca\uf3cb\uf3cc\uf3cd\uf3ce\uf3cf\uf3d0\uf3d1\uf3d2\uf3d3\uf3d4\uf3d5\uf3d6\uf3d7\uf3d8\uf3d9\uf3da\uf3db\uf3dc\uf3dd\uf3de\uf3df\uf3e0\uf3e1\uf3e2\uf3e3\uf3e4\uf3e5\uf3e6\uf3e7\uf3e8\uf3e9\uf3ea\uf3eb\uf3ec\uf3ed\uf3ee\uf3ef\uf3f0\uf3f1\uf3f2\uf3f3\uf3f4\uf3f5\uf3f6\uf3f7\uf3f8\uf3f9\uf3fa\uf3fb\uf3fc\uf3fd\uf3fe\uf3ff\uf400\uf401\uf402\uf403\uf404\uf405\uf406\uf407\uf408\uf409\uf40a\uf40b\uf40c\uf40d\uf40e\uf40f\uf410\uf411\uf412\uf413\uf414\uf415\uf416\uf417\uf418\uf419\uf41a\uf41b\uf41c\uf41d\uf41e\uf41f\uf420\uf421\uf422\uf423\uf424\uf425\uf426\uf427\uf428\uf429\uf42a\uf42b\uf42c\uf42d\uf42e\uf42f\uf430\uf431\uf432\uf433\uf434\uf435\uf436\uf437\uf438\uf439\uf43a\uf43b\uf43c\uf43d\uf43e\uf43f\uf440\uf441\uf442\uf443\uf444\uf445\uf446\uf447\uf448\uf449\uf44a\uf44b\uf44c\uf44d\uf44e\uf44f\uf450\uf451\uf452\uf453\uf454\uf455\uf456\uf457\uf458\uf459\uf45a\uf45b\uf45c\uf45d\uf45e\uf45f\uf460\uf461\uf462\uf463\uf464\uf465\uf466\uf467\uf468\uf469\uf46a\uf46b\uf46c\uf46d\uf46e\uf46f\uf470\uf471\uf472\uf473\uf474\uf475\uf476\uf477\uf478\uf479\uf47a\uf47b\uf47c\uf47d\uf47e\uf47f\uf480\uf481\uf482\uf483\uf484\uf485\uf486\uf487\uf488\uf489\uf48a\uf48b\uf48c\uf48d\uf48e\uf48f\uf490\uf491\uf492\uf493\uf494\uf495\uf496\uf497\uf498\uf499\uf49a\uf49b\uf49c\uf49d\uf49e\uf49f\uf4a0\uf4a1\uf4a2\uf4a3\uf4a4\uf4a5\uf4a6\uf4a7\uf4a8\uf4a9\uf4aa\uf4ab\uf4ac\uf4ad\uf4ae\uf4af\uf4b0\uf4b1\uf4b2\uf4b3\uf4b4\uf4b5\uf4b6\uf4b7\uf4b8\uf4b9\uf4ba\uf4bb\uf4bc\uf4bd\uf4be\uf4bf\uf4c0\uf4c1\uf4c2\uf4c3\uf4c4\uf4c5\uf4c6\uf4c7\uf4c8\uf4c9\uf4ca\uf4cb\uf4cc\uf4cd\uf4ce\uf4cf\uf4d0\uf4d1\uf4d2\uf4d3\uf4d4\uf4d5\uf4d6\uf4d7\uf4d8\uf4d9\uf4da\uf4db\uf4dc\uf4dd\uf4de\uf4df\uf4e0\uf4e1\uf4e2\uf4e3\uf4e4\uf4e5\uf4e6\uf4e7\uf4e8\uf4e9\uf4ea\uf4eb\uf4ec\uf4ed\uf4ee\uf4ef\uf4f0\uf4f1\uf4f2\uf4f3\uf4f4\uf4f5\uf4f6\uf4f7\uf4f8\uf4f9\uf4fa\uf4fb\uf4fc\uf4fd\uf4fe\uf4ff\uf500\uf501\uf502\uf503\uf504\uf505\uf506\uf507\uf508\uf509\uf50a\uf50b\uf50c\uf50d\uf50e\uf50f\uf510\uf511\uf512\uf513\uf514\uf515\uf516\uf517\uf518\uf519\uf51a\uf51b\uf51c\uf51d\uf51e\uf51f\uf520\uf521\uf522\uf523\uf524\uf525\uf526\uf527\uf528\uf529\uf52a\uf52b\uf52c\uf52d\uf52e\uf52f\uf530\uf531\uf532\uf533\uf534\uf535\uf536\uf537\uf538\uf539\uf53a\uf53b\uf53c\uf53d\uf53e\uf53f\uf540\uf541\uf542\uf543\uf544\uf545\uf546\uf547\uf548\uf549\uf54a\uf54b\uf54c\uf54d\uf54e\uf54f\uf550\uf551\uf552\uf553\uf554\uf555\uf556\uf557\uf558\uf559\uf55a\uf55b\uf55c\uf55d\uf55e\uf55f\uf560\uf561\uf562\uf563\uf564\uf565\uf566\uf567\uf568\uf569\uf56a\uf56b\uf56c\uf56d\uf56e\uf56f\uf570\uf571\uf572\uf573\uf574\uf575\uf576\uf577\uf578\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf582\uf583\uf584\uf585\uf586\uf587\uf588\uf589\uf58a\uf58b\uf58c\uf58d\uf58e\uf58f\uf590\uf591\uf592\uf593\uf594\uf595\uf596\uf597\uf598\uf599\uf59a\uf59b\uf59c\uf59d\uf59e\uf59f\uf5a0\uf5a1\uf5a2\uf5a3\uf5a4\uf5a5\uf5a6\uf5a7\uf5a8\uf5a9\uf5aa\uf5ab\uf5ac\uf5ad\uf5ae\uf5af\uf5b0\uf5b1\uf5b2\uf5b3\uf5b4\uf5b5\uf5b6\uf5b7\uf5b8\uf5b9\uf5ba\uf5bb\uf5bc\uf5bd\uf5be\uf5bf\uf5c0\uf5c1\uf5c2\uf5c3\uf5c4\uf5c5\uf5c6\uf5c7\uf5c8\uf5c9\uf5ca\uf5cb\uf5cc\uf5cd\uf5ce\uf5cf\uf5d0\uf5d1\uf5d2\uf5d3\uf5d4\uf5d5\uf5d6\uf5d7\uf5d8\uf5d9\uf5da\uf5db\uf5dc\uf5dd\uf5de\uf5df\uf5e0\uf5e1\uf5e2\uf5e3\uf5e4\uf5e5\uf5e6\uf5e7\uf5e8\uf5e9\uf5ea\uf5eb\uf5ec\uf5ed\uf5ee\uf5ef\uf5f0\uf5f1\uf5f2\uf5f3\uf5f4\uf5f5\uf5f6\uf5f7\uf5f8\uf5f9\uf5fa\uf5fb\uf5fc\uf5fd\uf5fe\uf5ff\uf600\uf601\uf602\uf603\uf604\uf605\uf606\uf607\uf608\uf609\uf60a\uf60b\uf60c\uf60d\uf60e\uf60f\uf610\uf611\uf612\uf613\uf614\uf615\uf616\uf617\uf618\uf619\uf61a\uf61b\uf61c\uf61d\uf61e\uf61f\uf620\uf621\uf622\uf623\uf624\uf625\uf626\uf627\uf628\uf629\uf62a\uf62b\uf62c\uf62d\uf62e\uf62f\uf630\uf631\uf632\uf633\uf634\uf635\uf636\uf637\uf638\uf639\uf63a\uf63b\uf63c\uf63d\uf63e\uf63f\uf640\uf641\uf642\uf643\uf644\uf645\uf646\uf647\uf648\uf649\uf64a\uf64b\uf64c\uf64d\uf64e\uf64f\uf650\uf651\uf652\uf653\uf654\uf655\uf656\uf657\uf658\uf659\uf65a\uf65b\uf65c\uf65d\uf65e\uf65f\uf660\uf661\uf662\uf663\uf664\uf665\uf666\uf667\uf668\uf669\uf66a\uf66b\uf66c\uf66d\uf66e\uf66f\uf670\uf671\uf672\uf673\uf674\uf675\uf676\uf677\uf678\uf679\uf67a\uf67b\uf67c\uf67d\uf67e\uf67f\uf680\uf681\uf682\uf683\uf684\uf685\uf686\uf687\uf688\uf689\uf68a\uf68b\uf68c\uf68d\uf68e\uf68f\uf690\uf691\uf692\uf693\uf694\uf695\uf696\uf697\uf698\uf699\uf69a\uf69b\uf69c\uf69d\uf69e\uf69f\uf6a0\uf6a1\uf6a2\uf6a3\uf6a4\uf6a5\uf6a6\uf6a7\uf6a8\uf6a9\uf6aa\uf6ab\uf6ac\uf6ad\uf6ae\uf6af\uf6b0\uf6b1\uf6b2\uf6b3\uf6b4\uf6b5\uf6b6\uf6b7\uf6b8\uf6b9\uf6ba\uf6bb\uf6bc\uf6bd\uf6be\uf6bf\uf6c0\uf6c1\uf6c2\uf6c3\uf6c4\uf6c5\uf6c6\uf6c7\uf6c8\uf6c9\uf6ca\uf6cb\uf6cc\uf6cd\uf6ce\uf6cf\uf6d0\uf6d1\uf6d2\uf6d3\uf6d4\uf6d5\uf6d6\uf6d7\uf6d8\uf6d9\uf6da\uf6db\uf6dc\uf6dd\uf6de\uf6df\uf6e0\uf6e1\uf6e2\uf6e3\uf6e4\uf6e5\uf6e6\uf6e7\uf6e8\uf6e9\uf6ea\uf6eb\uf6ec\uf6ed\uf6ee\uf6ef\uf6f0\uf6f1\uf6f2\uf6f3\uf6f4\uf6f5\uf6f6\uf6f7\uf6f8\uf6f9\uf6fa\uf6fb\uf6fc\uf6fd\uf6fe\uf6ff\uf700\uf701\uf702\uf703\uf704\uf705\uf706\uf707\uf708\uf709\uf70a\uf70b\uf70c\uf70d\uf70e\uf70f\uf710\uf711\uf712\uf713\uf714\uf715\uf716\uf717\uf718\uf719\uf71a\uf71b\uf71c\uf71d\uf71e\uf71f\uf720\uf721\uf722\uf723\uf724\uf725\uf726\uf727\uf728\uf729\uf72a\uf72b\uf72c\uf72d\uf72e\uf72f\uf730\uf731\uf732\uf733\uf734\uf735\uf736\uf737\uf738\uf739\uf73a\uf73b\uf73c\uf73d\uf73e\uf73f\uf740\uf741\uf742\uf743\uf744\uf745\uf746\uf747\uf748\uf749\uf74a\uf74b\uf74c\uf74d\uf74e\uf74f\uf750\uf751\uf752\uf753\uf754\uf755\uf756\uf757\uf758\uf759\uf75a\uf75b\uf75c\uf75d\uf75e\uf75f\uf760\uf761\uf762\uf763\uf764\uf765\uf766\uf767\uf768\uf769\uf76a\uf76b\uf76c\uf76d\uf76e\uf76f\uf770\uf771\uf772\uf773\uf774\uf775\uf776\uf777\uf778\uf779\uf77a\uf77b\uf77c\uf77d\uf77e\uf77f\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff\uf800\uf801\uf802\uf803\uf804\uf805\uf806\uf807\uf808\uf809\uf80a\uf80b\uf80c\uf80d\uf80e\uf80f\uf810\uf811\uf812\uf813\uf814\uf815\uf816\uf817\uf818\uf819\uf81a\uf81b\uf81c\uf81d\uf81e\uf81f\uf820\uf821\uf822\uf823\uf824\uf825\uf826\uf827\uf828\uf829\uf82a\uf82b\uf82c\uf82d\uf82e\uf82f\uf830\uf831\uf832\uf833\uf834\uf835\uf836\uf837\uf838\uf839\uf83a\uf83b\uf83c\uf83d\uf83e\uf83f\uf840\uf841\uf842\uf843\uf844\uf845\uf846\uf847\uf848\uf849\uf84a\uf84b\uf84c\uf84d\uf84e\uf84f\uf850\uf851\uf852\uf853\uf854\uf855\uf856\uf857\uf858\uf859\uf85a\uf85b\uf85c\uf85d\uf85e\uf85f\uf860\uf861\uf862\uf863\uf864\uf865\uf866\uf867\uf868\uf869\uf86a\uf86b\uf86c\uf86d\uf86e\uf86f\uf870\uf871\uf872\uf873\uf874\uf875\uf876\uf877\uf878\uf879\uf87a\uf87b\uf87c\uf87d\uf87e\uf87f\uf880\uf881\uf882\uf883\uf884\uf885\uf886\uf887\uf888\uf889\uf88a\uf88b\uf88c\uf88d\uf88e\uf88f\uf890\uf891\uf892\uf893\uf894\uf895\uf896\uf897\uf898\uf899\uf89a\uf89b\uf89c\uf89d\uf89e\uf89f\uf8a0\uf8a1\uf8a2\uf8a3\uf8a4\uf8a5\uf8a6\uf8a7\uf8a8\uf8a9\uf8aa\uf8ab\uf8ac\uf8ad\uf8ae\uf8af\uf8b0\uf8b1\uf8b2\uf8b3\uf8b4\uf8b5\uf8b6\uf8b7\uf8b8\uf8b9\uf8ba\uf8bb\uf8bc\uf8bd\uf8be\uf8bf\uf8c0\uf8c1\uf8c2\uf8c3\uf8c4\uf8c5\uf8c6\uf8c7\uf8c8\uf8c9\uf8ca\uf8cb\uf8cc\uf8cd\uf8ce\uf8cf\uf8d0\uf8d1\uf8d2\uf8d3\uf8d4\uf8d5\uf8d6\uf8d7\uf8d8\uf8d9\uf8da\uf8db\uf8dc\uf8dd\uf8de\uf8df\uf8e0\uf8e1\uf8e2\uf8e3\uf8e4\uf8e5\uf8e6\uf8e7\uf8e8\uf8e9\uf8ea\uf8eb\uf8ec\uf8ed\uf8ee\uf8ef\uf8f0\uf8f1\uf8f2\uf8f3\uf8f4\uf8f5\uf8f6\uf8f7\uf8f8\uf8f9\uf8fa\uf8fb\uf8fc\uf8fd\uf8fe\uf8ff' - -try: - Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\udb3e\udb3f\udb40\udb41\udb42\udb43\udb44\udb45\udb46\udb47\udb48\udb49\udb4a\udb4b\udb4c\udb4d\udb4e\udb4f\udb50\udb51\udb52\udb53\udb54\udb55\udb56\udb57\udb58\udb59\udb5a\udb5b\udb5c\udb5d\udb5e\udb5f\udb60\udb61\udb62\udb63\udb64\udb65\udb66\udb67\udb68\udb69\udb6a\udb6b\udb6c\udb6d\udb6e\udb6f\udb70\udb71\udb72\udb73\udb74\udb75\udb76\udb77\udb78\udb79\udb7a\udb7b\udb7c\udb7d\udb7e\udb7f\udb80\udb81\udb82\udb83\udb84\udb85\udb86\udb87\udb88\udb89\udb8a\udb8b\udb8c\udb8d\udb8e\udb8f\udb90\udb91\udb92\udb93\udb94\udb95\udb96\udb97\udb98\udb99\udb9a\udb9b\udb9c\udb9d\udb9e\udb9f\udba0\udba1\udba2\udba3\udba4\udba5\udba6\udba7\udba8\udba9\udbaa\udbab\udbac\udbad\udbae\udbaf\udbb0\udbb1\udbb2\udbb3\udbb4\udbb5\udbb6\udbb7\udbb8\udbb9\udbba\udbbb\udbbc\udbbd\udbbe\udbbf\udbc0\udbc1\udbc2\udbc3\udbc4\udbc5\udbc6\udbc7\udbc8\udbc9\udbca\udbcb\udbcc\udbcd\udbce\udbcf\udbd0\udbd1\udbd2\udbd3\udbd4\udbd5\udbd6\udbd7\udbd8\udbd9\udbda\udbdb\udbdc\udbdd\udbde\udbdf\udbe0\udbe1\udbe2\udbe3\udbe4\udbe5\udbe6\udbe7\udbe8\udbe9\udbea\udbeb\udbec\udbed\udbee\udbef\udbf0\udbf1\udbf2\udbf3\udbf4\udbf5\udbf6\udbf7\udbf8\udbf9\udbfa\udbfb\udbfc\udbfd\udbfe\U0010fc00\udc01\udc02\udc03\udc04\udc05\udc06\udc07\udc08\udc09\udc0a\udc0b\udc0c\udc0d\udc0e\udc0f\udc10\udc11\udc12\udc13\udc14\udc15\udc16\udc17\udc18\udc19\udc1a\udc1b\udc1c\udc1d\udc1e\udc1f\udc20\udc21\udc22\udc23\udc24\udc25\udc26\udc27\udc28\udc29\udc2a\udc2b\udc2c\udc2d\udc2e\udc2f\udc30\udc31\udc32\udc33\udc34\udc35\udc36\udc37\udc38\udc39\udc3a\udc3b\udc3c\udc3d\udc3e\udc3f\udc40\udc41\udc42\udc43\udc44\udc45\udc46\udc47\udc48\udc49\udc4a\udc4b\udc4c\udc4d\udc4e\udc4f\udc50\udc51\udc52\udc53\udc54\udc55\udc56\udc57\udc58\udc59\udc5a\udc5b\udc5c\udc5d\udc5e\udc5f\udc60\udc61\udc62\udc63\udc64\udc65\udc66\udc67\udc68\udc69\udc6a\udc6b\udc6c\udc6d\udc6e\udc6f\udc70\udc71\udc72\udc73\udc74\udc75\udc76\udc77\udc78\udc79\udc7a\udc7b\udc7c\udc7d\udc7e\udc7f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff\udd00\udd01\udd02\udd03\udd04\udd05\udd06\udd07\udd08\udd09\udd0a\udd0b\udd0c\udd0d\udd0e\udd0f\udd10\udd11\udd12\udd13\udd14\udd15\udd16\udd17\udd18\udd19\udd1a\udd1b\udd1c\udd1d\udd1e\udd1f\udd20\udd21\udd22\udd23\udd24\udd25\udd26\udd27\udd28\udd29\udd2a\udd2b\udd2c\udd2d\udd2e\udd2f\udd30\udd31\udd32\udd33\udd34\udd35\udd36\udd37\udd38\udd39\udd3a\udd3b\udd3c\udd3d\udd3e\udd3f\udd40\udd41\udd42\udd43\udd44\udd45\udd46\udd47\udd48\udd49\udd4a\udd4b\udd4c\udd4d\udd4e\udd4f\udd50\udd51\udd52\udd53\udd54\udd55\udd56\udd57\udd58\udd59\udd5a\udd5b\udd5c\udd5d\udd5e\udd5f\udd60\udd61\udd62\udd63\udd64\udd65\udd66\udd67\udd68\udd69\udd6a\udd6b\udd6c\udd6d\udd6e\udd6f\udd70\udd71\udd72\udd73\udd74\udd75\udd76\udd77\udd78\udd79\udd7a\udd7b\udd7c\udd7d\udd7e\udd7f\udd80\udd81\udd82\udd83\udd84\udd85\udd86\udd87\udd88\udd89\udd8a\udd8b\udd8c\udd8d\udd8e\udd8f\udd90\udd91\udd92\udd93\udd94\udd95\udd96\udd97\udd98\udd99\udd9a\udd9b\udd9c\udd9d\udd9e\udd9f\udda0\udda1\udda2\udda3\udda4\udda5\udda6\udda7\udda8\udda9\uddaa\uddab\uddac\uddad\uddae\uddaf\uddb0\uddb1\uddb2\uddb3\uddb4\uddb5\uddb6\uddb7\uddb8\uddb9\uddba\uddbb\uddbc\uddbd\uddbe\uddbf\uddc0\uddc1\uddc2\uddc3\uddc4\uddc5\uddc6\uddc7\uddc8\uddc9\uddca\uddcb\uddcc\uddcd\uddce\uddcf\uddd0\uddd1\uddd2\uddd3\uddd4\uddd5\uddd6\uddd7\uddd8\uddd9\uddda\udddb\udddc\udddd\uddde\udddf\udde0\udde1\udde2\udde3\udde4\udde5\udde6\udde7\udde8\udde9\uddea\uddeb\uddec\udded\uddee\uddef\uddf0\uddf1\uddf2\uddf3\uddf4\uddf5\uddf6\uddf7\uddf8\uddf9\uddfa\uddfb\uddfc\uddfd\uddfe\uddff\ude00\ude01\ude02\ude03\ude04\ude05\ude06\ude07\ude08\ude09\ude0a\ude0b\ude0c\ude0d\ude0e\ude0f\ude10\ude11\ude12\ude13\ude14\ude15\ude16\ude17\ude18\ude19\ude1a\ude1b\ude1c\ude1d\ude1e\ude1f\ude20\ude21\ude22\ude23\ude24\ude25\ude26\ude27\ude28\ude29\ude2a\ude2b\ude2c\ude2d\ude2e\ude2f\ude30\ude31\ude32\ude33\ude34\ude35\ude36\ude37\ude38\ude39\ude3a\ude3b\ude3c\ude3d\ude3e\ude3f\ude40\ude41\ude42\ude43\ude44\ude45\ude46\ude47\ude48\ude49\ude4a\ude4b\ude4c\ude4d\ude4e\ude4f\ude50\ude51\ude52\ude53\ude54\ude55\ude56\ude57\ude58\ude59\ude5a\ude5b\ude5c\ude5d\ude5e\ude5f\ude60\ude61\ude62\ude63\ude64\ude65\ude66\ude67\ude68\ude69\ude6a\ude6b\ude6c\ude6d\ude6e\ude6f\ude70\ude71\ude72\ude73\ude74\ude75\ude76\ude77\ude78\ude79\ude7a\ude7b\ude7c\ude7d\ude7e\ude7f\ude80\ude81\ude82\ude83\ude84\ude85\ude86\ude87\ude88\ude89\ude8a\ude8b\ude8c\ude8d\ude8e\ude8f\ude90\ude91\ude92\ude93\ude94\ude95\ude96\ude97\ude98\ude99\ude9a\ude9b\ude9c\ude9d\ude9e\ude9f\udea0\udea1\udea2\udea3\udea4\udea5\udea6\udea7\udea8\udea9\udeaa\udeab\udeac\udead\udeae\udeaf\udeb0\udeb1\udeb2\udeb3\udeb4\udeb5\udeb6\udeb7\udeb8\udeb9\udeba\udebb\udebc\udebd\udebe\udebf\udec0\udec1\udec2\udec3\udec4\udec5\udec6\udec7\udec8\udec9\udeca\udecb\udecc\udecd\udece\udecf\uded0\uded1\uded2\uded3\uded4\uded5\uded6\uded7\uded8\uded9\udeda\udedb\udedc\udedd\udede\udedf\udee0\udee1\udee2\udee3\udee4\udee5\udee6\udee7\udee8\udee9\udeea\udeeb\udeec\udeed\udeee\udeef\udef0\udef1\udef2\udef3\udef4\udef5\udef6\udef7\udef8\udef9\udefa\udefb\udefc\udefd\udefe\udeff\udf00\udf01\udf02\udf03\udf04\udf05\udf06\udf07\udf08\udf09\udf0a\udf0b\udf0c\udf0d\udf0e\udf0f\udf10\udf11\udf12\udf13\udf14\udf15\udf16\udf17\udf18\udf19\udf1a\udf1b\udf1c\udf1d\udf1e\udf1f\udf20\udf21\udf22\udf23\udf24\udf25\udf26\udf27\udf28\udf29\udf2a\udf2b\udf2c\udf2d\udf2e\udf2f\udf30\udf31\udf32\udf33\udf34\udf35\udf36\udf37\udf38\udf39\udf3a\udf3b\udf3c\udf3d\udf3e\udf3f\udf40\udf41\udf42\udf43\udf44\udf45\udf46\udf47\udf48\udf49\udf4a\udf4b\udf4c\udf4d\udf4e\udf4f\udf50\udf51\udf52\udf53\udf54\udf55\udf56\udf57\udf58\udf59\udf5a\udf5b\udf5c\udf5d\udf5e\udf5f\udf60\udf61\udf62\udf63\udf64\udf65\udf66\udf67\udf68\udf69\udf6a\udf6b\udf6c\udf6d\udf6e\udf6f\udf70\udf71\udf72\udf73\udf74\udf75\udf76\udf77\udf78\udf79\udf7a\udf7b\udf7c\udf7d\udf7e\udf7f\udf80\udf81\udf82\udf83\udf84\udf85\udf86\udf87\udf88\udf89\udf8a\udf8b\udf8c\udf8d\udf8e\udf8f\udf90\udf91\udf92\udf93\udf94\udf95\udf96\udf97\udf98\udf99\udf9a\udf9b\udf9c\udf9d\udf9e\udf9f\udfa0\udfa1\udfa2\udfa3\udfa4\udfa5\udfa6\udfa7\udfa8\udfa9\udfaa\udfab\udfac\udfad\udfae\udfaf\udfb0\udfb1\udfb2\udfb3\udfb4\udfb5\udfb6\udfb7\udfb8\udfb9\udfba\udfbb\udfbc\udfbd\udfbe\udfbf\udfc0\udfc1\udfc2\udfc3\udfc4\udfc5\udfc6\udfc7\udfc8\udfc9\udfca\udfcb\udfcc\udfcd\udfce\udfcf\udfd0\udfd1\udfd2\udfd3\udfd4\udfd5\udfd6\udfd7\udfd8\udfd9\udfda\udfdb\udfdc\udfdd\udfde\udfdf\udfe0\udfe1\udfe2\udfe3\udfe4\udfe5\udfe6\udfe7\udfe8\udfe9\udfea\udfeb\udfec\udfed\udfee\udfef\udff0\udff1\udff2\udff3\udff4\udff5\udff6\udff7\udff8\udff9\udffa\udffb\udffc\udffd\udffe\udfff'") -except UnicodeDecodeError: - Cs = '' # Jython can't handle isolated surrogates - -Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213c\u213d\u2146\u2147\u2148\u2149\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3\u2ce4\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a' - -Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f' - -Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e32\u0e33\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94\u0e95\u0e96\u0e97\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead\u0eae\u0eaf\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0edc\u0edd\u0f00\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f88\u0f89\u0f8a\u0f8b\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1023\u1024\u1025\u1026\u1027\u1029\u102a\u1050\u1051\u1052\u1053\u1054\u1055\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6\u10f7\u10f8\u10f9\u10fa\u1100\u1101\u1102\u1103\u1104\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110d\u110e\u110f\u1110\u1111\u1112\u1113\u1114\u1115\u1116\u1117\u1118\u1119\u111a\u111b\u111c\u111d\u111e\u111f\u1120\u1121\u1122\u1123\u1124\u1125\u1126\u1127\u1128\u1129\u112a\u112b\u112c\u112d\u112e\u112f\u1130\u1131\u1132\u1133\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u1148\u1149\u114a\u114b\u114c\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u1158\u1159\u115f\u1160\u1161\u1162\u1163\u1164\u1165\u1166\u1167\u1168\u1169\u116a\u116b\u116c\u116d\u116e\u116f\u1170\u1171\u1172\u1173\u1174\u1175\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u1185\u1186\u1187\u1188\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u119f\u11a0\u11a1\u11a2\u11a8\u11a9\u11aa\u11ab\u11ac\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u11c8\u11c9\u11ca\u11cb\u11cc\u11cd\u11ce\u11cf\u11d0\u11d1\u11d2\u11d3\u11d4\u11d5\u11d6\u11d7\u11d8\u11d9\u11da\u11db\u11dc\u11dd\u11de\u11df\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u11f2\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u1358\u1359\u135a\u1380\u1381\u1382\u1383\u1384\u1385\u1386\u1387\u1388\u1389\u138a\u138b\u138c\u138d\u138e\u138f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u13a6\u13a7\u13a8\u13a9\u13aa\u13ab\u13ac\u13ad\u13ae\u13af\u13b0\u13b1\u13b2\u13b3\u13b4\u13b5\u13b6\u13b7\u13b8\u13b9\u13ba\u13bb\u13bc\u13bd\u13be\u13bf\u13c0\u13c1\u13c2\u13c3\u13c4\u13c5\u13c6\u13c7\u13c8\u13c9\u13ca\u13cb\u13cc\u13cd\u13ce\u13cf\u13d0\u13d1\u13d2\u13d3\u13d4\u13d5\u13d6\u13d7\u13d8\u13d9\u13da\u13db\u13dc\u13dd\u13de\u13df\u13e0\u13e1\u13e2\u13e3\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u13ee\u13ef\u13f0\u13f1\u13f2\u13f3\u13f4\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157c\u157d\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u166f\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17dc\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182a\u182b\u182c\u182d\u182e\u182f\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183a\u183b\u183c\u183d\u183e\u183f\u1840\u1841\u1842\u1844\u1845\u1846\u1847\u1848\u1849\u184a\u184b\u184c\u184d\u184e\u184f\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185a\u185b\u185c\u185d\u185e\u185f\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186a\u186b\u186c\u186d\u186e\u186f\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188a\u188b\u188c\u188d\u188e\u188f\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189a\u189b\u189c\u189d\u189e\u189f\u18a0\u18a1\u18a2\u18a3\u18a4\u18a5\u18a6\u18a7\u18a8\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190a\u190b\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u2135\u2136\u2137\u2138\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u3006\u303c\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u309f\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30ff\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312a\u312b\u312c\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313b\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u3463\u3464\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u346e\u346f\u3470\u3471\u3472\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u3499\u349a\u349b\u349c\u349d\u349e\u349f\u34a0\u34a1\u34a2\u34a3\u34a4\u34a5\u34a6\u34a7\u34a8\u34a9\u34aa\u34ab\u34ac\u34ad\u34ae\u34af\u34b0\u34b1\u34b2\u34b3\u34b4\u34b5\u34b6\u34b7\u34b8\u34b9\u34ba\u34bb\u34bc\u34bd\u34be\u34bf\u34c0\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cb\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d3\u34d4\u34d5\u34d6\u34d7\u34d8\u34d9\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u34e7\u34e8\u34e9\u34ea\u34eb\u34ec\u34ed\u34ee\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u350c\u350d\u350e\u350f\u3510\u3511\u3512\u3513\u3514\u3515\u3516\u3517\u3518\u3519\u351a\u351b\u351c\u351d\u351e\u351f\u3520\u3521\u3522\u3523\u3524\u3525\u3526\u3527\u3528\u3529\u352a\u352b\u352c\u352d\u352e\u352f\u3530\u3531\u3532\u3533\u3534\u3535\u3536\u3537\u3538\u3539\u353a\u353b\u353c\u353d\u353e\u353f\u3540\u3541\u3542\u3543\u3544\u3545\u3546\u3547\u3548\u3549\u354a\u354b\u354c\u354d\u354e\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558\u3559\u355a\u355b\u355c\u355d\u355e\u355f\u3560\u3561\u3562\u3563\u3564\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3605\u3606\u3607\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u3677\u3678\u3679\u367a\u367b\u367c\u367d\u367e\u367f\u3680\u3681\u3682\u3683\u3684\u3685\u3686\u3687\u3688\u3689\u368a\u368b\u368c\u368d\u368e\u368f\u3690\u3691\u3692\u3693\u3694\u3695\u3696\u3697\u3698\u3699\u369a\u369b\u369c\u369d\u369e\u369f\u36a0\u36a1\u36a2\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e0\u36e1\u36e2\u36e3\u36e4\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u3723\u3724\u3725\u3726\u3727\u3728\u3729\u372a\u372b\u372c\u372d\u372e\u372f\u3730\u3731\u3732\u3733\u3734\u3735\u3736\u3737\u3738\u3739\u373a\u373b\u373c\u373d\u373e\u373f\u3740\u3741\u3742\u3743\u3744\u3745\u3746\u3747\u3748\u3749\u374a\u374b\u374c\u374d\u374e\u374f\u3750\u3751\u3752\u3753\u3754\u3755\u3756\u3757\u3758\u3759\u375a\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u3762\u3763\u3764\u3765\u3766\u3767\u3768\u3769\u376a\u376b\u376c\u376d\u376e\u376f\u3770\u3771\u3772\u3773\u3774\u3775\u3776\u3777\u3778\u3779\u377a\u377b\u377c\u377d\u377e\u377f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u3787\u3788\u3789\u378a\u378b\u378c\u378d\u378e\u378f\u3790\u3791\u3792\u3793\u3794\u3795\u3796\u3797\u3798\u3799\u379a\u379b\u379c\u379d\u379e\u379f\u37a0\u37a1\u37a2\u37a3\u37a4\u37a5\u37a6\u37a7\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b7\u37b8\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u37c4\u37c5\u37c6\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9a\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da3\u3da4\u3da5\u3da6\u3da7\u3da8\u3da9\u3daa\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e36\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f75\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3fef\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u408c\u408d\u408e\u408f\u4090\u4091\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u409e\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40df\u40e0\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u40f0\u40f1\u40f2\u40f3\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4100\u4101\u4102\u4103\u4104\u4105\u4106\u4107\u4108\u4109\u410a\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4122\u4123\u4124\u4125\u4126\u4127\u4128\u4129\u412a\u412b\u412c\u412d\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u417e\u417f\u4180\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u4188\u4189\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4191\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a3\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c2\u41c3\u41c4\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d3\u41d4\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4274\u4275\u4276\u4277\u4278\u4279\u427a\u427b\u427c\u427d\u427e\u427f\u4280\u4281\u4282\u4283\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4290\u4291\u4292\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u429a\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b4\u42b5\u42b6\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u4316\u4317\u4318\u4319\u431a\u431b\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u432e\u432f\u4330\u4331\u4332\u4333\u4334\u4335\u4336\u4337\u4338\u4339\u433a\u433b\u433c\u433d\u433e\u433f\u4340\u4341\u4342\u4343\u4344\u4345\u4346\u4347\u4348\u4349\u434a\u434b\u434c\u434d\u434e\u434f\u4350\u4351\u4352\u4353\u4354\u4355\u4356\u4357\u4358\u4359\u435a\u435b\u435c\u435d\u435e\u435f\u4360\u4361\u4362\u4363\u4364\u4365\u4366\u4367\u4368\u4369\u436a\u436b\u436c\u436d\u436e\u436f\u4370\u4371\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u437f\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4390\u4391\u4392\u4393\u4394\u4395\u4396\u4397\u4398\u4399\u439a\u439b\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43aa\u43ab\u43ac\u43ad\u43ae\u43af\u43b0\u43b1\u43b2\u43b3\u43b4\u43b5\u43b6\u43b7\u43b8\u43b9\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c0\u43c1\u43c2\u43c3\u43c4\u43c5\u43c6\u43c7\u43c8\u43c9\u43ca\u43cb\u43cc\u43cd\u43ce\u43cf\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u443e\u443f\u4440\u4441\u4442\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445b\u445c\u445d\u445e\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u45d6\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u460b\u460c\u460d\u460e\u460f\u4610\u4611\u4612\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u4651\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u466e\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u4721\u4722\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u472b\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u4754\u4755\u4756\u4757\u4758\u4759\u475a\u475b\u475c\u475d\u475e\u475f\u4760\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u4775\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u47b9\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u47c7\u47c8\u47c9\u47ca\u47cb\u47cc\u47cd\u47ce\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d6\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fc\u47fd\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u481e\u481f\u4820\u4821\u4822\u4823\u4824\u4825\u4826\u4827\u4828\u4829\u482a\u482b\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u4899\u489a\u489b\u489c\u489d\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u496f\u4970\u4971\u4972\u4973\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u497f\u4980\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a1b\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u4a30\u4a31\u4a32\u4a33\u4a34\u4a35\u4a36\u4a37\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4a3f\u4a40\u4a41\u4a42\u4a43\u4a44\u4a45\u4a46\u4a47\u4a48\u4a49\u4a4a\u4a4b\u4a4c\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u4a5f\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u4a85\u4a86\u4a87\u4a88\u4a89\u4a8a\u4a8b\u4a8c\u4a8d\u4a8e\u4a8f\u4a90\u4a91\u4a92\u4a93\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aab\u4aac\u4aad\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab4\u4ab5\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u4abf\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u4ae8\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af8\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4be0\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u4bf5\u4bf6\u4bf7\u4bf8\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d2a\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d41\u4d42\u4d43\u4d44\u4d45\u4d46\u4d47\u4d48\u4d49\u4d4a\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u4d51\u4d52\u4d53\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5b\u4d5c\u4d5d\u4d5e\u4d5f\u4d60\u4d61\u4d62\u4d63\u4d64\u4d65\u4d66\u4d67\u4d68\u4d69\u4d6a\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d75\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7b\u4d7c\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d86\u4d87\u4d88\u4d89\u4d8a\u4d8b\u4d8c\u4d8d\u4d8e\u4d8f\u4d90\u4d91\u4d92\u4d93\u4d94\u4d95\u4d96\u4d97\u4d98\u4d99\u4d9a\u4d9b\u4d9c\u4d9d\u4d9e\u4d9f\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5\u4da6\u4da7\u4da8\u4da9\u4daa\u4dab\u4dac\u4dad\u4dae\u4daf\u4db0\u4db1\u4db2\u4db3\u4db4\u4db5\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e21\u4e22\u4e23\u4e24\u4e25\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e39\u4e3a\u4e3b\u4e3c\u4e3d\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u4e45\u4e46\u4e47\u4e48\u4e49\u4e4a\u4e4b\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e62\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u4e79\u4e7a\u4e7b\u4e7c\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e83\u4e84\u4e85\u4e86\u4e87\u4e88\u4e89\u4e8a\u4e8b\u4e8c\u4e8d\u4e8e\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u4ea8\u4ea9\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4eb8\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512b\u512c\u512d\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513a\u513b\u513c\u513d\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u5159\u515a\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u5166\u5167\u5168\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u5177\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5180\u5181\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u5195\u5196\u5197\u5198\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u51a9\u51aa\u51ab\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d6\u51d7\u51d8\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u5268\u5269\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5272\u5273\u5274\u5275\u5276\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u5280\u5281\u5282\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u5290\u5291\u5292\u5293\u5294\u5295\u5296\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d6\u52d7\u52d8\u52d9\u52da\u52db\u52dc\u52dd\u52de\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u5311\u5312\u5313\u5314\u5315\u5316\u5317\u5318\u5319\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u5326\u5327\u5328\u5329\u532a\u532b\u532c\u532d\u532e\u532f\u5330\u5331\u5332\u5333\u5334\u5335\u5336\u5337\u5338\u5339\u533a\u533b\u533c\u533d\u533e\u533f\u5340\u5341\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u534b\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u5354\u5355\u5356\u5357\u5358\u5359\u535a\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u536b\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u5379\u537a\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u538e\u538f\u5390\u5391\u5392\u5393\u5394\u5395\u5396\u5397\u5398\u5399\u539a\u539b\u539c\u539d\u539e\u539f\u53a0\u53a1\u53a2\u53a3\u53a4\u53a5\u53a6\u53a7\u53a8\u53a9\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5651\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u56ba\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56cc\u56cd\u56ce\u56cf\u56d0\u56d1\u56d2\u56d3\u56d4\u56d5\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u56ff\u5700\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u5725\u5726\u5727\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5900\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a90\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b34\u5b35\u5b36\u5b37\u5b38\u5b39\u5b3a\u5b3b\u5b3c\u5b3d\u5b3e\u5b3f\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b4f\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u5b68\u5b69\u5b6a\u5b6b\u5b6c\u5b6d\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b74\u5b75\u5b76\u5b77\u5b78\u5b79\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b81\u5b82\u5b83\u5b84\u5b85\u5b86\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba7\u5ba8\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c2d\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u5c5e\u5c5f\u5c60\u5c61\u5c62\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d\u5c6e\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4a\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u61f6\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u6203\u6204\u6205\u6206\u6207\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u6214\u6215\u6216\u6217\u6218\u6219\u621a\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6226\u6227\u6228\u6229\u622a\u622b\u622c\u622d\u622e\u622f\u6230\u6231\u6232\u6233\u6234\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u623d\u623e\u623f\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u625f\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6278\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b2\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u6526\u6527\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u658b\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u66ee\u66ef\u66f0\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u66fd\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6715\u6716\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6994\u6995\u6996\u6997\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6aeb\u6aec\u6aed\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0c\u6b0d\u6b0e\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b23\u6b24\u6b25\u6b26\u6b27\u6b28\u6b29\u6b2a\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b5e\u6b5f\u6b60\u6b61\u6b62\u6b63\u6b64\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b6f\u6b70\u6b71\u6b72\u6b73\u6b74\u6b75\u6b76\u6b77\u6b78\u6b79\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u6bac\u6bad\u6bae\u6baf\u6bb0\u6bb1\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bba\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc5\u6bc6\u6bc7\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u6bd9\u6bda\u6bdb\u6bdc\u6bdd\u6bde\u6bdf\u6be0\u6be1\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u6beb\u6bec\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0d\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d16\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecb\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edc\u6edd\u6ede\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3d\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd2\u6fd3\u6fd4\u6fd5\u6fd6\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u7179\u717a\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a6\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u7245\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7260\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u727a\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7345\u7346\u7347\u7348\u7349\u734a\u734b\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u7375\u7376\u7377\u7378\u7379\u737a\u737b\u737c\u737d\u737e\u737f\u7380\u7381\u7382\u7383\u7384\u7385\u7386\u7387\u7388\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74e3\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u750f\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751b\u751c\u751d\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u7527\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7568\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7572\u7573\u7574\u7575\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u757f\u7580\u7581\u7582\u7583\u7584\u7585\u7586\u7587\u7588\u7589\u758a\u758b\u758c\u758d\u758e\u758f\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u7629\u762a\u762b\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u764d\u764e\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76bf\u76c0\u76c1\u76c2\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e0\u76e1\u76e2\u76e3\u76e4\u76e5\u76e6\u76e7\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7\u77d8\u77d9\u77da\u77db\u77dc\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u7878\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u79a9\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79b7\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a76\u7a77\u7a78\u7a79\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac5\u7ac6\u7ac7\u7ac8\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc0\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc0\u7cc1\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u7ce8\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd0\u7dd1\u7dd2\u7dd3\u7dd4\u7dd5\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4b\u7e4c\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e71\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e93\u7e94\u7e95\u7e96\u7e97\u7e98\u7e99\u7e9a\u7e9b\u7e9c\u7e9d\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u7f35\u7f36\u7f37\u7f38\u7f39\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f3f\u7f40\u7f41\u7f42\u7f43\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u7f50\u7f51\u7f52\u7f53\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f65\u7f66\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8061\u8062\u8063\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8073\u8074\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a4\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81dc\u81dd\u81de\u81df\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u81e6\u81e7\u81e8\u81e9\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u81f5\u81f6\u81f7\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u8211\u8212\u8213\u8214\u8215\u8216\u8217\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u821f\u8220\u8221\u8222\u8223\u8224\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u823d\u823e\u823f\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8279\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u842a\u842b\u842c\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u8613\u8614\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ab\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d6\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u8862\u8863\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u8963\u8964\u8965\u8966\u8967\u8968\u8969\u896a\u896b\u896c\u896d\u896e\u896f\u8970\u8971\u8972\u8973\u8974\u8975\u8976\u8977\u8978\u8979\u897a\u897b\u897c\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8990\u8991\u8992\u8993\u8994\u8995\u8996\u8997\u8998\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a0\u89a1\u89a2\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89be\u89bf\u89c0\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89e8\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u89ef\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8ae9\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c76\u8c77\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u8d19\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8db6\u8db7\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6e\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u8e80\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u8e9d\u8e9e\u8e9f\u8ea0\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8ea9\u8eaa\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u8eb1\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ebe\u8ebf\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8faf\u8fb0\u8fb1\u8fb2\u8fb3\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u908c\u908d\u908e\u908f\u9090\u9091\u9092\u9093\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9144\u9145\u9146\u9147\u9148\u9149\u914a\u914b\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf\u91c0\u91c1\u91c2\u91c3\u91c4\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93f8\u93f9\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u9467\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u9577\u9578\u9579\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u95de\u95df\u95e0\u95e1\u95e2\u95e3\u95e4\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u974b\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u9860\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u986d\u986e\u986f\u9870\u9871\u9872\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98aa\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u9a5d\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u9ab8\u9ab9\u9aba\u9abb\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac1\u9ac2\u9ac3\u9ac4\u9ac5\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add\u9ade\u9adf\u9ae0\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u9b03\u9b04\u9b05\u9b06\u9b07\u9b08\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b33\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u9b43\u9b44\u9b45\u9b46\u9b47\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b61\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfb\u9bfc\u9bfd\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c1c\u9c1d\u9c1e\u9c1f\u9c20\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d64\u9d65\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d90\u9d91\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcb\u9dcc\u9dcd\u9dce\u9dcf\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9dfc\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e16\u9e17\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u9e77\u9e78\u9e79\u9e7a\u9e7b\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e87\u9e88\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ecd\u9ece\u9ecf\u9ed0\u9ed1\u9ed2\u9ed3\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9edc\u9edd\u9ede\u9edf\u9ee0\u9ee1\u9ee2\u9ee3\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eed\u9eee\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef5\u9ef6\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f05\u9f06\u9f07\u9f08\u9f09\u9f0a\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f14\u9f15\u9f16\u9f17\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u9f22\u9f23\u9f24\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f33\u9f34\u9f35\u9f36\u9f37\u9f38\u9f39\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u9f56\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fab\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fb4\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua800\ua801\ua803\ua804\ua805\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\uac00\uac01\uac02\uac03\uac04\uac05\uac06\uac07\uac08\uac09\uac0a\uac0b\uac0c\uac0d\uac0e\uac0f\uac10\uac11\uac12\uac13\uac14\uac15\uac16\uac17\uac18\uac19\uac1a\uac1b\uac1c\uac1d\uac1e\uac1f\uac20\uac21\uac22\uac23\uac24\uac25\uac26\uac27\uac28\uac29\uac2a\uac2b\uac2c\uac2d\uac2e\uac2f\uac30\uac31\uac32\uac33\uac34\uac35\uac36\uac37\uac38\uac39\uac3a\uac3b\uac3c\uac3d\uac3e\uac3f\uac40\uac41\uac42\uac43\uac44\uac45\uac46\uac47\uac48\uac49\uac4a\uac4b\uac4c\uac4d\uac4e\uac4f\uac50\uac51\uac52\uac53\uac54\uac55\uac56\uac57\uac58\uac59\uac5a\uac5b\uac5c\uac5d\uac5e\uac5f\uac60\uac61\uac62\uac63\uac64\uac65\uac66\uac67\uac68\uac69\uac6a\uac6b\uac6c\uac6d\uac6e\uac6f\uac70\uac71\uac72\uac73\uac74\uac75\uac76\uac77\uac78\uac79\uac7a\uac7b\uac7c\uac7d\uac7e\uac7f\uac80\uac81\uac82\uac83\uac84\uac85\uac86\uac87\uac88\uac89\uac8a\uac8b\uac8c\uac8d\uac8e\uac8f\uac90\uac91\uac92\uac93\uac94\uac95\uac96\uac97\uac98\uac99\uac9a\uac9b\uac9c\uac9d\uac9e\uac9f\uaca0\uaca1\uaca2\uaca3\uaca4\uaca5\uaca6\uaca7\uaca8\uaca9\uacaa\uacab\uacac\uacad\uacae\uacaf\uacb0\uacb1\uacb2\uacb3\uacb4\uacb5\uacb6\uacb7\uacb8\uacb9\uacba\uacbb\uacbc\uacbd\uacbe\uacbf\uacc0\uacc1\uacc2\uacc3\uacc4\uacc5\uacc6\uacc7\uacc8\uacc9\uacca\uaccb\uaccc\uaccd\uacce\uaccf\uacd0\uacd1\uacd2\uacd3\uacd4\uacd5\uacd6\uacd7\uacd8\uacd9\uacda\uacdb\uacdc\uacdd\uacde\uacdf\uace0\uace1\uace2\uace3\uace4\uace5\uace6\uace7\uace8\uace9\uacea\uaceb\uacec\uaced\uacee\uacef\uacf0\uacf1\uacf2\uacf3\uacf4\uacf5\uacf6\uacf7\uacf8\uacf9\uacfa\uacfb\uacfc\uacfd\uacfe\uacff\uad00\uad01\uad02\uad03\uad04\uad05\uad06\uad07\uad08\uad09\uad0a\uad0b\uad0c\uad0d\uad0e\uad0f\uad10\uad11\uad12\uad13\uad14\uad15\uad16\uad17\uad18\uad19\uad1a\uad1b\uad1c\uad1d\uad1e\uad1f\uad20\uad21\uad22\uad23\uad24\uad25\uad26\uad27\uad28\uad29\uad2a\uad2b\uad2c\uad2d\uad2e\uad2f\uad30\uad31\uad32\uad33\uad34\uad35\uad36\uad37\uad38\uad39\uad3a\uad3b\uad3c\uad3d\uad3e\uad3f\uad40\uad41\uad42\uad43\uad44\uad45\uad46\uad47\uad48\uad49\uad4a\uad4b\uad4c\uad4d\uad4e\uad4f\uad50\uad51\uad52\uad53\uad54\uad55\uad56\uad57\uad58\uad59\uad5a\uad5b\uad5c\uad5d\uad5e\uad5f\uad60\uad61\uad62\uad63\uad64\uad65\uad66\uad67\uad68\uad69\uad6a\uad6b\uad6c\uad6d\uad6e\uad6f\uad70\uad71\uad72\uad73\uad74\uad75\uad76\uad77\uad78\uad79\uad7a\uad7b\uad7c\uad7d\uad7e\uad7f\uad80\uad81\uad82\uad83\uad84\uad85\uad86\uad87\uad88\uad89\uad8a\uad8b\uad8c\uad8d\uad8e\uad8f\uad90\uad91\uad92\uad93\uad94\uad95\uad96\uad97\uad98\uad99\uad9a\uad9b\uad9c\uad9d\uad9e\uad9f\uada0\uada1\uada2\uada3\uada4\uada5\uada6\uada7\uada8\uada9\uadaa\uadab\uadac\uadad\uadae\uadaf\uadb0\uadb1\uadb2\uadb3\uadb4\uadb5\uadb6\uadb7\uadb8\uadb9\uadba\uadbb\uadbc\uadbd\uadbe\uadbf\uadc0\uadc1\uadc2\uadc3\uadc4\uadc5\uadc6\uadc7\uadc8\uadc9\uadca\uadcb\uadcc\uadcd\uadce\uadcf\uadd0\uadd1\uadd2\uadd3\uadd4\uadd5\uadd6\uadd7\uadd8\uadd9\uadda\uaddb\uaddc\uaddd\uadde\uaddf\uade0\uade1\uade2\uade3\uade4\uade5\uade6\uade7\uade8\uade9\uadea\uadeb\uadec\uaded\uadee\uadef\uadf0\uadf1\uadf2\uadf3\uadf4\uadf5\uadf6\uadf7\uadf8\uadf9\uadfa\uadfb\uadfc\uadfd\uadfe\uadff\uae00\uae01\uae02\uae03\uae04\uae05\uae06\uae07\uae08\uae09\uae0a\uae0b\uae0c\uae0d\uae0e\uae0f\uae10\uae11\uae12\uae13\uae14\uae15\uae16\uae17\uae18\uae19\uae1a\uae1b\uae1c\uae1d\uae1e\uae1f\uae20\uae21\uae22\uae23\uae24\uae25\uae26\uae27\uae28\uae29\uae2a\uae2b\uae2c\uae2d\uae2e\uae2f\uae30\uae31\uae32\uae33\uae34\uae35\uae36\uae37\uae38\uae39\uae3a\uae3b\uae3c\uae3d\uae3e\uae3f\uae40\uae41\uae42\uae43\uae44\uae45\uae46\uae47\uae48\uae49\uae4a\uae4b\uae4c\uae4d\uae4e\uae4f\uae50\uae51\uae52\uae53\uae54\uae55\uae56\uae57\uae58\uae59\uae5a\uae5b\uae5c\uae5d\uae5e\uae5f\uae60\uae61\uae62\uae63\uae64\uae65\uae66\uae67\uae68\uae69\uae6a\uae6b\uae6c\uae6d\uae6e\uae6f\uae70\uae71\uae72\uae73\uae74\uae75\uae76\uae77\uae78\uae79\uae7a\uae7b\uae7c\uae7d\uae7e\uae7f\uae80\uae81\uae82\uae83\uae84\uae85\uae86\uae87\uae88\uae89\uae8a\uae8b\uae8c\uae8d\uae8e\uae8f\uae90\uae91\uae92\uae93\uae94\uae95\uae96\uae97\uae98\uae99\uae9a\uae9b\uae9c\uae9d\uae9e\uae9f\uaea0\uaea1\uaea2\uaea3\uaea4\uaea5\uaea6\uaea7\uaea8\uaea9\uaeaa\uaeab\uaeac\uaead\uaeae\uaeaf\uaeb0\uaeb1\uaeb2\uaeb3\uaeb4\uaeb5\uaeb6\uaeb7\uaeb8\uaeb9\uaeba\uaebb\uaebc\uaebd\uaebe\uaebf\uaec0\uaec1\uaec2\uaec3\uaec4\uaec5\uaec6\uaec7\uaec8\uaec9\uaeca\uaecb\uaecc\uaecd\uaece\uaecf\uaed0\uaed1\uaed2\uaed3\uaed4\uaed5\uaed6\uaed7\uaed8\uaed9\uaeda\uaedb\uaedc\uaedd\uaede\uaedf\uaee0\uaee1\uaee2\uaee3\uaee4\uaee5\uaee6\uaee7\uaee8\uaee9\uaeea\uaeeb\uaeec\uaeed\uaeee\uaeef\uaef0\uaef1\uaef2\uaef3\uaef4\uaef5\uaef6\uaef7\uaef8\uaef9\uaefa\uaefb\uaefc\uaefd\uaefe\uaeff\uaf00\uaf01\uaf02\uaf03\uaf04\uaf05\uaf06\uaf07\uaf08\uaf09\uaf0a\uaf0b\uaf0c\uaf0d\uaf0e\uaf0f\uaf10\uaf11\uaf12\uaf13\uaf14\uaf15\uaf16\uaf17\uaf18\uaf19\uaf1a\uaf1b\uaf1c\uaf1d\uaf1e\uaf1f\uaf20\uaf21\uaf22\uaf23\uaf24\uaf25\uaf26\uaf27\uaf28\uaf29\uaf2a\uaf2b\uaf2c\uaf2d\uaf2e\uaf2f\uaf30\uaf31\uaf32\uaf33\uaf34\uaf35\uaf36\uaf37\uaf38\uaf39\uaf3a\uaf3b\uaf3c\uaf3d\uaf3e\uaf3f\uaf40\uaf41\uaf42\uaf43\uaf44\uaf45\uaf46\uaf47\uaf48\uaf49\uaf4a\uaf4b\uaf4c\uaf4d\uaf4e\uaf4f\uaf50\uaf51\uaf52\uaf53\uaf54\uaf55\uaf56\uaf57\uaf58\uaf59\uaf5a\uaf5b\uaf5c\uaf5d\uaf5e\uaf5f\uaf60\uaf61\uaf62\uaf63\uaf64\uaf65\uaf66\uaf67\uaf68\uaf69\uaf6a\uaf6b\uaf6c\uaf6d\uaf6e\uaf6f\uaf70\uaf71\uaf72\uaf73\uaf74\uaf75\uaf76\uaf77\uaf78\uaf79\uaf7a\uaf7b\uaf7c\uaf7d\uaf7e\uaf7f\uaf80\uaf81\uaf82\uaf83\uaf84\uaf85\uaf86\uaf87\uaf88\uaf89\uaf8a\uaf8b\uaf8c\uaf8d\uaf8e\uaf8f\uaf90\uaf91\uaf92\uaf93\uaf94\uaf95\uaf96\uaf97\uaf98\uaf99\uaf9a\uaf9b\uaf9c\uaf9d\uaf9e\uaf9f\uafa0\uafa1\uafa2\uafa3\uafa4\uafa5\uafa6\uafa7\uafa8\uafa9\uafaa\uafab\uafac\uafad\uafae\uafaf\uafb0\uafb1\uafb2\uafb3\uafb4\uafb5\uafb6\uafb7\uafb8\uafb9\uafba\uafbb\uafbc\uafbd\uafbe\uafbf\uafc0\uafc1\uafc2\uafc3\uafc4\uafc5\uafc6\uafc7\uafc8\uafc9\uafca\uafcb\uafcc\uafcd\uafce\uafcf\uafd0\uafd1\uafd2\uafd3\uafd4\uafd5\uafd6\uafd7\uafd8\uafd9\uafda\uafdb\uafdc\uafdd\uafde\uafdf\uafe0\uafe1\uafe2\uafe3\uafe4\uafe5\uafe6\uafe7\uafe8\uafe9\uafea\uafeb\uafec\uafed\uafee\uafef\uaff0\uaff1\uaff2\uaff3\uaff4\uaff5\uaff6\uaff7\uaff8\uaff9\uaffa\uaffb\uaffc\uaffd\uaffe\uafff\ub000\ub001\ub002\ub003\ub004\ub005\ub006\ub007\ub008\ub009\ub00a\ub00b\ub00c\ub00d\ub00e\ub00f\ub010\ub011\ub012\ub013\ub014\ub015\ub016\ub017\ub018\ub019\ub01a\ub01b\ub01c\ub01d\ub01e\ub01f\ub020\ub021\ub022\ub023\ub024\ub025\ub026\ub027\ub028\ub029\ub02a\ub02b\ub02c\ub02d\ub02e\ub02f\ub030\ub031\ub032\ub033\ub034\ub035\ub036\ub037\ub038\ub039\ub03a\ub03b\ub03c\ub03d\ub03e\ub03f\ub040\ub041\ub042\ub043\ub044\ub045\ub046\ub047\ub048\ub049\ub04a\ub04b\ub04c\ub04d\ub04e\ub04f\ub050\ub051\ub052\ub053\ub054\ub055\ub056\ub057\ub058\ub059\ub05a\ub05b\ub05c\ub05d\ub05e\ub05f\ub060\ub061\ub062\ub063\ub064\ub065\ub066\ub067\ub068\ub069\ub06a\ub06b\ub06c\ub06d\ub06e\ub06f\ub070\ub071\ub072\ub073\ub074\ub075\ub076\ub077\ub078\ub079\ub07a\ub07b\ub07c\ub07d\ub07e\ub07f\ub080\ub081\ub082\ub083\ub084\ub085\ub086\ub087\ub088\ub089\ub08a\ub08b\ub08c\ub08d\ub08e\ub08f\ub090\ub091\ub092\ub093\ub094\ub095\ub096\ub097\ub098\ub099\ub09a\ub09b\ub09c\ub09d\ub09e\ub09f\ub0a0\ub0a1\ub0a2\ub0a3\ub0a4\ub0a5\ub0a6\ub0a7\ub0a8\ub0a9\ub0aa\ub0ab\ub0ac\ub0ad\ub0ae\ub0af\ub0b0\ub0b1\ub0b2\ub0b3\ub0b4\ub0b5\ub0b6\ub0b7\ub0b8\ub0b9\ub0ba\ub0bb\ub0bc\ub0bd\ub0be\ub0bf\ub0c0\ub0c1\ub0c2\ub0c3\ub0c4\ub0c5\ub0c6\ub0c7\ub0c8\ub0c9\ub0ca\ub0cb\ub0cc\ub0cd\ub0ce\ub0cf\ub0d0\ub0d1\ub0d2\ub0d3\ub0d4\ub0d5\ub0d6\ub0d7\ub0d8\ub0d9\ub0da\ub0db\ub0dc\ub0dd\ub0de\ub0df\ub0e0\ub0e1\ub0e2\ub0e3\ub0e4\ub0e5\ub0e6\ub0e7\ub0e8\ub0e9\ub0ea\ub0eb\ub0ec\ub0ed\ub0ee\ub0ef\ub0f0\ub0f1\ub0f2\ub0f3\ub0f4\ub0f5\ub0f6\ub0f7\ub0f8\ub0f9\ub0fa\ub0fb\ub0fc\ub0fd\ub0fe\ub0ff\ub100\ub101\ub102\ub103\ub104\ub105\ub106\ub107\ub108\ub109\ub10a\ub10b\ub10c\ub10d\ub10e\ub10f\ub110\ub111\ub112\ub113\ub114\ub115\ub116\ub117\ub118\ub119\ub11a\ub11b\ub11c\ub11d\ub11e\ub11f\ub120\ub121\ub122\ub123\ub124\ub125\ub126\ub127\ub128\ub129\ub12a\ub12b\ub12c\ub12d\ub12e\ub12f\ub130\ub131\ub132\ub133\ub134\ub135\ub136\ub137\ub138\ub139\ub13a\ub13b\ub13c\ub13d\ub13e\ub13f\ub140\ub141\ub142\ub143\ub144\ub145\ub146\ub147\ub148\ub149\ub14a\ub14b\ub14c\ub14d\ub14e\ub14f\ub150\ub151\ub152\ub153\ub154\ub155\ub156\ub157\ub158\ub159\ub15a\ub15b\ub15c\ub15d\ub15e\ub15f\ub160\ub161\ub162\ub163\ub164\ub165\ub166\ub167\ub168\ub169\ub16a\ub16b\ub16c\ub16d\ub16e\ub16f\ub170\ub171\ub172\ub173\ub174\ub175\ub176\ub177\ub178\ub179\ub17a\ub17b\ub17c\ub17d\ub17e\ub17f\ub180\ub181\ub182\ub183\ub184\ub185\ub186\ub187\ub188\ub189\ub18a\ub18b\ub18c\ub18d\ub18e\ub18f\ub190\ub191\ub192\ub193\ub194\ub195\ub196\ub197\ub198\ub199\ub19a\ub19b\ub19c\ub19d\ub19e\ub19f\ub1a0\ub1a1\ub1a2\ub1a3\ub1a4\ub1a5\ub1a6\ub1a7\ub1a8\ub1a9\ub1aa\ub1ab\ub1ac\ub1ad\ub1ae\ub1af\ub1b0\ub1b1\ub1b2\ub1b3\ub1b4\ub1b5\ub1b6\ub1b7\ub1b8\ub1b9\ub1ba\ub1bb\ub1bc\ub1bd\ub1be\ub1bf\ub1c0\ub1c1\ub1c2\ub1c3\ub1c4\ub1c5\ub1c6\ub1c7\ub1c8\ub1c9\ub1ca\ub1cb\ub1cc\ub1cd\ub1ce\ub1cf\ub1d0\ub1d1\ub1d2\ub1d3\ub1d4\ub1d5\ub1d6\ub1d7\ub1d8\ub1d9\ub1da\ub1db\ub1dc\ub1dd\ub1de\ub1df\ub1e0\ub1e1\ub1e2\ub1e3\ub1e4\ub1e5\ub1e6\ub1e7\ub1e8\ub1e9\ub1ea\ub1eb\ub1ec\ub1ed\ub1ee\ub1ef\ub1f0\ub1f1\ub1f2\ub1f3\ub1f4\ub1f5\ub1f6\ub1f7\ub1f8\ub1f9\ub1fa\ub1fb\ub1fc\ub1fd\ub1fe\ub1ff\ub200\ub201\ub202\ub203\ub204\ub205\ub206\ub207\ub208\ub209\ub20a\ub20b\ub20c\ub20d\ub20e\ub20f\ub210\ub211\ub212\ub213\ub214\ub215\ub216\ub217\ub218\ub219\ub21a\ub21b\ub21c\ub21d\ub21e\ub21f\ub220\ub221\ub222\ub223\ub224\ub225\ub226\ub227\ub228\ub229\ub22a\ub22b\ub22c\ub22d\ub22e\ub22f\ub230\ub231\ub232\ub233\ub234\ub235\ub236\ub237\ub238\ub239\ub23a\ub23b\ub23c\ub23d\ub23e\ub23f\ub240\ub241\ub242\ub243\ub244\ub245\ub246\ub247\ub248\ub249\ub24a\ub24b\ub24c\ub24d\ub24e\ub24f\ub250\ub251\ub252\ub253\ub254\ub255\ub256\ub257\ub258\ub259\ub25a\ub25b\ub25c\ub25d\ub25e\ub25f\ub260\ub261\ub262\ub263\ub264\ub265\ub266\ub267\ub268\ub269\ub26a\ub26b\ub26c\ub26d\ub26e\ub26f\ub270\ub271\ub272\ub273\ub274\ub275\ub276\ub277\ub278\ub279\ub27a\ub27b\ub27c\ub27d\ub27e\ub27f\ub280\ub281\ub282\ub283\ub284\ub285\ub286\ub287\ub288\ub289\ub28a\ub28b\ub28c\ub28d\ub28e\ub28f\ub290\ub291\ub292\ub293\ub294\ub295\ub296\ub297\ub298\ub299\ub29a\ub29b\ub29c\ub29d\ub29e\ub29f\ub2a0\ub2a1\ub2a2\ub2a3\ub2a4\ub2a5\ub2a6\ub2a7\ub2a8\ub2a9\ub2aa\ub2ab\ub2ac\ub2ad\ub2ae\ub2af\ub2b0\ub2b1\ub2b2\ub2b3\ub2b4\ub2b5\ub2b6\ub2b7\ub2b8\ub2b9\ub2ba\ub2bb\ub2bc\ub2bd\ub2be\ub2bf\ub2c0\ub2c1\ub2c2\ub2c3\ub2c4\ub2c5\ub2c6\ub2c7\ub2c8\ub2c9\ub2ca\ub2cb\ub2cc\ub2cd\ub2ce\ub2cf\ub2d0\ub2d1\ub2d2\ub2d3\ub2d4\ub2d5\ub2d6\ub2d7\ub2d8\ub2d9\ub2da\ub2db\ub2dc\ub2dd\ub2de\ub2df\ub2e0\ub2e1\ub2e2\ub2e3\ub2e4\ub2e5\ub2e6\ub2e7\ub2e8\ub2e9\ub2ea\ub2eb\ub2ec\ub2ed\ub2ee\ub2ef\ub2f0\ub2f1\ub2f2\ub2f3\ub2f4\ub2f5\ub2f6\ub2f7\ub2f8\ub2f9\ub2fa\ub2fb\ub2fc\ub2fd\ub2fe\ub2ff\ub300\ub301\ub302\ub303\ub304\ub305\ub306\ub307\ub308\ub309\ub30a\ub30b\ub30c\ub30d\ub30e\ub30f\ub310\ub311\ub312\ub313\ub314\ub315\ub316\ub317\ub318\ub319\ub31a\ub31b\ub31c\ub31d\ub31e\ub31f\ub320\ub321\ub322\ub323\ub324\ub325\ub326\ub327\ub328\ub329\ub32a\ub32b\ub32c\ub32d\ub32e\ub32f\ub330\ub331\ub332\ub333\ub334\ub335\ub336\ub337\ub338\ub339\ub33a\ub33b\ub33c\ub33d\ub33e\ub33f\ub340\ub341\ub342\ub343\ub344\ub345\ub346\ub347\ub348\ub349\ub34a\ub34b\ub34c\ub34d\ub34e\ub34f\ub350\ub351\ub352\ub353\ub354\ub355\ub356\ub357\ub358\ub359\ub35a\ub35b\ub35c\ub35d\ub35e\ub35f\ub360\ub361\ub362\ub363\ub364\ub365\ub366\ub367\ub368\ub369\ub36a\ub36b\ub36c\ub36d\ub36e\ub36f\ub370\ub371\ub372\ub373\ub374\ub375\ub376\ub377\ub378\ub379\ub37a\ub37b\ub37c\ub37d\ub37e\ub37f\ub380\ub381\ub382\ub383\ub384\ub385\ub386\ub387\ub388\ub389\ub38a\ub38b\ub38c\ub38d\ub38e\ub38f\ub390\ub391\ub392\ub393\ub394\ub395\ub396\ub397\ub398\ub399\ub39a\ub39b\ub39c\ub39d\ub39e\ub39f\ub3a0\ub3a1\ub3a2\ub3a3\ub3a4\ub3a5\ub3a6\ub3a7\ub3a8\ub3a9\ub3aa\ub3ab\ub3ac\ub3ad\ub3ae\ub3af\ub3b0\ub3b1\ub3b2\ub3b3\ub3b4\ub3b5\ub3b6\ub3b7\ub3b8\ub3b9\ub3ba\ub3bb\ub3bc\ub3bd\ub3be\ub3bf\ub3c0\ub3c1\ub3c2\ub3c3\ub3c4\ub3c5\ub3c6\ub3c7\ub3c8\ub3c9\ub3ca\ub3cb\ub3cc\ub3cd\ub3ce\ub3cf\ub3d0\ub3d1\ub3d2\ub3d3\ub3d4\ub3d5\ub3d6\ub3d7\ub3d8\ub3d9\ub3da\ub3db\ub3dc\ub3dd\ub3de\ub3df\ub3e0\ub3e1\ub3e2\ub3e3\ub3e4\ub3e5\ub3e6\ub3e7\ub3e8\ub3e9\ub3ea\ub3eb\ub3ec\ub3ed\ub3ee\ub3ef\ub3f0\ub3f1\ub3f2\ub3f3\ub3f4\ub3f5\ub3f6\ub3f7\ub3f8\ub3f9\ub3fa\ub3fb\ub3fc\ub3fd\ub3fe\ub3ff\ub400\ub401\ub402\ub403\ub404\ub405\ub406\ub407\ub408\ub409\ub40a\ub40b\ub40c\ub40d\ub40e\ub40f\ub410\ub411\ub412\ub413\ub414\ub415\ub416\ub417\ub418\ub419\ub41a\ub41b\ub41c\ub41d\ub41e\ub41f\ub420\ub421\ub422\ub423\ub424\ub425\ub426\ub427\ub428\ub429\ub42a\ub42b\ub42c\ub42d\ub42e\ub42f\ub430\ub431\ub432\ub433\ub434\ub435\ub436\ub437\ub438\ub439\ub43a\ub43b\ub43c\ub43d\ub43e\ub43f\ub440\ub441\ub442\ub443\ub444\ub445\ub446\ub447\ub448\ub449\ub44a\ub44b\ub44c\ub44d\ub44e\ub44f\ub450\ub451\ub452\ub453\ub454\ub455\ub456\ub457\ub458\ub459\ub45a\ub45b\ub45c\ub45d\ub45e\ub45f\ub460\ub461\ub462\ub463\ub464\ub465\ub466\ub467\ub468\ub469\ub46a\ub46b\ub46c\ub46d\ub46e\ub46f\ub470\ub471\ub472\ub473\ub474\ub475\ub476\ub477\ub478\ub479\ub47a\ub47b\ub47c\ub47d\ub47e\ub47f\ub480\ub481\ub482\ub483\ub484\ub485\ub486\ub487\ub488\ub489\ub48a\ub48b\ub48c\ub48d\ub48e\ub48f\ub490\ub491\ub492\ub493\ub494\ub495\ub496\ub497\ub498\ub499\ub49a\ub49b\ub49c\ub49d\ub49e\ub49f\ub4a0\ub4a1\ub4a2\ub4a3\ub4a4\ub4a5\ub4a6\ub4a7\ub4a8\ub4a9\ub4aa\ub4ab\ub4ac\ub4ad\ub4ae\ub4af\ub4b0\ub4b1\ub4b2\ub4b3\ub4b4\ub4b5\ub4b6\ub4b7\ub4b8\ub4b9\ub4ba\ub4bb\ub4bc\ub4bd\ub4be\ub4bf\ub4c0\ub4c1\ub4c2\ub4c3\ub4c4\ub4c5\ub4c6\ub4c7\ub4c8\ub4c9\ub4ca\ub4cb\ub4cc\ub4cd\ub4ce\ub4cf\ub4d0\ub4d1\ub4d2\ub4d3\ub4d4\ub4d5\ub4d6\ub4d7\ub4d8\ub4d9\ub4da\ub4db\ub4dc\ub4dd\ub4de\ub4df\ub4e0\ub4e1\ub4e2\ub4e3\ub4e4\ub4e5\ub4e6\ub4e7\ub4e8\ub4e9\ub4ea\ub4eb\ub4ec\ub4ed\ub4ee\ub4ef\ub4f0\ub4f1\ub4f2\ub4f3\ub4f4\ub4f5\ub4f6\ub4f7\ub4f8\ub4f9\ub4fa\ub4fb\ub4fc\ub4fd\ub4fe\ub4ff\ub500\ub501\ub502\ub503\ub504\ub505\ub506\ub507\ub508\ub509\ub50a\ub50b\ub50c\ub50d\ub50e\ub50f\ub510\ub511\ub512\ub513\ub514\ub515\ub516\ub517\ub518\ub519\ub51a\ub51b\ub51c\ub51d\ub51e\ub51f\ub520\ub521\ub522\ub523\ub524\ub525\ub526\ub527\ub528\ub529\ub52a\ub52b\ub52c\ub52d\ub52e\ub52f\ub530\ub531\ub532\ub533\ub534\ub535\ub536\ub537\ub538\ub539\ub53a\ub53b\ub53c\ub53d\ub53e\ub53f\ub540\ub541\ub542\ub543\ub544\ub545\ub546\ub547\ub548\ub549\ub54a\ub54b\ub54c\ub54d\ub54e\ub54f\ub550\ub551\ub552\ub553\ub554\ub555\ub556\ub557\ub558\ub559\ub55a\ub55b\ub55c\ub55d\ub55e\ub55f\ub560\ub561\ub562\ub563\ub564\ub565\ub566\ub567\ub568\ub569\ub56a\ub56b\ub56c\ub56d\ub56e\ub56f\ub570\ub571\ub572\ub573\ub574\ub575\ub576\ub577\ub578\ub579\ub57a\ub57b\ub57c\ub57d\ub57e\ub57f\ub580\ub581\ub582\ub583\ub584\ub585\ub586\ub587\ub588\ub589\ub58a\ub58b\ub58c\ub58d\ub58e\ub58f\ub590\ub591\ub592\ub593\ub594\ub595\ub596\ub597\ub598\ub599\ub59a\ub59b\ub59c\ub59d\ub59e\ub59f\ub5a0\ub5a1\ub5a2\ub5a3\ub5a4\ub5a5\ub5a6\ub5a7\ub5a8\ub5a9\ub5aa\ub5ab\ub5ac\ub5ad\ub5ae\ub5af\ub5b0\ub5b1\ub5b2\ub5b3\ub5b4\ub5b5\ub5b6\ub5b7\ub5b8\ub5b9\ub5ba\ub5bb\ub5bc\ub5bd\ub5be\ub5bf\ub5c0\ub5c1\ub5c2\ub5c3\ub5c4\ub5c5\ub5c6\ub5c7\ub5c8\ub5c9\ub5ca\ub5cb\ub5cc\ub5cd\ub5ce\ub5cf\ub5d0\ub5d1\ub5d2\ub5d3\ub5d4\ub5d5\ub5d6\ub5d7\ub5d8\ub5d9\ub5da\ub5db\ub5dc\ub5dd\ub5de\ub5df\ub5e0\ub5e1\ub5e2\ub5e3\ub5e4\ub5e5\ub5e6\ub5e7\ub5e8\ub5e9\ub5ea\ub5eb\ub5ec\ub5ed\ub5ee\ub5ef\ub5f0\ub5f1\ub5f2\ub5f3\ub5f4\ub5f5\ub5f6\ub5f7\ub5f8\ub5f9\ub5fa\ub5fb\ub5fc\ub5fd\ub5fe\ub5ff\ub600\ub601\ub602\ub603\ub604\ub605\ub606\ub607\ub608\ub609\ub60a\ub60b\ub60c\ub60d\ub60e\ub60f\ub610\ub611\ub612\ub613\ub614\ub615\ub616\ub617\ub618\ub619\ub61a\ub61b\ub61c\ub61d\ub61e\ub61f\ub620\ub621\ub622\ub623\ub624\ub625\ub626\ub627\ub628\ub629\ub62a\ub62b\ub62c\ub62d\ub62e\ub62f\ub630\ub631\ub632\ub633\ub634\ub635\ub636\ub637\ub638\ub639\ub63a\ub63b\ub63c\ub63d\ub63e\ub63f\ub640\ub641\ub642\ub643\ub644\ub645\ub646\ub647\ub648\ub649\ub64a\ub64b\ub64c\ub64d\ub64e\ub64f\ub650\ub651\ub652\ub653\ub654\ub655\ub656\ub657\ub658\ub659\ub65a\ub65b\ub65c\ub65d\ub65e\ub65f\ub660\ub661\ub662\ub663\ub664\ub665\ub666\ub667\ub668\ub669\ub66a\ub66b\ub66c\ub66d\ub66e\ub66f\ub670\ub671\ub672\ub673\ub674\ub675\ub676\ub677\ub678\ub679\ub67a\ub67b\ub67c\ub67d\ub67e\ub67f\ub680\ub681\ub682\ub683\ub684\ub685\ub686\ub687\ub688\ub689\ub68a\ub68b\ub68c\ub68d\ub68e\ub68f\ub690\ub691\ub692\ub693\ub694\ub695\ub696\ub697\ub698\ub699\ub69a\ub69b\ub69c\ub69d\ub69e\ub69f\ub6a0\ub6a1\ub6a2\ub6a3\ub6a4\ub6a5\ub6a6\ub6a7\ub6a8\ub6a9\ub6aa\ub6ab\ub6ac\ub6ad\ub6ae\ub6af\ub6b0\ub6b1\ub6b2\ub6b3\ub6b4\ub6b5\ub6b6\ub6b7\ub6b8\ub6b9\ub6ba\ub6bb\ub6bc\ub6bd\ub6be\ub6bf\ub6c0\ub6c1\ub6c2\ub6c3\ub6c4\ub6c5\ub6c6\ub6c7\ub6c8\ub6c9\ub6ca\ub6cb\ub6cc\ub6cd\ub6ce\ub6cf\ub6d0\ub6d1\ub6d2\ub6d3\ub6d4\ub6d5\ub6d6\ub6d7\ub6d8\ub6d9\ub6da\ub6db\ub6dc\ub6dd\ub6de\ub6df\ub6e0\ub6e1\ub6e2\ub6e3\ub6e4\ub6e5\ub6e6\ub6e7\ub6e8\ub6e9\ub6ea\ub6eb\ub6ec\ub6ed\ub6ee\ub6ef\ub6f0\ub6f1\ub6f2\ub6f3\ub6f4\ub6f5\ub6f6\ub6f7\ub6f8\ub6f9\ub6fa\ub6fb\ub6fc\ub6fd\ub6fe\ub6ff\ub700\ub701\ub702\ub703\ub704\ub705\ub706\ub707\ub708\ub709\ub70a\ub70b\ub70c\ub70d\ub70e\ub70f\ub710\ub711\ub712\ub713\ub714\ub715\ub716\ub717\ub718\ub719\ub71a\ub71b\ub71c\ub71d\ub71e\ub71f\ub720\ub721\ub722\ub723\ub724\ub725\ub726\ub727\ub728\ub729\ub72a\ub72b\ub72c\ub72d\ub72e\ub72f\ub730\ub731\ub732\ub733\ub734\ub735\ub736\ub737\ub738\ub739\ub73a\ub73b\ub73c\ub73d\ub73e\ub73f\ub740\ub741\ub742\ub743\ub744\ub745\ub746\ub747\ub748\ub749\ub74a\ub74b\ub74c\ub74d\ub74e\ub74f\ub750\ub751\ub752\ub753\ub754\ub755\ub756\ub757\ub758\ub759\ub75a\ub75b\ub75c\ub75d\ub75e\ub75f\ub760\ub761\ub762\ub763\ub764\ub765\ub766\ub767\ub768\ub769\ub76a\ub76b\ub76c\ub76d\ub76e\ub76f\ub770\ub771\ub772\ub773\ub774\ub775\ub776\ub777\ub778\ub779\ub77a\ub77b\ub77c\ub77d\ub77e\ub77f\ub780\ub781\ub782\ub783\ub784\ub785\ub786\ub787\ub788\ub789\ub78a\ub78b\ub78c\ub78d\ub78e\ub78f\ub790\ub791\ub792\ub793\ub794\ub795\ub796\ub797\ub798\ub799\ub79a\ub79b\ub79c\ub79d\ub79e\ub79f\ub7a0\ub7a1\ub7a2\ub7a3\ub7a4\ub7a5\ub7a6\ub7a7\ub7a8\ub7a9\ub7aa\ub7ab\ub7ac\ub7ad\ub7ae\ub7af\ub7b0\ub7b1\ub7b2\ub7b3\ub7b4\ub7b5\ub7b6\ub7b7\ub7b8\ub7b9\ub7ba\ub7bb\ub7bc\ub7bd\ub7be\ub7bf\ub7c0\ub7c1\ub7c2\ub7c3\ub7c4\ub7c5\ub7c6\ub7c7\ub7c8\ub7c9\ub7ca\ub7cb\ub7cc\ub7cd\ub7ce\ub7cf\ub7d0\ub7d1\ub7d2\ub7d3\ub7d4\ub7d5\ub7d6\ub7d7\ub7d8\ub7d9\ub7da\ub7db\ub7dc\ub7dd\ub7de\ub7df\ub7e0\ub7e1\ub7e2\ub7e3\ub7e4\ub7e5\ub7e6\ub7e7\ub7e8\ub7e9\ub7ea\ub7eb\ub7ec\ub7ed\ub7ee\ub7ef\ub7f0\ub7f1\ub7f2\ub7f3\ub7f4\ub7f5\ub7f6\ub7f7\ub7f8\ub7f9\ub7fa\ub7fb\ub7fc\ub7fd\ub7fe\ub7ff\ub800\ub801\ub802\ub803\ub804\ub805\ub806\ub807\ub808\ub809\ub80a\ub80b\ub80c\ub80d\ub80e\ub80f\ub810\ub811\ub812\ub813\ub814\ub815\ub816\ub817\ub818\ub819\ub81a\ub81b\ub81c\ub81d\ub81e\ub81f\ub820\ub821\ub822\ub823\ub824\ub825\ub826\ub827\ub828\ub829\ub82a\ub82b\ub82c\ub82d\ub82e\ub82f\ub830\ub831\ub832\ub833\ub834\ub835\ub836\ub837\ub838\ub839\ub83a\ub83b\ub83c\ub83d\ub83e\ub83f\ub840\ub841\ub842\ub843\ub844\ub845\ub846\ub847\ub848\ub849\ub84a\ub84b\ub84c\ub84d\ub84e\ub84f\ub850\ub851\ub852\ub853\ub854\ub855\ub856\ub857\ub858\ub859\ub85a\ub85b\ub85c\ub85d\ub85e\ub85f\ub860\ub861\ub862\ub863\ub864\ub865\ub866\ub867\ub868\ub869\ub86a\ub86b\ub86c\ub86d\ub86e\ub86f\ub870\ub871\ub872\ub873\ub874\ub875\ub876\ub877\ub878\ub879\ub87a\ub87b\ub87c\ub87d\ub87e\ub87f\ub880\ub881\ub882\ub883\ub884\ub885\ub886\ub887\ub888\ub889\ub88a\ub88b\ub88c\ub88d\ub88e\ub88f\ub890\ub891\ub892\ub893\ub894\ub895\ub896\ub897\ub898\ub899\ub89a\ub89b\ub89c\ub89d\ub89e\ub89f\ub8a0\ub8a1\ub8a2\ub8a3\ub8a4\ub8a5\ub8a6\ub8a7\ub8a8\ub8a9\ub8aa\ub8ab\ub8ac\ub8ad\ub8ae\ub8af\ub8b0\ub8b1\ub8b2\ub8b3\ub8b4\ub8b5\ub8b6\ub8b7\ub8b8\ub8b9\ub8ba\ub8bb\ub8bc\ub8bd\ub8be\ub8bf\ub8c0\ub8c1\ub8c2\ub8c3\ub8c4\ub8c5\ub8c6\ub8c7\ub8c8\ub8c9\ub8ca\ub8cb\ub8cc\ub8cd\ub8ce\ub8cf\ub8d0\ub8d1\ub8d2\ub8d3\ub8d4\ub8d5\ub8d6\ub8d7\ub8d8\ub8d9\ub8da\ub8db\ub8dc\ub8dd\ub8de\ub8df\ub8e0\ub8e1\ub8e2\ub8e3\ub8e4\ub8e5\ub8e6\ub8e7\ub8e8\ub8e9\ub8ea\ub8eb\ub8ec\ub8ed\ub8ee\ub8ef\ub8f0\ub8f1\ub8f2\ub8f3\ub8f4\ub8f5\ub8f6\ub8f7\ub8f8\ub8f9\ub8fa\ub8fb\ub8fc\ub8fd\ub8fe\ub8ff\ub900\ub901\ub902\ub903\ub904\ub905\ub906\ub907\ub908\ub909\ub90a\ub90b\ub90c\ub90d\ub90e\ub90f\ub910\ub911\ub912\ub913\ub914\ub915\ub916\ub917\ub918\ub919\ub91a\ub91b\ub91c\ub91d\ub91e\ub91f\ub920\ub921\ub922\ub923\ub924\ub925\ub926\ub927\ub928\ub929\ub92a\ub92b\ub92c\ub92d\ub92e\ub92f\ub930\ub931\ub932\ub933\ub934\ub935\ub936\ub937\ub938\ub939\ub93a\ub93b\ub93c\ub93d\ub93e\ub93f\ub940\ub941\ub942\ub943\ub944\ub945\ub946\ub947\ub948\ub949\ub94a\ub94b\ub94c\ub94d\ub94e\ub94f\ub950\ub951\ub952\ub953\ub954\ub955\ub956\ub957\ub958\ub959\ub95a\ub95b\ub95c\ub95d\ub95e\ub95f\ub960\ub961\ub962\ub963\ub964\ub965\ub966\ub967\ub968\ub969\ub96a\ub96b\ub96c\ub96d\ub96e\ub96f\ub970\ub971\ub972\ub973\ub974\ub975\ub976\ub977\ub978\ub979\ub97a\ub97b\ub97c\ub97d\ub97e\ub97f\ub980\ub981\ub982\ub983\ub984\ub985\ub986\ub987\ub988\ub989\ub98a\ub98b\ub98c\ub98d\ub98e\ub98f\ub990\ub991\ub992\ub993\ub994\ub995\ub996\ub997\ub998\ub999\ub99a\ub99b\ub99c\ub99d\ub99e\ub99f\ub9a0\ub9a1\ub9a2\ub9a3\ub9a4\ub9a5\ub9a6\ub9a7\ub9a8\ub9a9\ub9aa\ub9ab\ub9ac\ub9ad\ub9ae\ub9af\ub9b0\ub9b1\ub9b2\ub9b3\ub9b4\ub9b5\ub9b6\ub9b7\ub9b8\ub9b9\ub9ba\ub9bb\ub9bc\ub9bd\ub9be\ub9bf\ub9c0\ub9c1\ub9c2\ub9c3\ub9c4\ub9c5\ub9c6\ub9c7\ub9c8\ub9c9\ub9ca\ub9cb\ub9cc\ub9cd\ub9ce\ub9cf\ub9d0\ub9d1\ub9d2\ub9d3\ub9d4\ub9d5\ub9d6\ub9d7\ub9d8\ub9d9\ub9da\ub9db\ub9dc\ub9dd\ub9de\ub9df\ub9e0\ub9e1\ub9e2\ub9e3\ub9e4\ub9e5\ub9e6\ub9e7\ub9e8\ub9e9\ub9ea\ub9eb\ub9ec\ub9ed\ub9ee\ub9ef\ub9f0\ub9f1\ub9f2\ub9f3\ub9f4\ub9f5\ub9f6\ub9f7\ub9f8\ub9f9\ub9fa\ub9fb\ub9fc\ub9fd\ub9fe\ub9ff\uba00\uba01\uba02\uba03\uba04\uba05\uba06\uba07\uba08\uba09\uba0a\uba0b\uba0c\uba0d\uba0e\uba0f\uba10\uba11\uba12\uba13\uba14\uba15\uba16\uba17\uba18\uba19\uba1a\uba1b\uba1c\uba1d\uba1e\uba1f\uba20\uba21\uba22\uba23\uba24\uba25\uba26\uba27\uba28\uba29\uba2a\uba2b\uba2c\uba2d\uba2e\uba2f\uba30\uba31\uba32\uba33\uba34\uba35\uba36\uba37\uba38\uba39\uba3a\uba3b\uba3c\uba3d\uba3e\uba3f\uba40\uba41\uba42\uba43\uba44\uba45\uba46\uba47\uba48\uba49\uba4a\uba4b\uba4c\uba4d\uba4e\uba4f\uba50\uba51\uba52\uba53\uba54\uba55\uba56\uba57\uba58\uba59\uba5a\uba5b\uba5c\uba5d\uba5e\uba5f\uba60\uba61\uba62\uba63\uba64\uba65\uba66\uba67\uba68\uba69\uba6a\uba6b\uba6c\uba6d\uba6e\uba6f\uba70\uba71\uba72\uba73\uba74\uba75\uba76\uba77\uba78\uba79\uba7a\uba7b\uba7c\uba7d\uba7e\uba7f\uba80\uba81\uba82\uba83\uba84\uba85\uba86\uba87\uba88\uba89\uba8a\uba8b\uba8c\uba8d\uba8e\uba8f\uba90\uba91\uba92\uba93\uba94\uba95\uba96\uba97\uba98\uba99\uba9a\uba9b\uba9c\uba9d\uba9e\uba9f\ubaa0\ubaa1\ubaa2\ubaa3\ubaa4\ubaa5\ubaa6\ubaa7\ubaa8\ubaa9\ubaaa\ubaab\ubaac\ubaad\ubaae\ubaaf\ubab0\ubab1\ubab2\ubab3\ubab4\ubab5\ubab6\ubab7\ubab8\ubab9\ubaba\ubabb\ubabc\ubabd\ubabe\ubabf\ubac0\ubac1\ubac2\ubac3\ubac4\ubac5\ubac6\ubac7\ubac8\ubac9\ubaca\ubacb\ubacc\ubacd\ubace\ubacf\ubad0\ubad1\ubad2\ubad3\ubad4\ubad5\ubad6\ubad7\ubad8\ubad9\ubada\ubadb\ubadc\ubadd\ubade\ubadf\ubae0\ubae1\ubae2\ubae3\ubae4\ubae5\ubae6\ubae7\ubae8\ubae9\ubaea\ubaeb\ubaec\ubaed\ubaee\ubaef\ubaf0\ubaf1\ubaf2\ubaf3\ubaf4\ubaf5\ubaf6\ubaf7\ubaf8\ubaf9\ubafa\ubafb\ubafc\ubafd\ubafe\ubaff\ubb00\ubb01\ubb02\ubb03\ubb04\ubb05\ubb06\ubb07\ubb08\ubb09\ubb0a\ubb0b\ubb0c\ubb0d\ubb0e\ubb0f\ubb10\ubb11\ubb12\ubb13\ubb14\ubb15\ubb16\ubb17\ubb18\ubb19\ubb1a\ubb1b\ubb1c\ubb1d\ubb1e\ubb1f\ubb20\ubb21\ubb22\ubb23\ubb24\ubb25\ubb26\ubb27\ubb28\ubb29\ubb2a\ubb2b\ubb2c\ubb2d\ubb2e\ubb2f\ubb30\ubb31\ubb32\ubb33\ubb34\ubb35\ubb36\ubb37\ubb38\ubb39\ubb3a\ubb3b\ubb3c\ubb3d\ubb3e\ubb3f\ubb40\ubb41\ubb42\ubb43\ubb44\ubb45\ubb46\ubb47\ubb48\ubb49\ubb4a\ubb4b\ubb4c\ubb4d\ubb4e\ubb4f\ubb50\ubb51\ubb52\ubb53\ubb54\ubb55\ubb56\ubb57\ubb58\ubb59\ubb5a\ubb5b\ubb5c\ubb5d\ubb5e\ubb5f\ubb60\ubb61\ubb62\ubb63\ubb64\ubb65\ubb66\ubb67\ubb68\ubb69\ubb6a\ubb6b\ubb6c\ubb6d\ubb6e\ubb6f\ubb70\ubb71\ubb72\ubb73\ubb74\ubb75\ubb76\ubb77\ubb78\ubb79\ubb7a\ubb7b\ubb7c\ubb7d\ubb7e\ubb7f\ubb80\ubb81\ubb82\ubb83\ubb84\ubb85\ubb86\ubb87\ubb88\ubb89\ubb8a\ubb8b\ubb8c\ubb8d\ubb8e\ubb8f\ubb90\ubb91\ubb92\ubb93\ubb94\ubb95\ubb96\ubb97\ubb98\ubb99\ubb9a\ubb9b\ubb9c\ubb9d\ubb9e\ubb9f\ubba0\ubba1\ubba2\ubba3\ubba4\ubba5\ubba6\ubba7\ubba8\ubba9\ubbaa\ubbab\ubbac\ubbad\ubbae\ubbaf\ubbb0\ubbb1\ubbb2\ubbb3\ubbb4\ubbb5\ubbb6\ubbb7\ubbb8\ubbb9\ubbba\ubbbb\ubbbc\ubbbd\ubbbe\ubbbf\ubbc0\ubbc1\ubbc2\ubbc3\ubbc4\ubbc5\ubbc6\ubbc7\ubbc8\ubbc9\ubbca\ubbcb\ubbcc\ubbcd\ubbce\ubbcf\ubbd0\ubbd1\ubbd2\ubbd3\ubbd4\ubbd5\ubbd6\ubbd7\ubbd8\ubbd9\ubbda\ubbdb\ubbdc\ubbdd\ubbde\ubbdf\ubbe0\ubbe1\ubbe2\ubbe3\ubbe4\ubbe5\ubbe6\ubbe7\ubbe8\ubbe9\ubbea\ubbeb\ubbec\ubbed\ubbee\ubbef\ubbf0\ubbf1\ubbf2\ubbf3\ubbf4\ubbf5\ubbf6\ubbf7\ubbf8\ubbf9\ubbfa\ubbfb\ubbfc\ubbfd\ubbfe\ubbff\ubc00\ubc01\ubc02\ubc03\ubc04\ubc05\ubc06\ubc07\ubc08\ubc09\ubc0a\ubc0b\ubc0c\ubc0d\ubc0e\ubc0f\ubc10\ubc11\ubc12\ubc13\ubc14\ubc15\ubc16\ubc17\ubc18\ubc19\ubc1a\ubc1b\ubc1c\ubc1d\ubc1e\ubc1f\ubc20\ubc21\ubc22\ubc23\ubc24\ubc25\ubc26\ubc27\ubc28\ubc29\ubc2a\ubc2b\ubc2c\ubc2d\ubc2e\ubc2f\ubc30\ubc31\ubc32\ubc33\ubc34\ubc35\ubc36\ubc37\ubc38\ubc39\ubc3a\ubc3b\ubc3c\ubc3d\ubc3e\ubc3f\ubc40\ubc41\ubc42\ubc43\ubc44\ubc45\ubc46\ubc47\ubc48\ubc49\ubc4a\ubc4b\ubc4c\ubc4d\ubc4e\ubc4f\ubc50\ubc51\ubc52\ubc53\ubc54\ubc55\ubc56\ubc57\ubc58\ubc59\ubc5a\ubc5b\ubc5c\ubc5d\ubc5e\ubc5f\ubc60\ubc61\ubc62\ubc63\ubc64\ubc65\ubc66\ubc67\ubc68\ubc69\ubc6a\ubc6b\ubc6c\ubc6d\ubc6e\ubc6f\ubc70\ubc71\ubc72\ubc73\ubc74\ubc75\ubc76\ubc77\ubc78\ubc79\ubc7a\ubc7b\ubc7c\ubc7d\ubc7e\ubc7f\ubc80\ubc81\ubc82\ubc83\ubc84\ubc85\ubc86\ubc87\ubc88\ubc89\ubc8a\ubc8b\ubc8c\ubc8d\ubc8e\ubc8f\ubc90\ubc91\ubc92\ubc93\ubc94\ubc95\ubc96\ubc97\ubc98\ubc99\ubc9a\ubc9b\ubc9c\ubc9d\ubc9e\ubc9f\ubca0\ubca1\ubca2\ubca3\ubca4\ubca5\ubca6\ubca7\ubca8\ubca9\ubcaa\ubcab\ubcac\ubcad\ubcae\ubcaf\ubcb0\ubcb1\ubcb2\ubcb3\ubcb4\ubcb5\ubcb6\ubcb7\ubcb8\ubcb9\ubcba\ubcbb\ubcbc\ubcbd\ubcbe\ubcbf\ubcc0\ubcc1\ubcc2\ubcc3\ubcc4\ubcc5\ubcc6\ubcc7\ubcc8\ubcc9\ubcca\ubccb\ubccc\ubccd\ubcce\ubccf\ubcd0\ubcd1\ubcd2\ubcd3\ubcd4\ubcd5\ubcd6\ubcd7\ubcd8\ubcd9\ubcda\ubcdb\ubcdc\ubcdd\ubcde\ubcdf\ubce0\ubce1\ubce2\ubce3\ubce4\ubce5\ubce6\ubce7\ubce8\ubce9\ubcea\ubceb\ubcec\ubced\ubcee\ubcef\ubcf0\ubcf1\ubcf2\ubcf3\ubcf4\ubcf5\ubcf6\ubcf7\ubcf8\ubcf9\ubcfa\ubcfb\ubcfc\ubcfd\ubcfe\ubcff\ubd00\ubd01\ubd02\ubd03\ubd04\ubd05\ubd06\ubd07\ubd08\ubd09\ubd0a\ubd0b\ubd0c\ubd0d\ubd0e\ubd0f\ubd10\ubd11\ubd12\ubd13\ubd14\ubd15\ubd16\ubd17\ubd18\ubd19\ubd1a\ubd1b\ubd1c\ubd1d\ubd1e\ubd1f\ubd20\ubd21\ubd22\ubd23\ubd24\ubd25\ubd26\ubd27\ubd28\ubd29\ubd2a\ubd2b\ubd2c\ubd2d\ubd2e\ubd2f\ubd30\ubd31\ubd32\ubd33\ubd34\ubd35\ubd36\ubd37\ubd38\ubd39\ubd3a\ubd3b\ubd3c\ubd3d\ubd3e\ubd3f\ubd40\ubd41\ubd42\ubd43\ubd44\ubd45\ubd46\ubd47\ubd48\ubd49\ubd4a\ubd4b\ubd4c\ubd4d\ubd4e\ubd4f\ubd50\ubd51\ubd52\ubd53\ubd54\ubd55\ubd56\ubd57\ubd58\ubd59\ubd5a\ubd5b\ubd5c\ubd5d\ubd5e\ubd5f\ubd60\ubd61\ubd62\ubd63\ubd64\ubd65\ubd66\ubd67\ubd68\ubd69\ubd6a\ubd6b\ubd6c\ubd6d\ubd6e\ubd6f\ubd70\ubd71\ubd72\ubd73\ubd74\ubd75\ubd76\ubd77\ubd78\ubd79\ubd7a\ubd7b\ubd7c\ubd7d\ubd7e\ubd7f\ubd80\ubd81\ubd82\ubd83\ubd84\ubd85\ubd86\ubd87\ubd88\ubd89\ubd8a\ubd8b\ubd8c\ubd8d\ubd8e\ubd8f\ubd90\ubd91\ubd92\ubd93\ubd94\ubd95\ubd96\ubd97\ubd98\ubd99\ubd9a\ubd9b\ubd9c\ubd9d\ubd9e\ubd9f\ubda0\ubda1\ubda2\ubda3\ubda4\ubda5\ubda6\ubda7\ubda8\ubda9\ubdaa\ubdab\ubdac\ubdad\ubdae\ubdaf\ubdb0\ubdb1\ubdb2\ubdb3\ubdb4\ubdb5\ubdb6\ubdb7\ubdb8\ubdb9\ubdba\ubdbb\ubdbc\ubdbd\ubdbe\ubdbf\ubdc0\ubdc1\ubdc2\ubdc3\ubdc4\ubdc5\ubdc6\ubdc7\ubdc8\ubdc9\ubdca\ubdcb\ubdcc\ubdcd\ubdce\ubdcf\ubdd0\ubdd1\ubdd2\ubdd3\ubdd4\ubdd5\ubdd6\ubdd7\ubdd8\ubdd9\ubdda\ubddb\ubddc\ubddd\ubdde\ubddf\ubde0\ubde1\ubde2\ubde3\ubde4\ubde5\ubde6\ubde7\ubde8\ubde9\ubdea\ubdeb\ubdec\ubded\ubdee\ubdef\ubdf0\ubdf1\ubdf2\ubdf3\ubdf4\ubdf5\ubdf6\ubdf7\ubdf8\ubdf9\ubdfa\ubdfb\ubdfc\ubdfd\ubdfe\ubdff\ube00\ube01\ube02\ube03\ube04\ube05\ube06\ube07\ube08\ube09\ube0a\ube0b\ube0c\ube0d\ube0e\ube0f\ube10\ube11\ube12\ube13\ube14\ube15\ube16\ube17\ube18\ube19\ube1a\ube1b\ube1c\ube1d\ube1e\ube1f\ube20\ube21\ube22\ube23\ube24\ube25\ube26\ube27\ube28\ube29\ube2a\ube2b\ube2c\ube2d\ube2e\ube2f\ube30\ube31\ube32\ube33\ube34\ube35\ube36\ube37\ube38\ube39\ube3a\ube3b\ube3c\ube3d\ube3e\ube3f\ube40\ube41\ube42\ube43\ube44\ube45\ube46\ube47\ube48\ube49\ube4a\ube4b\ube4c\ube4d\ube4e\ube4f\ube50\ube51\ube52\ube53\ube54\ube55\ube56\ube57\ube58\ube59\ube5a\ube5b\ube5c\ube5d\ube5e\ube5f\ube60\ube61\ube62\ube63\ube64\ube65\ube66\ube67\ube68\ube69\ube6a\ube6b\ube6c\ube6d\ube6e\ube6f\ube70\ube71\ube72\ube73\ube74\ube75\ube76\ube77\ube78\ube79\ube7a\ube7b\ube7c\ube7d\ube7e\ube7f\ube80\ube81\ube82\ube83\ube84\ube85\ube86\ube87\ube88\ube89\ube8a\ube8b\ube8c\ube8d\ube8e\ube8f\ube90\ube91\ube92\ube93\ube94\ube95\ube96\ube97\ube98\ube99\ube9a\ube9b\ube9c\ube9d\ube9e\ube9f\ubea0\ubea1\ubea2\ubea3\ubea4\ubea5\ubea6\ubea7\ubea8\ubea9\ubeaa\ubeab\ubeac\ubead\ubeae\ubeaf\ubeb0\ubeb1\ubeb2\ubeb3\ubeb4\ubeb5\ubeb6\ubeb7\ubeb8\ubeb9\ubeba\ubebb\ubebc\ubebd\ubebe\ubebf\ubec0\ubec1\ubec2\ubec3\ubec4\ubec5\ubec6\ubec7\ubec8\ubec9\ubeca\ubecb\ubecc\ubecd\ubece\ubecf\ubed0\ubed1\ubed2\ubed3\ubed4\ubed5\ubed6\ubed7\ubed8\ubed9\ubeda\ubedb\ubedc\ubedd\ubede\ubedf\ubee0\ubee1\ubee2\ubee3\ubee4\ubee5\ubee6\ubee7\ubee8\ubee9\ubeea\ubeeb\ubeec\ubeed\ubeee\ubeef\ubef0\ubef1\ubef2\ubef3\ubef4\ubef5\ubef6\ubef7\ubef8\ubef9\ubefa\ubefb\ubefc\ubefd\ubefe\ubeff\ubf00\ubf01\ubf02\ubf03\ubf04\ubf05\ubf06\ubf07\ubf08\ubf09\ubf0a\ubf0b\ubf0c\ubf0d\ubf0e\ubf0f\ubf10\ubf11\ubf12\ubf13\ubf14\ubf15\ubf16\ubf17\ubf18\ubf19\ubf1a\ubf1b\ubf1c\ubf1d\ubf1e\ubf1f\ubf20\ubf21\ubf22\ubf23\ubf24\ubf25\ubf26\ubf27\ubf28\ubf29\ubf2a\ubf2b\ubf2c\ubf2d\ubf2e\ubf2f\ubf30\ubf31\ubf32\ubf33\ubf34\ubf35\ubf36\ubf37\ubf38\ubf39\ubf3a\ubf3b\ubf3c\ubf3d\ubf3e\ubf3f\ubf40\ubf41\ubf42\ubf43\ubf44\ubf45\ubf46\ubf47\ubf48\ubf49\ubf4a\ubf4b\ubf4c\ubf4d\ubf4e\ubf4f\ubf50\ubf51\ubf52\ubf53\ubf54\ubf55\ubf56\ubf57\ubf58\ubf59\ubf5a\ubf5b\ubf5c\ubf5d\ubf5e\ubf5f\ubf60\ubf61\ubf62\ubf63\ubf64\ubf65\ubf66\ubf67\ubf68\ubf69\ubf6a\ubf6b\ubf6c\ubf6d\ubf6e\ubf6f\ubf70\ubf71\ubf72\ubf73\ubf74\ubf75\ubf76\ubf77\ubf78\ubf79\ubf7a\ubf7b\ubf7c\ubf7d\ubf7e\ubf7f\ubf80\ubf81\ubf82\ubf83\ubf84\ubf85\ubf86\ubf87\ubf88\ubf89\ubf8a\ubf8b\ubf8c\ubf8d\ubf8e\ubf8f\ubf90\ubf91\ubf92\ubf93\ubf94\ubf95\ubf96\ubf97\ubf98\ubf99\ubf9a\ubf9b\ubf9c\ubf9d\ubf9e\ubf9f\ubfa0\ubfa1\ubfa2\ubfa3\ubfa4\ubfa5\ubfa6\ubfa7\ubfa8\ubfa9\ubfaa\ubfab\ubfac\ubfad\ubfae\ubfaf\ubfb0\ubfb1\ubfb2\ubfb3\ubfb4\ubfb5\ubfb6\ubfb7\ubfb8\ubfb9\ubfba\ubfbb\ubfbc\ubfbd\ubfbe\ubfbf\ubfc0\ubfc1\ubfc2\ubfc3\ubfc4\ubfc5\ubfc6\ubfc7\ubfc8\ubfc9\ubfca\ubfcb\ubfcc\ubfcd\ubfce\ubfcf\ubfd0\ubfd1\ubfd2\ubfd3\ubfd4\ubfd5\ubfd6\ubfd7\ubfd8\ubfd9\ubfda\ubfdb\ubfdc\ubfdd\ubfde\ubfdf\ubfe0\ubfe1\ubfe2\ubfe3\ubfe4\ubfe5\ubfe6\ubfe7\ubfe8\ubfe9\ubfea\ubfeb\ubfec\ubfed\ubfee\ubfef\ubff0\ubff1\ubff2\ubff3\ubff4\ubff5\ubff6\ubff7\ubff8\ubff9\ubffa\ubffb\ubffc\ubffd\ubffe\ubfff\uc000\uc001\uc002\uc003\uc004\uc005\uc006\uc007\uc008\uc009\uc00a\uc00b\uc00c\uc00d\uc00e\uc00f\uc010\uc011\uc012\uc013\uc014\uc015\uc016\uc017\uc018\uc019\uc01a\uc01b\uc01c\uc01d\uc01e\uc01f\uc020\uc021\uc022\uc023\uc024\uc025\uc026\uc027\uc028\uc029\uc02a\uc02b\uc02c\uc02d\uc02e\uc02f\uc030\uc031\uc032\uc033\uc034\uc035\uc036\uc037\uc038\uc039\uc03a\uc03b\uc03c\uc03d\uc03e\uc03f\uc040\uc041\uc042\uc043\uc044\uc045\uc046\uc047\uc048\uc049\uc04a\uc04b\uc04c\uc04d\uc04e\uc04f\uc050\uc051\uc052\uc053\uc054\uc055\uc056\uc057\uc058\uc059\uc05a\uc05b\uc05c\uc05d\uc05e\uc05f\uc060\uc061\uc062\uc063\uc064\uc065\uc066\uc067\uc068\uc069\uc06a\uc06b\uc06c\uc06d\uc06e\uc06f\uc070\uc071\uc072\uc073\uc074\uc075\uc076\uc077\uc078\uc079\uc07a\uc07b\uc07c\uc07d\uc07e\uc07f\uc080\uc081\uc082\uc083\uc084\uc085\uc086\uc087\uc088\uc089\uc08a\uc08b\uc08c\uc08d\uc08e\uc08f\uc090\uc091\uc092\uc093\uc094\uc095\uc096\uc097\uc098\uc099\uc09a\uc09b\uc09c\uc09d\uc09e\uc09f\uc0a0\uc0a1\uc0a2\uc0a3\uc0a4\uc0a5\uc0a6\uc0a7\uc0a8\uc0a9\uc0aa\uc0ab\uc0ac\uc0ad\uc0ae\uc0af\uc0b0\uc0b1\uc0b2\uc0b3\uc0b4\uc0b5\uc0b6\uc0b7\uc0b8\uc0b9\uc0ba\uc0bb\uc0bc\uc0bd\uc0be\uc0bf\uc0c0\uc0c1\uc0c2\uc0c3\uc0c4\uc0c5\uc0c6\uc0c7\uc0c8\uc0c9\uc0ca\uc0cb\uc0cc\uc0cd\uc0ce\uc0cf\uc0d0\uc0d1\uc0d2\uc0d3\uc0d4\uc0d5\uc0d6\uc0d7\uc0d8\uc0d9\uc0da\uc0db\uc0dc\uc0dd\uc0de\uc0df\uc0e0\uc0e1\uc0e2\uc0e3\uc0e4\uc0e5\uc0e6\uc0e7\uc0e8\uc0e9\uc0ea\uc0eb\uc0ec\uc0ed\uc0ee\uc0ef\uc0f0\uc0f1\uc0f2\uc0f3\uc0f4\uc0f5\uc0f6\uc0f7\uc0f8\uc0f9\uc0fa\uc0fb\uc0fc\uc0fd\uc0fe\uc0ff\uc100\uc101\uc102\uc103\uc104\uc105\uc106\uc107\uc108\uc109\uc10a\uc10b\uc10c\uc10d\uc10e\uc10f\uc110\uc111\uc112\uc113\uc114\uc115\uc116\uc117\uc118\uc119\uc11a\uc11b\uc11c\uc11d\uc11e\uc11f\uc120\uc121\uc122\uc123\uc124\uc125\uc126\uc127\uc128\uc129\uc12a\uc12b\uc12c\uc12d\uc12e\uc12f\uc130\uc131\uc132\uc133\uc134\uc135\uc136\uc137\uc138\uc139\uc13a\uc13b\uc13c\uc13d\uc13e\uc13f\uc140\uc141\uc142\uc143\uc144\uc145\uc146\uc147\uc148\uc149\uc14a\uc14b\uc14c\uc14d\uc14e\uc14f\uc150\uc151\uc152\uc153\uc154\uc155\uc156\uc157\uc158\uc159\uc15a\uc15b\uc15c\uc15d\uc15e\uc15f\uc160\uc161\uc162\uc163\uc164\uc165\uc166\uc167\uc168\uc169\uc16a\uc16b\uc16c\uc16d\uc16e\uc16f\uc170\uc171\uc172\uc173\uc174\uc175\uc176\uc177\uc178\uc179\uc17a\uc17b\uc17c\uc17d\uc17e\uc17f\uc180\uc181\uc182\uc183\uc184\uc185\uc186\uc187\uc188\uc189\uc18a\uc18b\uc18c\uc18d\uc18e\uc18f\uc190\uc191\uc192\uc193\uc194\uc195\uc196\uc197\uc198\uc199\uc19a\uc19b\uc19c\uc19d\uc19e\uc19f\uc1a0\uc1a1\uc1a2\uc1a3\uc1a4\uc1a5\uc1a6\uc1a7\uc1a8\uc1a9\uc1aa\uc1ab\uc1ac\uc1ad\uc1ae\uc1af\uc1b0\uc1b1\uc1b2\uc1b3\uc1b4\uc1b5\uc1b6\uc1b7\uc1b8\uc1b9\uc1ba\uc1bb\uc1bc\uc1bd\uc1be\uc1bf\uc1c0\uc1c1\uc1c2\uc1c3\uc1c4\uc1c5\uc1c6\uc1c7\uc1c8\uc1c9\uc1ca\uc1cb\uc1cc\uc1cd\uc1ce\uc1cf\uc1d0\uc1d1\uc1d2\uc1d3\uc1d4\uc1d5\uc1d6\uc1d7\uc1d8\uc1d9\uc1da\uc1db\uc1dc\uc1dd\uc1de\uc1df\uc1e0\uc1e1\uc1e2\uc1e3\uc1e4\uc1e5\uc1e6\uc1e7\uc1e8\uc1e9\uc1ea\uc1eb\uc1ec\uc1ed\uc1ee\uc1ef\uc1f0\uc1f1\uc1f2\uc1f3\uc1f4\uc1f5\uc1f6\uc1f7\uc1f8\uc1f9\uc1fa\uc1fb\uc1fc\uc1fd\uc1fe\uc1ff\uc200\uc201\uc202\uc203\uc204\uc205\uc206\uc207\uc208\uc209\uc20a\uc20b\uc20c\uc20d\uc20e\uc20f\uc210\uc211\uc212\uc213\uc214\uc215\uc216\uc217\uc218\uc219\uc21a\uc21b\uc21c\uc21d\uc21e\uc21f\uc220\uc221\uc222\uc223\uc224\uc225\uc226\uc227\uc228\uc229\uc22a\uc22b\uc22c\uc22d\uc22e\uc22f\uc230\uc231\uc232\uc233\uc234\uc235\uc236\uc237\uc238\uc239\uc23a\uc23b\uc23c\uc23d\uc23e\uc23f\uc240\uc241\uc242\uc243\uc244\uc245\uc246\uc247\uc248\uc249\uc24a\uc24b\uc24c\uc24d\uc24e\uc24f\uc250\uc251\uc252\uc253\uc254\uc255\uc256\uc257\uc258\uc259\uc25a\uc25b\uc25c\uc25d\uc25e\uc25f\uc260\uc261\uc262\uc263\uc264\uc265\uc266\uc267\uc268\uc269\uc26a\uc26b\uc26c\uc26d\uc26e\uc26f\uc270\uc271\uc272\uc273\uc274\uc275\uc276\uc277\uc278\uc279\uc27a\uc27b\uc27c\uc27d\uc27e\uc27f\uc280\uc281\uc282\uc283\uc284\uc285\uc286\uc287\uc288\uc289\uc28a\uc28b\uc28c\uc28d\uc28e\uc28f\uc290\uc291\uc292\uc293\uc294\uc295\uc296\uc297\uc298\uc299\uc29a\uc29b\uc29c\uc29d\uc29e\uc29f\uc2a0\uc2a1\uc2a2\uc2a3\uc2a4\uc2a5\uc2a6\uc2a7\uc2a8\uc2a9\uc2aa\uc2ab\uc2ac\uc2ad\uc2ae\uc2af\uc2b0\uc2b1\uc2b2\uc2b3\uc2b4\uc2b5\uc2b6\uc2b7\uc2b8\uc2b9\uc2ba\uc2bb\uc2bc\uc2bd\uc2be\uc2bf\uc2c0\uc2c1\uc2c2\uc2c3\uc2c4\uc2c5\uc2c6\uc2c7\uc2c8\uc2c9\uc2ca\uc2cb\uc2cc\uc2cd\uc2ce\uc2cf\uc2d0\uc2d1\uc2d2\uc2d3\uc2d4\uc2d5\uc2d6\uc2d7\uc2d8\uc2d9\uc2da\uc2db\uc2dc\uc2dd\uc2de\uc2df\uc2e0\uc2e1\uc2e2\uc2e3\uc2e4\uc2e5\uc2e6\uc2e7\uc2e8\uc2e9\uc2ea\uc2eb\uc2ec\uc2ed\uc2ee\uc2ef\uc2f0\uc2f1\uc2f2\uc2f3\uc2f4\uc2f5\uc2f6\uc2f7\uc2f8\uc2f9\uc2fa\uc2fb\uc2fc\uc2fd\uc2fe\uc2ff\uc300\uc301\uc302\uc303\uc304\uc305\uc306\uc307\uc308\uc309\uc30a\uc30b\uc30c\uc30d\uc30e\uc30f\uc310\uc311\uc312\uc313\uc314\uc315\uc316\uc317\uc318\uc319\uc31a\uc31b\uc31c\uc31d\uc31e\uc31f\uc320\uc321\uc322\uc323\uc324\uc325\uc326\uc327\uc328\uc329\uc32a\uc32b\uc32c\uc32d\uc32e\uc32f\uc330\uc331\uc332\uc333\uc334\uc335\uc336\uc337\uc338\uc339\uc33a\uc33b\uc33c\uc33d\uc33e\uc33f\uc340\uc341\uc342\uc343\uc344\uc345\uc346\uc347\uc348\uc349\uc34a\uc34b\uc34c\uc34d\uc34e\uc34f\uc350\uc351\uc352\uc353\uc354\uc355\uc356\uc357\uc358\uc359\uc35a\uc35b\uc35c\uc35d\uc35e\uc35f\uc360\uc361\uc362\uc363\uc364\uc365\uc366\uc367\uc368\uc369\uc36a\uc36b\uc36c\uc36d\uc36e\uc36f\uc370\uc371\uc372\uc373\uc374\uc375\uc376\uc377\uc378\uc379\uc37a\uc37b\uc37c\uc37d\uc37e\uc37f\uc380\uc381\uc382\uc383\uc384\uc385\uc386\uc387\uc388\uc389\uc38a\uc38b\uc38c\uc38d\uc38e\uc38f\uc390\uc391\uc392\uc393\uc394\uc395\uc396\uc397\uc398\uc399\uc39a\uc39b\uc39c\uc39d\uc39e\uc39f\uc3a0\uc3a1\uc3a2\uc3a3\uc3a4\uc3a5\uc3a6\uc3a7\uc3a8\uc3a9\uc3aa\uc3ab\uc3ac\uc3ad\uc3ae\uc3af\uc3b0\uc3b1\uc3b2\uc3b3\uc3b4\uc3b5\uc3b6\uc3b7\uc3b8\uc3b9\uc3ba\uc3bb\uc3bc\uc3bd\uc3be\uc3bf\uc3c0\uc3c1\uc3c2\uc3c3\uc3c4\uc3c5\uc3c6\uc3c7\uc3c8\uc3c9\uc3ca\uc3cb\uc3cc\uc3cd\uc3ce\uc3cf\uc3d0\uc3d1\uc3d2\uc3d3\uc3d4\uc3d5\uc3d6\uc3d7\uc3d8\uc3d9\uc3da\uc3db\uc3dc\uc3dd\uc3de\uc3df\uc3e0\uc3e1\uc3e2\uc3e3\uc3e4\uc3e5\uc3e6\uc3e7\uc3e8\uc3e9\uc3ea\uc3eb\uc3ec\uc3ed\uc3ee\uc3ef\uc3f0\uc3f1\uc3f2\uc3f3\uc3f4\uc3f5\uc3f6\uc3f7\uc3f8\uc3f9\uc3fa\uc3fb\uc3fc\uc3fd\uc3fe\uc3ff\uc400\uc401\uc402\uc403\uc404\uc405\uc406\uc407\uc408\uc409\uc40a\uc40b\uc40c\uc40d\uc40e\uc40f\uc410\uc411\uc412\uc413\uc414\uc415\uc416\uc417\uc418\uc419\uc41a\uc41b\uc41c\uc41d\uc41e\uc41f\uc420\uc421\uc422\uc423\uc424\uc425\uc426\uc427\uc428\uc429\uc42a\uc42b\uc42c\uc42d\uc42e\uc42f\uc430\uc431\uc432\uc433\uc434\uc435\uc436\uc437\uc438\uc439\uc43a\uc43b\uc43c\uc43d\uc43e\uc43f\uc440\uc441\uc442\uc443\uc444\uc445\uc446\uc447\uc448\uc449\uc44a\uc44b\uc44c\uc44d\uc44e\uc44f\uc450\uc451\uc452\uc453\uc454\uc455\uc456\uc457\uc458\uc459\uc45a\uc45b\uc45c\uc45d\uc45e\uc45f\uc460\uc461\uc462\uc463\uc464\uc465\uc466\uc467\uc468\uc469\uc46a\uc46b\uc46c\uc46d\uc46e\uc46f\uc470\uc471\uc472\uc473\uc474\uc475\uc476\uc477\uc478\uc479\uc47a\uc47b\uc47c\uc47d\uc47e\uc47f\uc480\uc481\uc482\uc483\uc484\uc485\uc486\uc487\uc488\uc489\uc48a\uc48b\uc48c\uc48d\uc48e\uc48f\uc490\uc491\uc492\uc493\uc494\uc495\uc496\uc497\uc498\uc499\uc49a\uc49b\uc49c\uc49d\uc49e\uc49f\uc4a0\uc4a1\uc4a2\uc4a3\uc4a4\uc4a5\uc4a6\uc4a7\uc4a8\uc4a9\uc4aa\uc4ab\uc4ac\uc4ad\uc4ae\uc4af\uc4b0\uc4b1\uc4b2\uc4b3\uc4b4\uc4b5\uc4b6\uc4b7\uc4b8\uc4b9\uc4ba\uc4bb\uc4bc\uc4bd\uc4be\uc4bf\uc4c0\uc4c1\uc4c2\uc4c3\uc4c4\uc4c5\uc4c6\uc4c7\uc4c8\uc4c9\uc4ca\uc4cb\uc4cc\uc4cd\uc4ce\uc4cf\uc4d0\uc4d1\uc4d2\uc4d3\uc4d4\uc4d5\uc4d6\uc4d7\uc4d8\uc4d9\uc4da\uc4db\uc4dc\uc4dd\uc4de\uc4df\uc4e0\uc4e1\uc4e2\uc4e3\uc4e4\uc4e5\uc4e6\uc4e7\uc4e8\uc4e9\uc4ea\uc4eb\uc4ec\uc4ed\uc4ee\uc4ef\uc4f0\uc4f1\uc4f2\uc4f3\uc4f4\uc4f5\uc4f6\uc4f7\uc4f8\uc4f9\uc4fa\uc4fb\uc4fc\uc4fd\uc4fe\uc4ff\uc500\uc501\uc502\uc503\uc504\uc505\uc506\uc507\uc508\uc509\uc50a\uc50b\uc50c\uc50d\uc50e\uc50f\uc510\uc511\uc512\uc513\uc514\uc515\uc516\uc517\uc518\uc519\uc51a\uc51b\uc51c\uc51d\uc51e\uc51f\uc520\uc521\uc522\uc523\uc524\uc525\uc526\uc527\uc528\uc529\uc52a\uc52b\uc52c\uc52d\uc52e\uc52f\uc530\uc531\uc532\uc533\uc534\uc535\uc536\uc537\uc538\uc539\uc53a\uc53b\uc53c\uc53d\uc53e\uc53f\uc540\uc541\uc542\uc543\uc544\uc545\uc546\uc547\uc548\uc549\uc54a\uc54b\uc54c\uc54d\uc54e\uc54f\uc550\uc551\uc552\uc553\uc554\uc555\uc556\uc557\uc558\uc559\uc55a\uc55b\uc55c\uc55d\uc55e\uc55f\uc560\uc561\uc562\uc563\uc564\uc565\uc566\uc567\uc568\uc569\uc56a\uc56b\uc56c\uc56d\uc56e\uc56f\uc570\uc571\uc572\uc573\uc574\uc575\uc576\uc577\uc578\uc579\uc57a\uc57b\uc57c\uc57d\uc57e\uc57f\uc580\uc581\uc582\uc583\uc584\uc585\uc586\uc587\uc588\uc589\uc58a\uc58b\uc58c\uc58d\uc58e\uc58f\uc590\uc591\uc592\uc593\uc594\uc595\uc596\uc597\uc598\uc599\uc59a\uc59b\uc59c\uc59d\uc59e\uc59f\uc5a0\uc5a1\uc5a2\uc5a3\uc5a4\uc5a5\uc5a6\uc5a7\uc5a8\uc5a9\uc5aa\uc5ab\uc5ac\uc5ad\uc5ae\uc5af\uc5b0\uc5b1\uc5b2\uc5b3\uc5b4\uc5b5\uc5b6\uc5b7\uc5b8\uc5b9\uc5ba\uc5bb\uc5bc\uc5bd\uc5be\uc5bf\uc5c0\uc5c1\uc5c2\uc5c3\uc5c4\uc5c5\uc5c6\uc5c7\uc5c8\uc5c9\uc5ca\uc5cb\uc5cc\uc5cd\uc5ce\uc5cf\uc5d0\uc5d1\uc5d2\uc5d3\uc5d4\uc5d5\uc5d6\uc5d7\uc5d8\uc5d9\uc5da\uc5db\uc5dc\uc5dd\uc5de\uc5df\uc5e0\uc5e1\uc5e2\uc5e3\uc5e4\uc5e5\uc5e6\uc5e7\uc5e8\uc5e9\uc5ea\uc5eb\uc5ec\uc5ed\uc5ee\uc5ef\uc5f0\uc5f1\uc5f2\uc5f3\uc5f4\uc5f5\uc5f6\uc5f7\uc5f8\uc5f9\uc5fa\uc5fb\uc5fc\uc5fd\uc5fe\uc5ff\uc600\uc601\uc602\uc603\uc604\uc605\uc606\uc607\uc608\uc609\uc60a\uc60b\uc60c\uc60d\uc60e\uc60f\uc610\uc611\uc612\uc613\uc614\uc615\uc616\uc617\uc618\uc619\uc61a\uc61b\uc61c\uc61d\uc61e\uc61f\uc620\uc621\uc622\uc623\uc624\uc625\uc626\uc627\uc628\uc629\uc62a\uc62b\uc62c\uc62d\uc62e\uc62f\uc630\uc631\uc632\uc633\uc634\uc635\uc636\uc637\uc638\uc639\uc63a\uc63b\uc63c\uc63d\uc63e\uc63f\uc640\uc641\uc642\uc643\uc644\uc645\uc646\uc647\uc648\uc649\uc64a\uc64b\uc64c\uc64d\uc64e\uc64f\uc650\uc651\uc652\uc653\uc654\uc655\uc656\uc657\uc658\uc659\uc65a\uc65b\uc65c\uc65d\uc65e\uc65f\uc660\uc661\uc662\uc663\uc664\uc665\uc666\uc667\uc668\uc669\uc66a\uc66b\uc66c\uc66d\uc66e\uc66f\uc670\uc671\uc672\uc673\uc674\uc675\uc676\uc677\uc678\uc679\uc67a\uc67b\uc67c\uc67d\uc67e\uc67f\uc680\uc681\uc682\uc683\uc684\uc685\uc686\uc687\uc688\uc689\uc68a\uc68b\uc68c\uc68d\uc68e\uc68f\uc690\uc691\uc692\uc693\uc694\uc695\uc696\uc697\uc698\uc699\uc69a\uc69b\uc69c\uc69d\uc69e\uc69f\uc6a0\uc6a1\uc6a2\uc6a3\uc6a4\uc6a5\uc6a6\uc6a7\uc6a8\uc6a9\uc6aa\uc6ab\uc6ac\uc6ad\uc6ae\uc6af\uc6b0\uc6b1\uc6b2\uc6b3\uc6b4\uc6b5\uc6b6\uc6b7\uc6b8\uc6b9\uc6ba\uc6bb\uc6bc\uc6bd\uc6be\uc6bf\uc6c0\uc6c1\uc6c2\uc6c3\uc6c4\uc6c5\uc6c6\uc6c7\uc6c8\uc6c9\uc6ca\uc6cb\uc6cc\uc6cd\uc6ce\uc6cf\uc6d0\uc6d1\uc6d2\uc6d3\uc6d4\uc6d5\uc6d6\uc6d7\uc6d8\uc6d9\uc6da\uc6db\uc6dc\uc6dd\uc6de\uc6df\uc6e0\uc6e1\uc6e2\uc6e3\uc6e4\uc6e5\uc6e6\uc6e7\uc6e8\uc6e9\uc6ea\uc6eb\uc6ec\uc6ed\uc6ee\uc6ef\uc6f0\uc6f1\uc6f2\uc6f3\uc6f4\uc6f5\uc6f6\uc6f7\uc6f8\uc6f9\uc6fa\uc6fb\uc6fc\uc6fd\uc6fe\uc6ff\uc700\uc701\uc702\uc703\uc704\uc705\uc706\uc707\uc708\uc709\uc70a\uc70b\uc70c\uc70d\uc70e\uc70f\uc710\uc711\uc712\uc713\uc714\uc715\uc716\uc717\uc718\uc719\uc71a\uc71b\uc71c\uc71d\uc71e\uc71f\uc720\uc721\uc722\uc723\uc724\uc725\uc726\uc727\uc728\uc729\uc72a\uc72b\uc72c\uc72d\uc72e\uc72f\uc730\uc731\uc732\uc733\uc734\uc735\uc736\uc737\uc738\uc739\uc73a\uc73b\uc73c\uc73d\uc73e\uc73f\uc740\uc741\uc742\uc743\uc744\uc745\uc746\uc747\uc748\uc749\uc74a\uc74b\uc74c\uc74d\uc74e\uc74f\uc750\uc751\uc752\uc753\uc754\uc755\uc756\uc757\uc758\uc759\uc75a\uc75b\uc75c\uc75d\uc75e\uc75f\uc760\uc761\uc762\uc763\uc764\uc765\uc766\uc767\uc768\uc769\uc76a\uc76b\uc76c\uc76d\uc76e\uc76f\uc770\uc771\uc772\uc773\uc774\uc775\uc776\uc777\uc778\uc779\uc77a\uc77b\uc77c\uc77d\uc77e\uc77f\uc780\uc781\uc782\uc783\uc784\uc785\uc786\uc787\uc788\uc789\uc78a\uc78b\uc78c\uc78d\uc78e\uc78f\uc790\uc791\uc792\uc793\uc794\uc795\uc796\uc797\uc798\uc799\uc79a\uc79b\uc79c\uc79d\uc79e\uc79f\uc7a0\uc7a1\uc7a2\uc7a3\uc7a4\uc7a5\uc7a6\uc7a7\uc7a8\uc7a9\uc7aa\uc7ab\uc7ac\uc7ad\uc7ae\uc7af\uc7b0\uc7b1\uc7b2\uc7b3\uc7b4\uc7b5\uc7b6\uc7b7\uc7b8\uc7b9\uc7ba\uc7bb\uc7bc\uc7bd\uc7be\uc7bf\uc7c0\uc7c1\uc7c2\uc7c3\uc7c4\uc7c5\uc7c6\uc7c7\uc7c8\uc7c9\uc7ca\uc7cb\uc7cc\uc7cd\uc7ce\uc7cf\uc7d0\uc7d1\uc7d2\uc7d3\uc7d4\uc7d5\uc7d6\uc7d7\uc7d8\uc7d9\uc7da\uc7db\uc7dc\uc7dd\uc7de\uc7df\uc7e0\uc7e1\uc7e2\uc7e3\uc7e4\uc7e5\uc7e6\uc7e7\uc7e8\uc7e9\uc7ea\uc7eb\uc7ec\uc7ed\uc7ee\uc7ef\uc7f0\uc7f1\uc7f2\uc7f3\uc7f4\uc7f5\uc7f6\uc7f7\uc7f8\uc7f9\uc7fa\uc7fb\uc7fc\uc7fd\uc7fe\uc7ff\uc800\uc801\uc802\uc803\uc804\uc805\uc806\uc807\uc808\uc809\uc80a\uc80b\uc80c\uc80d\uc80e\uc80f\uc810\uc811\uc812\uc813\uc814\uc815\uc816\uc817\uc818\uc819\uc81a\uc81b\uc81c\uc81d\uc81e\uc81f\uc820\uc821\uc822\uc823\uc824\uc825\uc826\uc827\uc828\uc829\uc82a\uc82b\uc82c\uc82d\uc82e\uc82f\uc830\uc831\uc832\uc833\uc834\uc835\uc836\uc837\uc838\uc839\uc83a\uc83b\uc83c\uc83d\uc83e\uc83f\uc840\uc841\uc842\uc843\uc844\uc845\uc846\uc847\uc848\uc849\uc84a\uc84b\uc84c\uc84d\uc84e\uc84f\uc850\uc851\uc852\uc853\uc854\uc855\uc856\uc857\uc858\uc859\uc85a\uc85b\uc85c\uc85d\uc85e\uc85f\uc860\uc861\uc862\uc863\uc864\uc865\uc866\uc867\uc868\uc869\uc86a\uc86b\uc86c\uc86d\uc86e\uc86f\uc870\uc871\uc872\uc873\uc874\uc875\uc876\uc877\uc878\uc879\uc87a\uc87b\uc87c\uc87d\uc87e\uc87f\uc880\uc881\uc882\uc883\uc884\uc885\uc886\uc887\uc888\uc889\uc88a\uc88b\uc88c\uc88d\uc88e\uc88f\uc890\uc891\uc892\uc893\uc894\uc895\uc896\uc897\uc898\uc899\uc89a\uc89b\uc89c\uc89d\uc89e\uc89f\uc8a0\uc8a1\uc8a2\uc8a3\uc8a4\uc8a5\uc8a6\uc8a7\uc8a8\uc8a9\uc8aa\uc8ab\uc8ac\uc8ad\uc8ae\uc8af\uc8b0\uc8b1\uc8b2\uc8b3\uc8b4\uc8b5\uc8b6\uc8b7\uc8b8\uc8b9\uc8ba\uc8bb\uc8bc\uc8bd\uc8be\uc8bf\uc8c0\uc8c1\uc8c2\uc8c3\uc8c4\uc8c5\uc8c6\uc8c7\uc8c8\uc8c9\uc8ca\uc8cb\uc8cc\uc8cd\uc8ce\uc8cf\uc8d0\uc8d1\uc8d2\uc8d3\uc8d4\uc8d5\uc8d6\uc8d7\uc8d8\uc8d9\uc8da\uc8db\uc8dc\uc8dd\uc8de\uc8df\uc8e0\uc8e1\uc8e2\uc8e3\uc8e4\uc8e5\uc8e6\uc8e7\uc8e8\uc8e9\uc8ea\uc8eb\uc8ec\uc8ed\uc8ee\uc8ef\uc8f0\uc8f1\uc8f2\uc8f3\uc8f4\uc8f5\uc8f6\uc8f7\uc8f8\uc8f9\uc8fa\uc8fb\uc8fc\uc8fd\uc8fe\uc8ff\uc900\uc901\uc902\uc903\uc904\uc905\uc906\uc907\uc908\uc909\uc90a\uc90b\uc90c\uc90d\uc90e\uc90f\uc910\uc911\uc912\uc913\uc914\uc915\uc916\uc917\uc918\uc919\uc91a\uc91b\uc91c\uc91d\uc91e\uc91f\uc920\uc921\uc922\uc923\uc924\uc925\uc926\uc927\uc928\uc929\uc92a\uc92b\uc92c\uc92d\uc92e\uc92f\uc930\uc931\uc932\uc933\uc934\uc935\uc936\uc937\uc938\uc939\uc93a\uc93b\uc93c\uc93d\uc93e\uc93f\uc940\uc941\uc942\uc943\uc944\uc945\uc946\uc947\uc948\uc949\uc94a\uc94b\uc94c\uc94d\uc94e\uc94f\uc950\uc951\uc952\uc953\uc954\uc955\uc956\uc957\uc958\uc959\uc95a\uc95b\uc95c\uc95d\uc95e\uc95f\uc960\uc961\uc962\uc963\uc964\uc965\uc966\uc967\uc968\uc969\uc96a\uc96b\uc96c\uc96d\uc96e\uc96f\uc970\uc971\uc972\uc973\uc974\uc975\uc976\uc977\uc978\uc979\uc97a\uc97b\uc97c\uc97d\uc97e\uc97f\uc980\uc981\uc982\uc983\uc984\uc985\uc986\uc987\uc988\uc989\uc98a\uc98b\uc98c\uc98d\uc98e\uc98f\uc990\uc991\uc992\uc993\uc994\uc995\uc996\uc997\uc998\uc999\uc99a\uc99b\uc99c\uc99d\uc99e\uc99f\uc9a0\uc9a1\uc9a2\uc9a3\uc9a4\uc9a5\uc9a6\uc9a7\uc9a8\uc9a9\uc9aa\uc9ab\uc9ac\uc9ad\uc9ae\uc9af\uc9b0\uc9b1\uc9b2\uc9b3\uc9b4\uc9b5\uc9b6\uc9b7\uc9b8\uc9b9\uc9ba\uc9bb\uc9bc\uc9bd\uc9be\uc9bf\uc9c0\uc9c1\uc9c2\uc9c3\uc9c4\uc9c5\uc9c6\uc9c7\uc9c8\uc9c9\uc9ca\uc9cb\uc9cc\uc9cd\uc9ce\uc9cf\uc9d0\uc9d1\uc9d2\uc9d3\uc9d4\uc9d5\uc9d6\uc9d7\uc9d8\uc9d9\uc9da\uc9db\uc9dc\uc9dd\uc9de\uc9df\uc9e0\uc9e1\uc9e2\uc9e3\uc9e4\uc9e5\uc9e6\uc9e7\uc9e8\uc9e9\uc9ea\uc9eb\uc9ec\uc9ed\uc9ee\uc9ef\uc9f0\uc9f1\uc9f2\uc9f3\uc9f4\uc9f5\uc9f6\uc9f7\uc9f8\uc9f9\uc9fa\uc9fb\uc9fc\uc9fd\uc9fe\uc9ff\uca00\uca01\uca02\uca03\uca04\uca05\uca06\uca07\uca08\uca09\uca0a\uca0b\uca0c\uca0d\uca0e\uca0f\uca10\uca11\uca12\uca13\uca14\uca15\uca16\uca17\uca18\uca19\uca1a\uca1b\uca1c\uca1d\uca1e\uca1f\uca20\uca21\uca22\uca23\uca24\uca25\uca26\uca27\uca28\uca29\uca2a\uca2b\uca2c\uca2d\uca2e\uca2f\uca30\uca31\uca32\uca33\uca34\uca35\uca36\uca37\uca38\uca39\uca3a\uca3b\uca3c\uca3d\uca3e\uca3f\uca40\uca41\uca42\uca43\uca44\uca45\uca46\uca47\uca48\uca49\uca4a\uca4b\uca4c\uca4d\uca4e\uca4f\uca50\uca51\uca52\uca53\uca54\uca55\uca56\uca57\uca58\uca59\uca5a\uca5b\uca5c\uca5d\uca5e\uca5f\uca60\uca61\uca62\uca63\uca64\uca65\uca66\uca67\uca68\uca69\uca6a\uca6b\uca6c\uca6d\uca6e\uca6f\uca70\uca71\uca72\uca73\uca74\uca75\uca76\uca77\uca78\uca79\uca7a\uca7b\uca7c\uca7d\uca7e\uca7f\uca80\uca81\uca82\uca83\uca84\uca85\uca86\uca87\uca88\uca89\uca8a\uca8b\uca8c\uca8d\uca8e\uca8f\uca90\uca91\uca92\uca93\uca94\uca95\uca96\uca97\uca98\uca99\uca9a\uca9b\uca9c\uca9d\uca9e\uca9f\ucaa0\ucaa1\ucaa2\ucaa3\ucaa4\ucaa5\ucaa6\ucaa7\ucaa8\ucaa9\ucaaa\ucaab\ucaac\ucaad\ucaae\ucaaf\ucab0\ucab1\ucab2\ucab3\ucab4\ucab5\ucab6\ucab7\ucab8\ucab9\ucaba\ucabb\ucabc\ucabd\ucabe\ucabf\ucac0\ucac1\ucac2\ucac3\ucac4\ucac5\ucac6\ucac7\ucac8\ucac9\ucaca\ucacb\ucacc\ucacd\ucace\ucacf\ucad0\ucad1\ucad2\ucad3\ucad4\ucad5\ucad6\ucad7\ucad8\ucad9\ucada\ucadb\ucadc\ucadd\ucade\ucadf\ucae0\ucae1\ucae2\ucae3\ucae4\ucae5\ucae6\ucae7\ucae8\ucae9\ucaea\ucaeb\ucaec\ucaed\ucaee\ucaef\ucaf0\ucaf1\ucaf2\ucaf3\ucaf4\ucaf5\ucaf6\ucaf7\ucaf8\ucaf9\ucafa\ucafb\ucafc\ucafd\ucafe\ucaff\ucb00\ucb01\ucb02\ucb03\ucb04\ucb05\ucb06\ucb07\ucb08\ucb09\ucb0a\ucb0b\ucb0c\ucb0d\ucb0e\ucb0f\ucb10\ucb11\ucb12\ucb13\ucb14\ucb15\ucb16\ucb17\ucb18\ucb19\ucb1a\ucb1b\ucb1c\ucb1d\ucb1e\ucb1f\ucb20\ucb21\ucb22\ucb23\ucb24\ucb25\ucb26\ucb27\ucb28\ucb29\ucb2a\ucb2b\ucb2c\ucb2d\ucb2e\ucb2f\ucb30\ucb31\ucb32\ucb33\ucb34\ucb35\ucb36\ucb37\ucb38\ucb39\ucb3a\ucb3b\ucb3c\ucb3d\ucb3e\ucb3f\ucb40\ucb41\ucb42\ucb43\ucb44\ucb45\ucb46\ucb47\ucb48\ucb49\ucb4a\ucb4b\ucb4c\ucb4d\ucb4e\ucb4f\ucb50\ucb51\ucb52\ucb53\ucb54\ucb55\ucb56\ucb57\ucb58\ucb59\ucb5a\ucb5b\ucb5c\ucb5d\ucb5e\ucb5f\ucb60\ucb61\ucb62\ucb63\ucb64\ucb65\ucb66\ucb67\ucb68\ucb69\ucb6a\ucb6b\ucb6c\ucb6d\ucb6e\ucb6f\ucb70\ucb71\ucb72\ucb73\ucb74\ucb75\ucb76\ucb77\ucb78\ucb79\ucb7a\ucb7b\ucb7c\ucb7d\ucb7e\ucb7f\ucb80\ucb81\ucb82\ucb83\ucb84\ucb85\ucb86\ucb87\ucb88\ucb89\ucb8a\ucb8b\ucb8c\ucb8d\ucb8e\ucb8f\ucb90\ucb91\ucb92\ucb93\ucb94\ucb95\ucb96\ucb97\ucb98\ucb99\ucb9a\ucb9b\ucb9c\ucb9d\ucb9e\ucb9f\ucba0\ucba1\ucba2\ucba3\ucba4\ucba5\ucba6\ucba7\ucba8\ucba9\ucbaa\ucbab\ucbac\ucbad\ucbae\ucbaf\ucbb0\ucbb1\ucbb2\ucbb3\ucbb4\ucbb5\ucbb6\ucbb7\ucbb8\ucbb9\ucbba\ucbbb\ucbbc\ucbbd\ucbbe\ucbbf\ucbc0\ucbc1\ucbc2\ucbc3\ucbc4\ucbc5\ucbc6\ucbc7\ucbc8\ucbc9\ucbca\ucbcb\ucbcc\ucbcd\ucbce\ucbcf\ucbd0\ucbd1\ucbd2\ucbd3\ucbd4\ucbd5\ucbd6\ucbd7\ucbd8\ucbd9\ucbda\ucbdb\ucbdc\ucbdd\ucbde\ucbdf\ucbe0\ucbe1\ucbe2\ucbe3\ucbe4\ucbe5\ucbe6\ucbe7\ucbe8\ucbe9\ucbea\ucbeb\ucbec\ucbed\ucbee\ucbef\ucbf0\ucbf1\ucbf2\ucbf3\ucbf4\ucbf5\ucbf6\ucbf7\ucbf8\ucbf9\ucbfa\ucbfb\ucbfc\ucbfd\ucbfe\ucbff\ucc00\ucc01\ucc02\ucc03\ucc04\ucc05\ucc06\ucc07\ucc08\ucc09\ucc0a\ucc0b\ucc0c\ucc0d\ucc0e\ucc0f\ucc10\ucc11\ucc12\ucc13\ucc14\ucc15\ucc16\ucc17\ucc18\ucc19\ucc1a\ucc1b\ucc1c\ucc1d\ucc1e\ucc1f\ucc20\ucc21\ucc22\ucc23\ucc24\ucc25\ucc26\ucc27\ucc28\ucc29\ucc2a\ucc2b\ucc2c\ucc2d\ucc2e\ucc2f\ucc30\ucc31\ucc32\ucc33\ucc34\ucc35\ucc36\ucc37\ucc38\ucc39\ucc3a\ucc3b\ucc3c\ucc3d\ucc3e\ucc3f\ucc40\ucc41\ucc42\ucc43\ucc44\ucc45\ucc46\ucc47\ucc48\ucc49\ucc4a\ucc4b\ucc4c\ucc4d\ucc4e\ucc4f\ucc50\ucc51\ucc52\ucc53\ucc54\ucc55\ucc56\ucc57\ucc58\ucc59\ucc5a\ucc5b\ucc5c\ucc5d\ucc5e\ucc5f\ucc60\ucc61\ucc62\ucc63\ucc64\ucc65\ucc66\ucc67\ucc68\ucc69\ucc6a\ucc6b\ucc6c\ucc6d\ucc6e\ucc6f\ucc70\ucc71\ucc72\ucc73\ucc74\ucc75\ucc76\ucc77\ucc78\ucc79\ucc7a\ucc7b\ucc7c\ucc7d\ucc7e\ucc7f\ucc80\ucc81\ucc82\ucc83\ucc84\ucc85\ucc86\ucc87\ucc88\ucc89\ucc8a\ucc8b\ucc8c\ucc8d\ucc8e\ucc8f\ucc90\ucc91\ucc92\ucc93\ucc94\ucc95\ucc96\ucc97\ucc98\ucc99\ucc9a\ucc9b\ucc9c\ucc9d\ucc9e\ucc9f\ucca0\ucca1\ucca2\ucca3\ucca4\ucca5\ucca6\ucca7\ucca8\ucca9\uccaa\uccab\uccac\uccad\uccae\uccaf\uccb0\uccb1\uccb2\uccb3\uccb4\uccb5\uccb6\uccb7\uccb8\uccb9\uccba\uccbb\uccbc\uccbd\uccbe\uccbf\uccc0\uccc1\uccc2\uccc3\uccc4\uccc5\uccc6\uccc7\uccc8\uccc9\uccca\ucccb\ucccc\ucccd\uccce\ucccf\uccd0\uccd1\uccd2\uccd3\uccd4\uccd5\uccd6\uccd7\uccd8\uccd9\uccda\uccdb\uccdc\uccdd\uccde\uccdf\ucce0\ucce1\ucce2\ucce3\ucce4\ucce5\ucce6\ucce7\ucce8\ucce9\uccea\ucceb\uccec\ucced\uccee\uccef\uccf0\uccf1\uccf2\uccf3\uccf4\uccf5\uccf6\uccf7\uccf8\uccf9\uccfa\uccfb\uccfc\uccfd\uccfe\uccff\ucd00\ucd01\ucd02\ucd03\ucd04\ucd05\ucd06\ucd07\ucd08\ucd09\ucd0a\ucd0b\ucd0c\ucd0d\ucd0e\ucd0f\ucd10\ucd11\ucd12\ucd13\ucd14\ucd15\ucd16\ucd17\ucd18\ucd19\ucd1a\ucd1b\ucd1c\ucd1d\ucd1e\ucd1f\ucd20\ucd21\ucd22\ucd23\ucd24\ucd25\ucd26\ucd27\ucd28\ucd29\ucd2a\ucd2b\ucd2c\ucd2d\ucd2e\ucd2f\ucd30\ucd31\ucd32\ucd33\ucd34\ucd35\ucd36\ucd37\ucd38\ucd39\ucd3a\ucd3b\ucd3c\ucd3d\ucd3e\ucd3f\ucd40\ucd41\ucd42\ucd43\ucd44\ucd45\ucd46\ucd47\ucd48\ucd49\ucd4a\ucd4b\ucd4c\ucd4d\ucd4e\ucd4f\ucd50\ucd51\ucd52\ucd53\ucd54\ucd55\ucd56\ucd57\ucd58\ucd59\ucd5a\ucd5b\ucd5c\ucd5d\ucd5e\ucd5f\ucd60\ucd61\ucd62\ucd63\ucd64\ucd65\ucd66\ucd67\ucd68\ucd69\ucd6a\ucd6b\ucd6c\ucd6d\ucd6e\ucd6f\ucd70\ucd71\ucd72\ucd73\ucd74\ucd75\ucd76\ucd77\ucd78\ucd79\ucd7a\ucd7b\ucd7c\ucd7d\ucd7e\ucd7f\ucd80\ucd81\ucd82\ucd83\ucd84\ucd85\ucd86\ucd87\ucd88\ucd89\ucd8a\ucd8b\ucd8c\ucd8d\ucd8e\ucd8f\ucd90\ucd91\ucd92\ucd93\ucd94\ucd95\ucd96\ucd97\ucd98\ucd99\ucd9a\ucd9b\ucd9c\ucd9d\ucd9e\ucd9f\ucda0\ucda1\ucda2\ucda3\ucda4\ucda5\ucda6\ucda7\ucda8\ucda9\ucdaa\ucdab\ucdac\ucdad\ucdae\ucdaf\ucdb0\ucdb1\ucdb2\ucdb3\ucdb4\ucdb5\ucdb6\ucdb7\ucdb8\ucdb9\ucdba\ucdbb\ucdbc\ucdbd\ucdbe\ucdbf\ucdc0\ucdc1\ucdc2\ucdc3\ucdc4\ucdc5\ucdc6\ucdc7\ucdc8\ucdc9\ucdca\ucdcb\ucdcc\ucdcd\ucdce\ucdcf\ucdd0\ucdd1\ucdd2\ucdd3\ucdd4\ucdd5\ucdd6\ucdd7\ucdd8\ucdd9\ucdda\ucddb\ucddc\ucddd\ucdde\ucddf\ucde0\ucde1\ucde2\ucde3\ucde4\ucde5\ucde6\ucde7\ucde8\ucde9\ucdea\ucdeb\ucdec\ucded\ucdee\ucdef\ucdf0\ucdf1\ucdf2\ucdf3\ucdf4\ucdf5\ucdf6\ucdf7\ucdf8\ucdf9\ucdfa\ucdfb\ucdfc\ucdfd\ucdfe\ucdff\uce00\uce01\uce02\uce03\uce04\uce05\uce06\uce07\uce08\uce09\uce0a\uce0b\uce0c\uce0d\uce0e\uce0f\uce10\uce11\uce12\uce13\uce14\uce15\uce16\uce17\uce18\uce19\uce1a\uce1b\uce1c\uce1d\uce1e\uce1f\uce20\uce21\uce22\uce23\uce24\uce25\uce26\uce27\uce28\uce29\uce2a\uce2b\uce2c\uce2d\uce2e\uce2f\uce30\uce31\uce32\uce33\uce34\uce35\uce36\uce37\uce38\uce39\uce3a\uce3b\uce3c\uce3d\uce3e\uce3f\uce40\uce41\uce42\uce43\uce44\uce45\uce46\uce47\uce48\uce49\uce4a\uce4b\uce4c\uce4d\uce4e\uce4f\uce50\uce51\uce52\uce53\uce54\uce55\uce56\uce57\uce58\uce59\uce5a\uce5b\uce5c\uce5d\uce5e\uce5f\uce60\uce61\uce62\uce63\uce64\uce65\uce66\uce67\uce68\uce69\uce6a\uce6b\uce6c\uce6d\uce6e\uce6f\uce70\uce71\uce72\uce73\uce74\uce75\uce76\uce77\uce78\uce79\uce7a\uce7b\uce7c\uce7d\uce7e\uce7f\uce80\uce81\uce82\uce83\uce84\uce85\uce86\uce87\uce88\uce89\uce8a\uce8b\uce8c\uce8d\uce8e\uce8f\uce90\uce91\uce92\uce93\uce94\uce95\uce96\uce97\uce98\uce99\uce9a\uce9b\uce9c\uce9d\uce9e\uce9f\ucea0\ucea1\ucea2\ucea3\ucea4\ucea5\ucea6\ucea7\ucea8\ucea9\uceaa\uceab\uceac\ucead\uceae\uceaf\uceb0\uceb1\uceb2\uceb3\uceb4\uceb5\uceb6\uceb7\uceb8\uceb9\uceba\ucebb\ucebc\ucebd\ucebe\ucebf\ucec0\ucec1\ucec2\ucec3\ucec4\ucec5\ucec6\ucec7\ucec8\ucec9\uceca\ucecb\ucecc\ucecd\ucece\ucecf\uced0\uced1\uced2\uced3\uced4\uced5\uced6\uced7\uced8\uced9\uceda\ucedb\ucedc\ucedd\ucede\ucedf\ucee0\ucee1\ucee2\ucee3\ucee4\ucee5\ucee6\ucee7\ucee8\ucee9\uceea\uceeb\uceec\uceed\uceee\uceef\ucef0\ucef1\ucef2\ucef3\ucef4\ucef5\ucef6\ucef7\ucef8\ucef9\ucefa\ucefb\ucefc\ucefd\ucefe\uceff\ucf00\ucf01\ucf02\ucf03\ucf04\ucf05\ucf06\ucf07\ucf08\ucf09\ucf0a\ucf0b\ucf0c\ucf0d\ucf0e\ucf0f\ucf10\ucf11\ucf12\ucf13\ucf14\ucf15\ucf16\ucf17\ucf18\ucf19\ucf1a\ucf1b\ucf1c\ucf1d\ucf1e\ucf1f\ucf20\ucf21\ucf22\ucf23\ucf24\ucf25\ucf26\ucf27\ucf28\ucf29\ucf2a\ucf2b\ucf2c\ucf2d\ucf2e\ucf2f\ucf30\ucf31\ucf32\ucf33\ucf34\ucf35\ucf36\ucf37\ucf38\ucf39\ucf3a\ucf3b\ucf3c\ucf3d\ucf3e\ucf3f\ucf40\ucf41\ucf42\ucf43\ucf44\ucf45\ucf46\ucf47\ucf48\ucf49\ucf4a\ucf4b\ucf4c\ucf4d\ucf4e\ucf4f\ucf50\ucf51\ucf52\ucf53\ucf54\ucf55\ucf56\ucf57\ucf58\ucf59\ucf5a\ucf5b\ucf5c\ucf5d\ucf5e\ucf5f\ucf60\ucf61\ucf62\ucf63\ucf64\ucf65\ucf66\ucf67\ucf68\ucf69\ucf6a\ucf6b\ucf6c\ucf6d\ucf6e\ucf6f\ucf70\ucf71\ucf72\ucf73\ucf74\ucf75\ucf76\ucf77\ucf78\ucf79\ucf7a\ucf7b\ucf7c\ucf7d\ucf7e\ucf7f\ucf80\ucf81\ucf82\ucf83\ucf84\ucf85\ucf86\ucf87\ucf88\ucf89\ucf8a\ucf8b\ucf8c\ucf8d\ucf8e\ucf8f\ucf90\ucf91\ucf92\ucf93\ucf94\ucf95\ucf96\ucf97\ucf98\ucf99\ucf9a\ucf9b\ucf9c\ucf9d\ucf9e\ucf9f\ucfa0\ucfa1\ucfa2\ucfa3\ucfa4\ucfa5\ucfa6\ucfa7\ucfa8\ucfa9\ucfaa\ucfab\ucfac\ucfad\ucfae\ucfaf\ucfb0\ucfb1\ucfb2\ucfb3\ucfb4\ucfb5\ucfb6\ucfb7\ucfb8\ucfb9\ucfba\ucfbb\ucfbc\ucfbd\ucfbe\ucfbf\ucfc0\ucfc1\ucfc2\ucfc3\ucfc4\ucfc5\ucfc6\ucfc7\ucfc8\ucfc9\ucfca\ucfcb\ucfcc\ucfcd\ucfce\ucfcf\ucfd0\ucfd1\ucfd2\ucfd3\ucfd4\ucfd5\ucfd6\ucfd7\ucfd8\ucfd9\ucfda\ucfdb\ucfdc\ucfdd\ucfde\ucfdf\ucfe0\ucfe1\ucfe2\ucfe3\ucfe4\ucfe5\ucfe6\ucfe7\ucfe8\ucfe9\ucfea\ucfeb\ucfec\ucfed\ucfee\ucfef\ucff0\ucff1\ucff2\ucff3\ucff4\ucff5\ucff6\ucff7\ucff8\ucff9\ucffa\ucffb\ucffc\ucffd\ucffe\ucfff\ud000\ud001\ud002\ud003\ud004\ud005\ud006\ud007\ud008\ud009\ud00a\ud00b\ud00c\ud00d\ud00e\ud00f\ud010\ud011\ud012\ud013\ud014\ud015\ud016\ud017\ud018\ud019\ud01a\ud01b\ud01c\ud01d\ud01e\ud01f\ud020\ud021\ud022\ud023\ud024\ud025\ud026\ud027\ud028\ud029\ud02a\ud02b\ud02c\ud02d\ud02e\ud02f\ud030\ud031\ud032\ud033\ud034\ud035\ud036\ud037\ud038\ud039\ud03a\ud03b\ud03c\ud03d\ud03e\ud03f\ud040\ud041\ud042\ud043\ud044\ud045\ud046\ud047\ud048\ud049\ud04a\ud04b\ud04c\ud04d\ud04e\ud04f\ud050\ud051\ud052\ud053\ud054\ud055\ud056\ud057\ud058\ud059\ud05a\ud05b\ud05c\ud05d\ud05e\ud05f\ud060\ud061\ud062\ud063\ud064\ud065\ud066\ud067\ud068\ud069\ud06a\ud06b\ud06c\ud06d\ud06e\ud06f\ud070\ud071\ud072\ud073\ud074\ud075\ud076\ud077\ud078\ud079\ud07a\ud07b\ud07c\ud07d\ud07e\ud07f\ud080\ud081\ud082\ud083\ud084\ud085\ud086\ud087\ud088\ud089\ud08a\ud08b\ud08c\ud08d\ud08e\ud08f\ud090\ud091\ud092\ud093\ud094\ud095\ud096\ud097\ud098\ud099\ud09a\ud09b\ud09c\ud09d\ud09e\ud09f\ud0a0\ud0a1\ud0a2\ud0a3\ud0a4\ud0a5\ud0a6\ud0a7\ud0a8\ud0a9\ud0aa\ud0ab\ud0ac\ud0ad\ud0ae\ud0af\ud0b0\ud0b1\ud0b2\ud0b3\ud0b4\ud0b5\ud0b6\ud0b7\ud0b8\ud0b9\ud0ba\ud0bb\ud0bc\ud0bd\ud0be\ud0bf\ud0c0\ud0c1\ud0c2\ud0c3\ud0c4\ud0c5\ud0c6\ud0c7\ud0c8\ud0c9\ud0ca\ud0cb\ud0cc\ud0cd\ud0ce\ud0cf\ud0d0\ud0d1\ud0d2\ud0d3\ud0d4\ud0d5\ud0d6\ud0d7\ud0d8\ud0d9\ud0da\ud0db\ud0dc\ud0dd\ud0de\ud0df\ud0e0\ud0e1\ud0e2\ud0e3\ud0e4\ud0e5\ud0e6\ud0e7\ud0e8\ud0e9\ud0ea\ud0eb\ud0ec\ud0ed\ud0ee\ud0ef\ud0f0\ud0f1\ud0f2\ud0f3\ud0f4\ud0f5\ud0f6\ud0f7\ud0f8\ud0f9\ud0fa\ud0fb\ud0fc\ud0fd\ud0fe\ud0ff\ud100\ud101\ud102\ud103\ud104\ud105\ud106\ud107\ud108\ud109\ud10a\ud10b\ud10c\ud10d\ud10e\ud10f\ud110\ud111\ud112\ud113\ud114\ud115\ud116\ud117\ud118\ud119\ud11a\ud11b\ud11c\ud11d\ud11e\ud11f\ud120\ud121\ud122\ud123\ud124\ud125\ud126\ud127\ud128\ud129\ud12a\ud12b\ud12c\ud12d\ud12e\ud12f\ud130\ud131\ud132\ud133\ud134\ud135\ud136\ud137\ud138\ud139\ud13a\ud13b\ud13c\ud13d\ud13e\ud13f\ud140\ud141\ud142\ud143\ud144\ud145\ud146\ud147\ud148\ud149\ud14a\ud14b\ud14c\ud14d\ud14e\ud14f\ud150\ud151\ud152\ud153\ud154\ud155\ud156\ud157\ud158\ud159\ud15a\ud15b\ud15c\ud15d\ud15e\ud15f\ud160\ud161\ud162\ud163\ud164\ud165\ud166\ud167\ud168\ud169\ud16a\ud16b\ud16c\ud16d\ud16e\ud16f\ud170\ud171\ud172\ud173\ud174\ud175\ud176\ud177\ud178\ud179\ud17a\ud17b\ud17c\ud17d\ud17e\ud17f\ud180\ud181\ud182\ud183\ud184\ud185\ud186\ud187\ud188\ud189\ud18a\ud18b\ud18c\ud18d\ud18e\ud18f\ud190\ud191\ud192\ud193\ud194\ud195\ud196\ud197\ud198\ud199\ud19a\ud19b\ud19c\ud19d\ud19e\ud19f\ud1a0\ud1a1\ud1a2\ud1a3\ud1a4\ud1a5\ud1a6\ud1a7\ud1a8\ud1a9\ud1aa\ud1ab\ud1ac\ud1ad\ud1ae\ud1af\ud1b0\ud1b1\ud1b2\ud1b3\ud1b4\ud1b5\ud1b6\ud1b7\ud1b8\ud1b9\ud1ba\ud1bb\ud1bc\ud1bd\ud1be\ud1bf\ud1c0\ud1c1\ud1c2\ud1c3\ud1c4\ud1c5\ud1c6\ud1c7\ud1c8\ud1c9\ud1ca\ud1cb\ud1cc\ud1cd\ud1ce\ud1cf\ud1d0\ud1d1\ud1d2\ud1d3\ud1d4\ud1d5\ud1d6\ud1d7\ud1d8\ud1d9\ud1da\ud1db\ud1dc\ud1dd\ud1de\ud1df\ud1e0\ud1e1\ud1e2\ud1e3\ud1e4\ud1e5\ud1e6\ud1e7\ud1e8\ud1e9\ud1ea\ud1eb\ud1ec\ud1ed\ud1ee\ud1ef\ud1f0\ud1f1\ud1f2\ud1f3\ud1f4\ud1f5\ud1f6\ud1f7\ud1f8\ud1f9\ud1fa\ud1fb\ud1fc\ud1fd\ud1fe\ud1ff\ud200\ud201\ud202\ud203\ud204\ud205\ud206\ud207\ud208\ud209\ud20a\ud20b\ud20c\ud20d\ud20e\ud20f\ud210\ud211\ud212\ud213\ud214\ud215\ud216\ud217\ud218\ud219\ud21a\ud21b\ud21c\ud21d\ud21e\ud21f\ud220\ud221\ud222\ud223\ud224\ud225\ud226\ud227\ud228\ud229\ud22a\ud22b\ud22c\ud22d\ud22e\ud22f\ud230\ud231\ud232\ud233\ud234\ud235\ud236\ud237\ud238\ud239\ud23a\ud23b\ud23c\ud23d\ud23e\ud23f\ud240\ud241\ud242\ud243\ud244\ud245\ud246\ud247\ud248\ud249\ud24a\ud24b\ud24c\ud24d\ud24e\ud24f\ud250\ud251\ud252\ud253\ud254\ud255\ud256\ud257\ud258\ud259\ud25a\ud25b\ud25c\ud25d\ud25e\ud25f\ud260\ud261\ud262\ud263\ud264\ud265\ud266\ud267\ud268\ud269\ud26a\ud26b\ud26c\ud26d\ud26e\ud26f\ud270\ud271\ud272\ud273\ud274\ud275\ud276\ud277\ud278\ud279\ud27a\ud27b\ud27c\ud27d\ud27e\ud27f\ud280\ud281\ud282\ud283\ud284\ud285\ud286\ud287\ud288\ud289\ud28a\ud28b\ud28c\ud28d\ud28e\ud28f\ud290\ud291\ud292\ud293\ud294\ud295\ud296\ud297\ud298\ud299\ud29a\ud29b\ud29c\ud29d\ud29e\ud29f\ud2a0\ud2a1\ud2a2\ud2a3\ud2a4\ud2a5\ud2a6\ud2a7\ud2a8\ud2a9\ud2aa\ud2ab\ud2ac\ud2ad\ud2ae\ud2af\ud2b0\ud2b1\ud2b2\ud2b3\ud2b4\ud2b5\ud2b6\ud2b7\ud2b8\ud2b9\ud2ba\ud2bb\ud2bc\ud2bd\ud2be\ud2bf\ud2c0\ud2c1\ud2c2\ud2c3\ud2c4\ud2c5\ud2c6\ud2c7\ud2c8\ud2c9\ud2ca\ud2cb\ud2cc\ud2cd\ud2ce\ud2cf\ud2d0\ud2d1\ud2d2\ud2d3\ud2d4\ud2d5\ud2d6\ud2d7\ud2d8\ud2d9\ud2da\ud2db\ud2dc\ud2dd\ud2de\ud2df\ud2e0\ud2e1\ud2e2\ud2e3\ud2e4\ud2e5\ud2e6\ud2e7\ud2e8\ud2e9\ud2ea\ud2eb\ud2ec\ud2ed\ud2ee\ud2ef\ud2f0\ud2f1\ud2f2\ud2f3\ud2f4\ud2f5\ud2f6\ud2f7\ud2f8\ud2f9\ud2fa\ud2fb\ud2fc\ud2fd\ud2fe\ud2ff\ud300\ud301\ud302\ud303\ud304\ud305\ud306\ud307\ud308\ud309\ud30a\ud30b\ud30c\ud30d\ud30e\ud30f\ud310\ud311\ud312\ud313\ud314\ud315\ud316\ud317\ud318\ud319\ud31a\ud31b\ud31c\ud31d\ud31e\ud31f\ud320\ud321\ud322\ud323\ud324\ud325\ud326\ud327\ud328\ud329\ud32a\ud32b\ud32c\ud32d\ud32e\ud32f\ud330\ud331\ud332\ud333\ud334\ud335\ud336\ud337\ud338\ud339\ud33a\ud33b\ud33c\ud33d\ud33e\ud33f\ud340\ud341\ud342\ud343\ud344\ud345\ud346\ud347\ud348\ud349\ud34a\ud34b\ud34c\ud34d\ud34e\ud34f\ud350\ud351\ud352\ud353\ud354\ud355\ud356\ud357\ud358\ud359\ud35a\ud35b\ud35c\ud35d\ud35e\ud35f\ud360\ud361\ud362\ud363\ud364\ud365\ud366\ud367\ud368\ud369\ud36a\ud36b\ud36c\ud36d\ud36e\ud36f\ud370\ud371\ud372\ud373\ud374\ud375\ud376\ud377\ud378\ud379\ud37a\ud37b\ud37c\ud37d\ud37e\ud37f\ud380\ud381\ud382\ud383\ud384\ud385\ud386\ud387\ud388\ud389\ud38a\ud38b\ud38c\ud38d\ud38e\ud38f\ud390\ud391\ud392\ud393\ud394\ud395\ud396\ud397\ud398\ud399\ud39a\ud39b\ud39c\ud39d\ud39e\ud39f\ud3a0\ud3a1\ud3a2\ud3a3\ud3a4\ud3a5\ud3a6\ud3a7\ud3a8\ud3a9\ud3aa\ud3ab\ud3ac\ud3ad\ud3ae\ud3af\ud3b0\ud3b1\ud3b2\ud3b3\ud3b4\ud3b5\ud3b6\ud3b7\ud3b8\ud3b9\ud3ba\ud3bb\ud3bc\ud3bd\ud3be\ud3bf\ud3c0\ud3c1\ud3c2\ud3c3\ud3c4\ud3c5\ud3c6\ud3c7\ud3c8\ud3c9\ud3ca\ud3cb\ud3cc\ud3cd\ud3ce\ud3cf\ud3d0\ud3d1\ud3d2\ud3d3\ud3d4\ud3d5\ud3d6\ud3d7\ud3d8\ud3d9\ud3da\ud3db\ud3dc\ud3dd\ud3de\ud3df\ud3e0\ud3e1\ud3e2\ud3e3\ud3e4\ud3e5\ud3e6\ud3e7\ud3e8\ud3e9\ud3ea\ud3eb\ud3ec\ud3ed\ud3ee\ud3ef\ud3f0\ud3f1\ud3f2\ud3f3\ud3f4\ud3f5\ud3f6\ud3f7\ud3f8\ud3f9\ud3fa\ud3fb\ud3fc\ud3fd\ud3fe\ud3ff\ud400\ud401\ud402\ud403\ud404\ud405\ud406\ud407\ud408\ud409\ud40a\ud40b\ud40c\ud40d\ud40e\ud40f\ud410\ud411\ud412\ud413\ud414\ud415\ud416\ud417\ud418\ud419\ud41a\ud41b\ud41c\ud41d\ud41e\ud41f\ud420\ud421\ud422\ud423\ud424\ud425\ud426\ud427\ud428\ud429\ud42a\ud42b\ud42c\ud42d\ud42e\ud42f\ud430\ud431\ud432\ud433\ud434\ud435\ud436\ud437\ud438\ud439\ud43a\ud43b\ud43c\ud43d\ud43e\ud43f\ud440\ud441\ud442\ud443\ud444\ud445\ud446\ud447\ud448\ud449\ud44a\ud44b\ud44c\ud44d\ud44e\ud44f\ud450\ud451\ud452\ud453\ud454\ud455\ud456\ud457\ud458\ud459\ud45a\ud45b\ud45c\ud45d\ud45e\ud45f\ud460\ud461\ud462\ud463\ud464\ud465\ud466\ud467\ud468\ud469\ud46a\ud46b\ud46c\ud46d\ud46e\ud46f\ud470\ud471\ud472\ud473\ud474\ud475\ud476\ud477\ud478\ud479\ud47a\ud47b\ud47c\ud47d\ud47e\ud47f\ud480\ud481\ud482\ud483\ud484\ud485\ud486\ud487\ud488\ud489\ud48a\ud48b\ud48c\ud48d\ud48e\ud48f\ud490\ud491\ud492\ud493\ud494\ud495\ud496\ud497\ud498\ud499\ud49a\ud49b\ud49c\ud49d\ud49e\ud49f\ud4a0\ud4a1\ud4a2\ud4a3\ud4a4\ud4a5\ud4a6\ud4a7\ud4a8\ud4a9\ud4aa\ud4ab\ud4ac\ud4ad\ud4ae\ud4af\ud4b0\ud4b1\ud4b2\ud4b3\ud4b4\ud4b5\ud4b6\ud4b7\ud4b8\ud4b9\ud4ba\ud4bb\ud4bc\ud4bd\ud4be\ud4bf\ud4c0\ud4c1\ud4c2\ud4c3\ud4c4\ud4c5\ud4c6\ud4c7\ud4c8\ud4c9\ud4ca\ud4cb\ud4cc\ud4cd\ud4ce\ud4cf\ud4d0\ud4d1\ud4d2\ud4d3\ud4d4\ud4d5\ud4d6\ud4d7\ud4d8\ud4d9\ud4da\ud4db\ud4dc\ud4dd\ud4de\ud4df\ud4e0\ud4e1\ud4e2\ud4e3\ud4e4\ud4e5\ud4e6\ud4e7\ud4e8\ud4e9\ud4ea\ud4eb\ud4ec\ud4ed\ud4ee\ud4ef\ud4f0\ud4f1\ud4f2\ud4f3\ud4f4\ud4f5\ud4f6\ud4f7\ud4f8\ud4f9\ud4fa\ud4fb\ud4fc\ud4fd\ud4fe\ud4ff\ud500\ud501\ud502\ud503\ud504\ud505\ud506\ud507\ud508\ud509\ud50a\ud50b\ud50c\ud50d\ud50e\ud50f\ud510\ud511\ud512\ud513\ud514\ud515\ud516\ud517\ud518\ud519\ud51a\ud51b\ud51c\ud51d\ud51e\ud51f\ud520\ud521\ud522\ud523\ud524\ud525\ud526\ud527\ud528\ud529\ud52a\ud52b\ud52c\ud52d\ud52e\ud52f\ud530\ud531\ud532\ud533\ud534\ud535\ud536\ud537\ud538\ud539\ud53a\ud53b\ud53c\ud53d\ud53e\ud53f\ud540\ud541\ud542\ud543\ud544\ud545\ud546\ud547\ud548\ud549\ud54a\ud54b\ud54c\ud54d\ud54e\ud54f\ud550\ud551\ud552\ud553\ud554\ud555\ud556\ud557\ud558\ud559\ud55a\ud55b\ud55c\ud55d\ud55e\ud55f\ud560\ud561\ud562\ud563\ud564\ud565\ud566\ud567\ud568\ud569\ud56a\ud56b\ud56c\ud56d\ud56e\ud56f\ud570\ud571\ud572\ud573\ud574\ud575\ud576\ud577\ud578\ud579\ud57a\ud57b\ud57c\ud57d\ud57e\ud57f\ud580\ud581\ud582\ud583\ud584\ud585\ud586\ud587\ud588\ud589\ud58a\ud58b\ud58c\ud58d\ud58e\ud58f\ud590\ud591\ud592\ud593\ud594\ud595\ud596\ud597\ud598\ud599\ud59a\ud59b\ud59c\ud59d\ud59e\ud59f\ud5a0\ud5a1\ud5a2\ud5a3\ud5a4\ud5a5\ud5a6\ud5a7\ud5a8\ud5a9\ud5aa\ud5ab\ud5ac\ud5ad\ud5ae\ud5af\ud5b0\ud5b1\ud5b2\ud5b3\ud5b4\ud5b5\ud5b6\ud5b7\ud5b8\ud5b9\ud5ba\ud5bb\ud5bc\ud5bd\ud5be\ud5bf\ud5c0\ud5c1\ud5c2\ud5c3\ud5c4\ud5c5\ud5c6\ud5c7\ud5c8\ud5c9\ud5ca\ud5cb\ud5cc\ud5cd\ud5ce\ud5cf\ud5d0\ud5d1\ud5d2\ud5d3\ud5d4\ud5d5\ud5d6\ud5d7\ud5d8\ud5d9\ud5da\ud5db\ud5dc\ud5dd\ud5de\ud5df\ud5e0\ud5e1\ud5e2\ud5e3\ud5e4\ud5e5\ud5e6\ud5e7\ud5e8\ud5e9\ud5ea\ud5eb\ud5ec\ud5ed\ud5ee\ud5ef\ud5f0\ud5f1\ud5f2\ud5f3\ud5f4\ud5f5\ud5f6\ud5f7\ud5f8\ud5f9\ud5fa\ud5fb\ud5fc\ud5fd\ud5fe\ud5ff\ud600\ud601\ud602\ud603\ud604\ud605\ud606\ud607\ud608\ud609\ud60a\ud60b\ud60c\ud60d\ud60e\ud60f\ud610\ud611\ud612\ud613\ud614\ud615\ud616\ud617\ud618\ud619\ud61a\ud61b\ud61c\ud61d\ud61e\ud61f\ud620\ud621\ud622\ud623\ud624\ud625\ud626\ud627\ud628\ud629\ud62a\ud62b\ud62c\ud62d\ud62e\ud62f\ud630\ud631\ud632\ud633\ud634\ud635\ud636\ud637\ud638\ud639\ud63a\ud63b\ud63c\ud63d\ud63e\ud63f\ud640\ud641\ud642\ud643\ud644\ud645\ud646\ud647\ud648\ud649\ud64a\ud64b\ud64c\ud64d\ud64e\ud64f\ud650\ud651\ud652\ud653\ud654\ud655\ud656\ud657\ud658\ud659\ud65a\ud65b\ud65c\ud65d\ud65e\ud65f\ud660\ud661\ud662\ud663\ud664\ud665\ud666\ud667\ud668\ud669\ud66a\ud66b\ud66c\ud66d\ud66e\ud66f\ud670\ud671\ud672\ud673\ud674\ud675\ud676\ud677\ud678\ud679\ud67a\ud67b\ud67c\ud67d\ud67e\ud67f\ud680\ud681\ud682\ud683\ud684\ud685\ud686\ud687\ud688\ud689\ud68a\ud68b\ud68c\ud68d\ud68e\ud68f\ud690\ud691\ud692\ud693\ud694\ud695\ud696\ud697\ud698\ud699\ud69a\ud69b\ud69c\ud69d\ud69e\ud69f\ud6a0\ud6a1\ud6a2\ud6a3\ud6a4\ud6a5\ud6a6\ud6a7\ud6a8\ud6a9\ud6aa\ud6ab\ud6ac\ud6ad\ud6ae\ud6af\ud6b0\ud6b1\ud6b2\ud6b3\ud6b4\ud6b5\ud6b6\ud6b7\ud6b8\ud6b9\ud6ba\ud6bb\ud6bc\ud6bd\ud6be\ud6bf\ud6c0\ud6c1\ud6c2\ud6c3\ud6c4\ud6c5\ud6c6\ud6c7\ud6c8\ud6c9\ud6ca\ud6cb\ud6cc\ud6cd\ud6ce\ud6cf\ud6d0\ud6d1\ud6d2\ud6d3\ud6d4\ud6d5\ud6d6\ud6d7\ud6d8\ud6d9\ud6da\ud6db\ud6dc\ud6dd\ud6de\ud6df\ud6e0\ud6e1\ud6e2\ud6e3\ud6e4\ud6e5\ud6e6\ud6e7\ud6e8\ud6e9\ud6ea\ud6eb\ud6ec\ud6ed\ud6ee\ud6ef\ud6f0\ud6f1\ud6f2\ud6f3\ud6f4\ud6f5\ud6f6\ud6f7\ud6f8\ud6f9\ud6fa\ud6fb\ud6fc\ud6fd\ud6fe\ud6ff\ud700\ud701\ud702\ud703\ud704\ud705\ud706\ud707\ud708\ud709\ud70a\ud70b\ud70c\ud70d\ud70e\ud70f\ud710\ud711\ud712\ud713\ud714\ud715\ud716\ud717\ud718\ud719\ud71a\ud71b\ud71c\ud71d\ud71e\ud71f\ud720\ud721\ud722\ud723\ud724\ud725\ud726\ud727\ud728\ud729\ud72a\ud72b\ud72c\ud72d\ud72e\ud72f\ud730\ud731\ud732\ud733\ud734\ud735\ud736\ud737\ud738\ud739\ud73a\ud73b\ud73c\ud73d\ud73e\ud73f\ud740\ud741\ud742\ud743\ud744\ud745\ud746\ud747\ud748\ud749\ud74a\ud74b\ud74c\ud74d\ud74e\ud74f\ud750\ud751\ud752\ud753\ud754\ud755\ud756\ud757\ud758\ud759\ud75a\ud75b\ud75c\ud75d\ud75e\ud75f\ud760\ud761\ud762\ud763\ud764\ud765\ud766\ud767\ud768\ud769\ud76a\ud76b\ud76c\ud76d\ud76e\ud76f\ud770\ud771\ud772\ud773\ud774\ud775\ud776\ud777\ud778\ud779\ud77a\ud77b\ud77c\ud77d\ud77e\ud77f\ud780\ud781\ud782\ud783\ud784\ud785\ud786\ud787\ud788\ud789\ud78a\ud78b\ud78c\ud78d\ud78e\ud78f\ud790\ud791\ud792\ud793\ud794\ud795\ud796\ud797\ud798\ud799\ud79a\ud79b\ud79c\ud79d\ud79e\ud79f\ud7a0\ud7a1\ud7a2\ud7a3\uf900\uf901\uf902\uf903\uf904\uf905\uf906\uf907\uf908\uf909\uf90a\uf90b\uf90c\uf90d\uf90e\uf90f\uf910\uf911\uf912\uf913\uf914\uf915\uf916\uf917\uf918\uf919\uf91a\uf91b\uf91c\uf91d\uf91e\uf91f\uf920\uf921\uf922\uf923\uf924\uf925\uf926\uf927\uf928\uf929\uf92a\uf92b\uf92c\uf92d\uf92e\uf92f\uf930\uf931\uf932\uf933\uf934\uf935\uf936\uf937\uf938\uf939\uf93a\uf93b\uf93c\uf93d\uf93e\uf93f\uf940\uf941\uf942\uf943\uf944\uf945\uf946\uf947\uf948\uf949\uf94a\uf94b\uf94c\uf94d\uf94e\uf94f\uf950\uf951\uf952\uf953\uf954\uf955\uf956\uf957\uf958\uf959\uf95a\uf95b\uf95c\uf95d\uf95e\uf95f\uf960\uf961\uf962\uf963\uf964\uf965\uf966\uf967\uf968\uf969\uf96a\uf96b\uf96c\uf96d\uf96e\uf96f\uf970\uf971\uf972\uf973\uf974\uf975\uf976\uf977\uf978\uf979\uf97a\uf97b\uf97c\uf97d\uf97e\uf97f\uf980\uf981\uf982\uf983\uf984\uf985\uf986\uf987\uf988\uf989\uf98a\uf98b\uf98c\uf98d\uf98e\uf98f\uf990\uf991\uf992\uf993\uf994\uf995\uf996\uf997\uf998\uf999\uf99a\uf99b\uf99c\uf99d\uf99e\uf99f\uf9a0\uf9a1\uf9a2\uf9a3\uf9a4\uf9a5\uf9a6\uf9a7\uf9a8\uf9a9\uf9aa\uf9ab\uf9ac\uf9ad\uf9ae\uf9af\uf9b0\uf9b1\uf9b2\uf9b3\uf9b4\uf9b5\uf9b6\uf9b7\uf9b8\uf9b9\uf9ba\uf9bb\uf9bc\uf9bd\uf9be\uf9bf\uf9c0\uf9c1\uf9c2\uf9c3\uf9c4\uf9c5\uf9c6\uf9c7\uf9c8\uf9c9\uf9ca\uf9cb\uf9cc\uf9cd\uf9ce\uf9cf\uf9d0\uf9d1\uf9d2\uf9d3\uf9d4\uf9d5\uf9d6\uf9d7\uf9d8\uf9d9\uf9da\uf9db\uf9dc\uf9dd\uf9de\uf9df\uf9e0\uf9e1\uf9e2\uf9e3\uf9e4\uf9e5\uf9e6\uf9e7\uf9e8\uf9e9\uf9ea\uf9eb\uf9ec\uf9ed\uf9ee\uf9ef\uf9f0\uf9f1\uf9f2\uf9f3\uf9f4\uf9f5\uf9f6\uf9f7\uf9f8\uf9f9\uf9fa\uf9fb\uf9fc\uf9fd\uf9fe\uf9ff\ufa00\ufa01\ufa02\ufa03\ufa04\ufa05\ufa06\ufa07\ufa08\ufa09\ufa0a\ufa0b\ufa0c\ufa0d\ufa0e\ufa0f\ufa10\ufa11\ufa12\ufa13\ufa14\ufa15\ufa16\ufa17\ufa18\ufa19\ufa1a\ufa1b\ufa1c\ufa1d\ufa1e\ufa1f\ufa20\ufa21\ufa22\ufa23\ufa24\ufa25\ufa26\ufa27\ufa28\ufa29\ufa2a\ufa2b\ufa2c\ufa2d\ufa30\ufa31\ufa32\ufa33\ufa34\ufa35\ufa36\ufa37\ufa38\ufa39\ufa3a\ufa3b\ufa3c\ufa3d\ufa3e\ufa3f\ufa40\ufa41\ufa42\ufa43\ufa44\ufa45\ufa46\ufa47\ufa48\ufa49\ufa4a\ufa4b\ufa4c\ufa4d\ufa4e\ufa4f\ufa50\ufa51\ufa52\ufa53\ufa54\ufa55\ufa56\ufa57\ufa58\ufa59\ufa5a\ufa5b\ufa5c\ufa5d\ufa5e\ufa5f\ufa60\ufa61\ufa62\ufa63\ufa64\ufa65\ufa66\ufa67\ufa68\ufa69\ufa6a\ufa70\ufa71\ufa72\ufa73\ufa74\ufa75\ufa76\ufa77\ufa78\ufa79\ufa7a\ufa7b\ufa7c\ufa7d\ufa7e\ufa7f\ufa80\ufa81\ufa82\ufa83\ufa84\ufa85\ufa86\ufa87\ufa88\ufa89\ufa8a\ufa8b\ufa8c\ufa8d\ufa8e\ufa8f\ufa90\ufa91\ufa92\ufa93\ufa94\ufa95\ufa96\ufa97\ufa98\ufa99\ufa9a\ufa9b\ufa9c\ufa9d\ufa9e\ufa9f\ufaa0\ufaa1\ufaa2\ufaa3\ufaa4\ufaa5\ufaa6\ufaa7\ufaa8\ufaa9\ufaaa\ufaab\ufaac\ufaad\ufaae\ufaaf\ufab0\ufab1\ufab2\ufab3\ufab4\ufab5\ufab6\ufab7\ufab8\ufab9\ufaba\ufabb\ufabc\ufabd\ufabe\ufabf\ufac0\ufac1\ufac2\ufac3\ufac4\ufac5\ufac6\ufac7\ufac8\ufac9\ufaca\ufacb\ufacc\ufacd\uface\ufacf\ufad0\ufad1\ufad2\ufad3\ufad4\ufad5\ufad6\ufad7\ufad8\ufad9\ufb1d\ufb1f\ufb20\ufb21\ufb22\ufb23\ufb24\ufb25\ufb26\ufb27\ufb28\ufb2a\ufb2b\ufb2c\ufb2d\ufb2e\ufb2f\ufb30\ufb31\ufb32\ufb33\ufb34\ufb35\ufb36\ufb38\ufb39\ufb3a\ufb3b\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46\ufb47\ufb48\ufb49\ufb4a\ufb4b\ufb4c\ufb4d\ufb4e\ufb4f\ufb50\ufb51\ufb52\ufb53\ufb54\ufb55\ufb56\ufb57\ufb58\ufb59\ufb5a\ufb5b\ufb5c\ufb5d\ufb5e\ufb5f\ufb60\ufb61\ufb62\ufb63\ufb64\ufb65\ufb66\ufb67\ufb68\ufb69\ufb6a\ufb6b\ufb6c\ufb6d\ufb6e\ufb6f\ufb70\ufb71\ufb72\ufb73\ufb74\ufb75\ufb76\ufb77\ufb78\ufb79\ufb7a\ufb7b\ufb7c\ufb7d\ufb7e\ufb7f\ufb80\ufb81\ufb82\ufb83\ufb84\ufb85\ufb86\ufb87\ufb88\ufb89\ufb8a\ufb8b\ufb8c\ufb8d\ufb8e\ufb8f\ufb90\ufb91\ufb92\ufb93\ufb94\ufb95\ufb96\ufb97\ufb98\ufb99\ufb9a\ufb9b\ufb9c\ufb9d\ufb9e\ufb9f\ufba0\ufba1\ufba2\ufba3\ufba4\ufba5\ufba6\ufba7\ufba8\ufba9\ufbaa\ufbab\ufbac\ufbad\ufbae\ufbaf\ufbb0\ufbb1\ufbd3\ufbd4\ufbd5\ufbd6\ufbd7\ufbd8\ufbd9\ufbda\ufbdb\ufbdc\ufbdd\ufbde\ufbdf\ufbe0\ufbe1\ufbe2\ufbe3\ufbe4\ufbe5\ufbe6\ufbe7\ufbe8\ufbe9\ufbea\ufbeb\ufbec\ufbed\ufbee\ufbef\ufbf0\ufbf1\ufbf2\ufbf3\ufbf4\ufbf5\ufbf6\ufbf7\ufbf8\ufbf9\ufbfa\ufbfb\ufbfc\ufbfd\ufbfe\ufbff\ufc00\ufc01\ufc02\ufc03\ufc04\ufc05\ufc06\ufc07\ufc08\ufc09\ufc0a\ufc0b\ufc0c\ufc0d\ufc0e\ufc0f\ufc10\ufc11\ufc12\ufc13\ufc14\ufc15\ufc16\ufc17\ufc18\ufc19\ufc1a\ufc1b\ufc1c\ufc1d\ufc1e\ufc1f\ufc20\ufc21\ufc22\ufc23\ufc24\ufc25\ufc26\ufc27\ufc28\ufc29\ufc2a\ufc2b\ufc2c\ufc2d\ufc2e\ufc2f\ufc30\ufc31\ufc32\ufc33\ufc34\ufc35\ufc36\ufc37\ufc38\ufc39\ufc3a\ufc3b\ufc3c\ufc3d\ufc3e\ufc3f\ufc40\ufc41\ufc42\ufc43\ufc44\ufc45\ufc46\ufc47\ufc48\ufc49\ufc4a\ufc4b\ufc4c\ufc4d\ufc4e\ufc4f\ufc50\ufc51\ufc52\ufc53\ufc54\ufc55\ufc56\ufc57\ufc58\ufc59\ufc5a\ufc5b\ufc5c\ufc5d\ufc5e\ufc5f\ufc60\ufc61\ufc62\ufc63\ufc64\ufc65\ufc66\ufc67\ufc68\ufc69\ufc6a\ufc6b\ufc6c\ufc6d\ufc6e\ufc6f\ufc70\ufc71\ufc72\ufc73\ufc74\ufc75\ufc76\ufc77\ufc78\ufc79\ufc7a\ufc7b\ufc7c\ufc7d\ufc7e\ufc7f\ufc80\ufc81\ufc82\ufc83\ufc84\ufc85\ufc86\ufc87\ufc88\ufc89\ufc8a\ufc8b\ufc8c\ufc8d\ufc8e\ufc8f\ufc90\ufc91\ufc92\ufc93\ufc94\ufc95\ufc96\ufc97\ufc98\ufc99\ufc9a\ufc9b\ufc9c\ufc9d\ufc9e\ufc9f\ufca0\ufca1\ufca2\ufca3\ufca4\ufca5\ufca6\ufca7\ufca8\ufca9\ufcaa\ufcab\ufcac\ufcad\ufcae\ufcaf\ufcb0\ufcb1\ufcb2\ufcb3\ufcb4\ufcb5\ufcb6\ufcb7\ufcb8\ufcb9\ufcba\ufcbb\ufcbc\ufcbd\ufcbe\ufcbf\ufcc0\ufcc1\ufcc2\ufcc3\ufcc4\ufcc5\ufcc6\ufcc7\ufcc8\ufcc9\ufcca\ufccb\ufccc\ufccd\ufcce\ufccf\ufcd0\ufcd1\ufcd2\ufcd3\ufcd4\ufcd5\ufcd6\ufcd7\ufcd8\ufcd9\ufcda\ufcdb\ufcdc\ufcdd\ufcde\ufcdf\ufce0\ufce1\ufce2\ufce3\ufce4\ufce5\ufce6\ufce7\ufce8\ufce9\ufcea\ufceb\ufcec\ufced\ufcee\ufcef\ufcf0\ufcf1\ufcf2\ufcf3\ufcf4\ufcf5\ufcf6\ufcf7\ufcf8\ufcf9\ufcfa\ufcfb\ufcfc\ufcfd\ufcfe\ufcff\ufd00\ufd01\ufd02\ufd03\ufd04\ufd05\ufd06\ufd07\ufd08\ufd09\ufd0a\ufd0b\ufd0c\ufd0d\ufd0e\ufd0f\ufd10\ufd11\ufd12\ufd13\ufd14\ufd15\ufd16\ufd17\ufd18\ufd19\ufd1a\ufd1b\ufd1c\ufd1d\ufd1e\ufd1f\ufd20\ufd21\ufd22\ufd23\ufd24\ufd25\ufd26\ufd27\ufd28\ufd29\ufd2a\ufd2b\ufd2c\ufd2d\ufd2e\ufd2f\ufd30\ufd31\ufd32\ufd33\ufd34\ufd35\ufd36\ufd37\ufd38\ufd39\ufd3a\ufd3b\ufd3c\ufd3d\ufd50\ufd51\ufd52\ufd53\ufd54\ufd55\ufd56\ufd57\ufd58\ufd59\ufd5a\ufd5b\ufd5c\ufd5d\ufd5e\ufd5f\ufd60\ufd61\ufd62\ufd63\ufd64\ufd65\ufd66\ufd67\ufd68\ufd69\ufd6a\ufd6b\ufd6c\ufd6d\ufd6e\ufd6f\ufd70\ufd71\ufd72\ufd73\ufd74\ufd75\ufd76\ufd77\ufd78\ufd79\ufd7a\ufd7b\ufd7c\ufd7d\ufd7e\ufd7f\ufd80\ufd81\ufd82\ufd83\ufd84\ufd85\ufd86\ufd87\ufd88\ufd89\ufd8a\ufd8b\ufd8c\ufd8d\ufd8e\ufd8f\ufd92\ufd93\ufd94\ufd95\ufd96\ufd97\ufd98\ufd99\ufd9a\ufd9b\ufd9c\ufd9d\ufd9e\ufd9f\ufda0\ufda1\ufda2\ufda3\ufda4\ufda5\ufda6\ufda7\ufda8\ufda9\ufdaa\ufdab\ufdac\ufdad\ufdae\ufdaf\ufdb0\ufdb1\ufdb2\ufdb3\ufdb4\ufdb5\ufdb6\ufdb7\ufdb8\ufdb9\ufdba\ufdbb\ufdbc\ufdbd\ufdbe\ufdbf\ufdc0\ufdc1\ufdc2\ufdc3\ufdc4\ufdc5\ufdc6\ufdc7\ufdf0\ufdf1\ufdf2\ufdf3\ufdf4\ufdf5\ufdf6\ufdf7\ufdf8\ufdf9\ufdfa\ufdfb\ufe70\ufe71\ufe72\ufe73\ufe74\ufe76\ufe77\ufe78\ufe79\ufe7a\ufe7b\ufe7c\ufe7d\ufe7e\ufe7f\ufe80\ufe81\ufe82\ufe83\ufe84\ufe85\ufe86\ufe87\ufe88\ufe89\ufe8a\ufe8b\ufe8c\ufe8d\ufe8e\ufe8f\ufe90\ufe91\ufe92\ufe93\ufe94\ufe95\ufe96\ufe97\ufe98\ufe99\ufe9a\ufe9b\ufe9c\ufe9d\ufe9e\ufe9f\ufea0\ufea1\ufea2\ufea3\ufea4\ufea5\ufea6\ufea7\ufea8\ufea9\ufeaa\ufeab\ufeac\ufead\ufeae\ufeaf\ufeb0\ufeb1\ufeb2\ufeb3\ufeb4\ufeb5\ufeb6\ufeb7\ufeb8\ufeb9\ufeba\ufebb\ufebc\ufebd\ufebe\ufebf\ufec0\ufec1\ufec2\ufec3\ufec4\ufec5\ufec6\ufec7\ufec8\ufec9\ufeca\ufecb\ufecc\ufecd\ufece\ufecf\ufed0\ufed1\ufed2\ufed3\ufed4\ufed5\ufed6\ufed7\ufed8\ufed9\ufeda\ufedb\ufedc\ufedd\ufede\ufedf\ufee0\ufee1\ufee2\ufee3\ufee4\ufee5\ufee6\ufee7\ufee8\ufee9\ufeea\ufeeb\ufeec\ufeed\ufeee\ufeef\ufef0\ufef1\ufef2\ufef3\ufef4\ufef5\ufef6\ufef7\ufef8\ufef9\ufefa\ufefb\ufefc\uff66\uff67\uff68\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\uff8a\uff8b\uff8c\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d\uffa0\uffa1\uffa2\uffa3\uffa4\uffa5\uffa6\uffa7\uffa8\uffa9\uffaa\uffab\uffac\uffad\uffae\uffaf\uffb0\uffb1\uffb2\uffb3\uffb4\uffb5\uffb6\uffb7\uffb8\uffb9\uffba\uffbb\uffbc\uffbd\uffbe\uffc2\uffc3\uffc4\uffc5\uffc6\uffc7\uffca\uffcb\uffcc\uffcd\uffce\uffcf\uffd2\uffd3\uffd4\uffd5\uffd6\uffd7\uffda\uffdb\uffdc' - -Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc' - -Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a' - -Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827' - -Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4' - -Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23' - -Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' - -Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a' - -No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf' - -Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f' - -Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d' - -Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63' - -Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d' - -Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c' - -Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65' - -Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62' - -Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6' - -Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3' - -Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\ufb29\ufe62\ufe64\ufe65\ufe66\uff0b\uff1c\uff1d\uff1e\uff5c\uff5e\uffe2\uffe9\uffea\uffeb\uffec' - -So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u2701\u2702\u2703\u2704\u2706\u2707\u2708\u2709\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274d\u274f\u2750\u2751\u2752\u2756\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2e80\u2e81\u2e82\u2e83\u2e84\u2e85\u2e86\u2e87\u2e88\u2e89\u2e8a\u2e8b\u2e8c\u2e8d\u2e8e\u2e8f\u2e90\u2e91\u2e92\u2e93\u2e94\u2e95\u2e96\u2e97\u2e98\u2e99\u2e9b\u2e9c\u2e9d\u2e9e\u2e9f\u2ea0\u2ea1\u2ea2\u2ea3\u2ea4\u2ea5\u2ea6\u2ea7\u2ea8\u2ea9\u2eaa\u2eab\u2eac\u2ead\u2eae\u2eaf\u2eb0\u2eb1\u2eb2\u2eb3\u2eb4\u2eb5\u2eb6\u2eb7\u2eb8\u2eb9\u2eba\u2ebb\u2ebc\u2ebd\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u2ee2\u2ee3\u2ee4\u2ee5\u2ee6\u2ee7\u2ee8\u2ee9\u2eea\u2eeb\u2eec\u2eed\u2eee\u2eef\u2ef0\u2ef1\u2ef2\u2ef3\u2f00\u2f01\u2f02\u2f03\u2f04\u2f05\u2f06\u2f07\u2f08\u2f09\u2f0a\u2f0b\u2f0c\u2f0d\u2f0e\u2f0f\u2f10\u2f11\u2f12\u2f13\u2f14\u2f15\u2f16\u2f17\u2f18\u2f19\u2f1a\u2f1b\u2f1c\u2f1d\u2f1e\u2f1f\u2f20\u2f21\u2f22\u2f23\u2f24\u2f25\u2f26\u2f27\u2f28\u2f29\u2f2a\u2f2b\u2f2c\u2f2d\u2f2e\u2f2f\u2f30\u2f31\u2f32\u2f33\u2f34\u2f35\u2f36\u2f37\u2f38\u2f39\u2f3a\u2f3b\u2f3c\u2f3d\u2f3e\u2f3f\u2f40\u2f41\u2f42\u2f43\u2f44\u2f45\u2f46\u2f47\u2f48\u2f49\u2f4a\u2f4b\u2f4c\u2f4d\u2f4e\u2f4f\u2f50\u2f51\u2f52\u2f53\u2f54\u2f55\u2f56\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2f5d\u2f5e\u2f5f\u2f60\u2f61\u2f62\u2f63\u2f64\u2f65\u2f66\u2f67\u2f68\u2f69\u2f6a\u2f6b\u2f6c\u2f6d\u2f6e\u2f6f\u2f70\u2f71\u2f72\u2f73\u2f74\u2f75\u2f76\u2f77\u2f78\u2f79\u2f7a\u2f7b\u2f7c\u2f7d\u2f7e\u2f7f\u2f80\u2f81\u2f82\u2f83\u2f84\u2f85\u2f86\u2f87\u2f88\u2f89\u2f8a\u2f8b\u2f8c\u2f8d\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3250\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u326b\u326c\u326d\u326e\u326f\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u328a\u328b\u328c\u328d\u328e\u328f\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329a\u329b\u329c\u329d\u329e\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u32a5\u32a6\u32a7\u32a8\u32a9\u32aa\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32c0\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32ce\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e1\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f7\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330c\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331a\u331b\u331c\u331d\u331e\u331f\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332a\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333a\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b\u334c\u334d\u334e\u334f\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339b\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a2\u33a3\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ab\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua828\ua829\ua82a\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd' - -Zl = u'\u2028' - -Zp = u'\u2029' - -Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000' - -cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs'] - -def combine(*args): - return u''.join([globals()[cat] for cat in args]) - -xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC' - -xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u302A-\u302F\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA802\uA803-\uA805\uA806\uA807-\uA80A\uA80B\uA80C-\uA822\uA823-\uA824\uA825-\uA826\uA827\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE00-\uFE0F\uFE20-\uFE23\uFE33-\uFE34\uFE4D-\uFE4F\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFF9E-\uFF9F\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC' - -def allexcept(*args): - newcats = cats[:] - for arg in args: - newcats.remove(arg) - return u''.join([globals()[cat] for cat in newcats]) - -if __name__ == '__main__': - import unicodedata - - categories = {} - - f = open(__file__.rstrip('co')) - try: - content = f.read() - finally: - f.close() - - header = content[:content.find('Cc =')] - footer = content[content.find("def combine("):] - - for code in range(65535): - c = unichr(code) - cat = unicodedata.category(c) - categories.setdefault(cat, []).append(c) - - f = open(__file__, 'w') - f.write(header) - - for cat in sorted(categories): - val = u''.join(categories[cat]) - if cat == 'Cs': - # Jython can't handle isolated surrogates - f.write("""\ -try: - Cs = eval(r"%r") -except UnicodeDecodeError: - Cs = '' # Jython can't handle isolated surrogates\n\n""" % val) - else: - f.write('%s = %r\n\n' % (cat, val)) - f.write('cats = %r\n\n' % sorted(categories.keys())) - - f.write(footer) - f.close() diff --git a/module/lib/jinja2/bccache.py b/module/lib/jinja2/bccache.py deleted file mode 100644 index 1e2236c3a..000000000 --- a/module/lib/jinja2/bccache.py +++ /dev/null @@ -1,280 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.bccache - ~~~~~~~~~~~~~~ - - This module implements the bytecode cache system Jinja is optionally - using. This is useful if you have very complex template situations and - the compiliation of all those templates slow down your application too - much. - - Situations where this is useful are often forking web applications that - are initialized on the first request. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -from os import path, listdir -import marshal -import tempfile -import cPickle as pickle -import fnmatch -from cStringIO import StringIO -try: - from hashlib import sha1 -except ImportError: - from sha import new as sha1 -from jinja2.utils import open_if_exists - - -bc_version = 1 -bc_magic = 'j2'.encode('ascii') + pickle.dumps(bc_version, 2) - - -class Bucket(object): - """Buckets are used to store the bytecode for one template. It's created - and initialized by the bytecode cache and passed to the loading functions. - - The buckets get an internal checksum from the cache assigned and use this - to automatically reject outdated cache material. Individual bytecode - cache subclasses don't have to care about cache invalidation. - """ - - def __init__(self, environment, key, checksum): - self.environment = environment - self.key = key - self.checksum = checksum - self.reset() - - def reset(self): - """Resets the bucket (unloads the bytecode).""" - self.code = None - - def load_bytecode(self, f): - """Loads bytecode from a file or file like object.""" - # make sure the magic header is correct - magic = f.read(len(bc_magic)) - if magic != bc_magic: - self.reset() - return - # the source code of the file changed, we need to reload - checksum = pickle.load(f) - if self.checksum != checksum: - self.reset() - return - # now load the code. Because marshal is not able to load - # from arbitrary streams we have to work around that - if isinstance(f, file): - self.code = marshal.load(f) - else: - self.code = marshal.loads(f.read()) - - def write_bytecode(self, f): - """Dump the bytecode into the file or file like object passed.""" - if self.code is None: - raise TypeError('can\'t write empty bucket') - f.write(bc_magic) - pickle.dump(self.checksum, f, 2) - if isinstance(f, file): - marshal.dump(self.code, f) - else: - f.write(marshal.dumps(self.code)) - - def bytecode_from_string(self, string): - """Load bytecode from a string.""" - self.load_bytecode(StringIO(string)) - - def bytecode_to_string(self): - """Return the bytecode as string.""" - out = StringIO() - self.write_bytecode(out) - return out.getvalue() - - -class BytecodeCache(object): - """To implement your own bytecode cache you have to subclass this class - and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of - these methods are passed a :class:`~jinja2.bccache.Bucket`. - - A very basic bytecode cache that saves the bytecode on the file system:: - - from os import path - - class MyCache(BytecodeCache): - - def __init__(self, directory): - self.directory = directory - - def load_bytecode(self, bucket): - filename = path.join(self.directory, bucket.key) - if path.exists(filename): - with open(filename, 'rb') as f: - bucket.load_bytecode(f) - - def dump_bytecode(self, bucket): - filename = path.join(self.directory, bucket.key) - with open(filename, 'wb') as f: - bucket.write_bytecode(f) - - A more advanced version of a filesystem based bytecode cache is part of - Jinja2. - """ - - def load_bytecode(self, bucket): - """Subclasses have to override this method to load bytecode into a - bucket. If they are not able to find code in the cache for the - bucket, it must not do anything. - """ - raise NotImplementedError() - - def dump_bytecode(self, bucket): - """Subclasses have to override this method to write the bytecode - from a bucket back to the cache. If it unable to do so it must not - fail silently but raise an exception. - """ - raise NotImplementedError() - - def clear(self): - """Clears the cache. This method is not used by Jinja2 but should be - implemented to allow applications to clear the bytecode cache used - by a particular environment. - """ - - def get_cache_key(self, name, filename=None): - """Returns the unique hash key for this template name.""" - hash = sha1(name.encode('utf-8')) - if filename is not None: - if isinstance(filename, unicode): - filename = filename.encode('utf-8') - hash.update('|' + filename) - return hash.hexdigest() - - def get_source_checksum(self, source): - """Returns a checksum for the source.""" - return sha1(source.encode('utf-8')).hexdigest() - - def get_bucket(self, environment, name, filename, source): - """Return a cache bucket for the given template. All arguments are - mandatory but filename may be `None`. - """ - key = self.get_cache_key(name, filename) - checksum = self.get_source_checksum(source) - bucket = Bucket(environment, key, checksum) - self.load_bytecode(bucket) - return bucket - - def set_bucket(self, bucket): - """Put the bucket into the cache.""" - self.dump_bytecode(bucket) - - -class FileSystemBytecodeCache(BytecodeCache): - """A bytecode cache that stores bytecode on the filesystem. It accepts - two arguments: The directory where the cache items are stored and a - pattern string that is used to build the filename. - - If no directory is specified the system temporary items folder is used. - - The pattern can be used to have multiple separate caches operate on the - same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` - is replaced with the cache key. - - >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') - - This bytecode cache supports clearing of the cache using the clear method. - """ - - def __init__(self, directory=None, pattern='__jinja2_%s.cache'): - if directory is None: - directory = tempfile.gettempdir() - self.directory = directory - self.pattern = pattern - - def _get_cache_filename(self, bucket): - return path.join(self.directory, self.pattern % bucket.key) - - def load_bytecode(self, bucket): - f = open_if_exists(self._get_cache_filename(bucket), 'rb') - if f is not None: - try: - bucket.load_bytecode(f) - finally: - f.close() - - def dump_bytecode(self, bucket): - f = open(self._get_cache_filename(bucket), 'wb') - try: - bucket.write_bytecode(f) - finally: - f.close() - - def clear(self): - # imported lazily here because google app-engine doesn't support - # write access on the file system and the function does not exist - # normally. - from os import remove - files = fnmatch.filter(listdir(self.directory), self.pattern % '*') - for filename in files: - try: - remove(path.join(self.directory, filename)) - except OSError: - pass - - -class MemcachedBytecodeCache(BytecodeCache): - """This class implements a bytecode cache that uses a memcache cache for - storing the information. It does not enforce a specific memcache library - (tummy's memcache or cmemcache) but will accept any class that provides - the minimal interface required. - - Libraries compatible with this class: - - - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache - - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ - - `cmemcache <http://gijsbert.org/cmemcache/>`_ - - (Unfortunately the django cache interface is not compatible because it - does not support storing binary data, only unicode. You can however pass - the underlying cache client to the bytecode cache which is available - as `django.core.cache.cache._client`.) - - The minimal interface for the client passed to the constructor is this: - - .. class:: MinimalClientInterface - - .. method:: set(key, value[, timeout]) - - Stores the bytecode in the cache. `value` is a string and - `timeout` the timeout of the key. If timeout is not provided - a default timeout or no timeout should be assumed, if it's - provided it's an integer with the number of seconds the cache - item should exist. - - .. method:: get(key) - - Returns the value for the cache key. If the item does not - exist in the cache the return value must be `None`. - - The other arguments to the constructor are the prefix for all keys that - is added before the actual cache key and the timeout for the bytecode in - the cache system. We recommend a high (or no) timeout. - - This bytecode cache does not support clearing of used items in the cache. - The clear method is a no-operation function. - """ - - def __init__(self, client, prefix='jinja2/bytecode/', timeout=None): - self.client = client - self.prefix = prefix - self.timeout = timeout - - def load_bytecode(self, bucket): - code = self.client.get(self.prefix + bucket.key) - if code is not None: - bucket.bytecode_from_string(code) - - def dump_bytecode(self, bucket): - args = (self.prefix + bucket.key, bucket.bytecode_to_string()) - if self.timeout is not None: - args += (self.timeout,) - self.client.set(*args) diff --git a/module/lib/jinja2/compiler.py b/module/lib/jinja2/compiler.py deleted file mode 100644 index 57641596a..000000000 --- a/module/lib/jinja2/compiler.py +++ /dev/null @@ -1,1640 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.compiler - ~~~~~~~~~~~~~~~ - - Compiles nodes into python code. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from cStringIO import StringIO -from itertools import chain -from copy import deepcopy -from jinja2 import nodes -from jinja2.nodes import EvalContext -from jinja2.visitor import NodeVisitor, NodeTransformer -from jinja2.exceptions import TemplateAssertionError -from jinja2.utils import Markup, concat, escape, is_python_keyword, next - - -operators = { - 'eq': '==', - 'ne': '!=', - 'gt': '>', - 'gteq': '>=', - 'lt': '<', - 'lteq': '<=', - 'in': 'in', - 'notin': 'not in' -} - -try: - exec '(0 if 0 else 0)' -except SyntaxError: - have_condexpr = False -else: - have_condexpr = True - - -# what method to iterate over items do we want to use for dict iteration -# in generated code? on 2.x let's go with iteritems, on 3.x with items -if hasattr(dict, 'iteritems'): - dict_item_iter = 'iteritems' -else: - dict_item_iter = 'items' - - -# does if 0: dummy(x) get us x into the scope? -def unoptimize_before_dead_code(): - x = 42 - def f(): - if 0: dummy(x) - return f -unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure) - - -def generate(node, environment, name, filename, stream=None, - defer_init=False): - """Generate the python source for a node tree.""" - if not isinstance(node, nodes.Template): - raise TypeError('Can\'t compile non template nodes') - generator = CodeGenerator(environment, name, filename, stream, defer_init) - generator.visit(node) - if stream is None: - return generator.stream.getvalue() - - -def has_safe_repr(value): - """Does the node have a safe representation?""" - if value is None or value is NotImplemented or value is Ellipsis: - return True - if isinstance(value, (bool, int, long, float, complex, basestring, - xrange, Markup)): - return True - if isinstance(value, (tuple, list, set, frozenset)): - for item in value: - if not has_safe_repr(item): - return False - return True - elif isinstance(value, dict): - for key, value in value.iteritems(): - if not has_safe_repr(key): - return False - if not has_safe_repr(value): - return False - return True - return False - - -def find_undeclared(nodes, names): - """Check if the names passed are accessed undeclared. The return value - is a set of all the undeclared names from the sequence of names found. - """ - visitor = UndeclaredNameVisitor(names) - try: - for node in nodes: - visitor.visit(node) - except VisitorExit: - pass - return visitor.undeclared - - -class Identifiers(object): - """Tracks the status of identifiers in frames.""" - - def __init__(self): - # variables that are known to be declared (probably from outer - # frames or because they are special for the frame) - self.declared = set() - - # undeclared variables from outer scopes - self.outer_undeclared = set() - - # names that are accessed without being explicitly declared by - # this one or any of the outer scopes. Names can appear both in - # declared and undeclared. - self.undeclared = set() - - # names that are declared locally - self.declared_locally = set() - - # names that are declared by parameters - self.declared_parameter = set() - - def add_special(self, name): - """Register a special name like `loop`.""" - self.undeclared.discard(name) - self.declared.add(name) - - def is_declared(self, name, local_only=False): - """Check if a name is declared in this or an outer scope.""" - if name in self.declared_locally or name in self.declared_parameter: - return True - if local_only: - return False - return name in self.declared - - def copy(self): - return deepcopy(self) - - -class Frame(object): - """Holds compile time information for us.""" - - def __init__(self, eval_ctx, parent=None): - self.eval_ctx = eval_ctx - self.identifiers = Identifiers() - - # a toplevel frame is the root + soft frames such as if conditions. - self.toplevel = False - - # the root frame is basically just the outermost frame, so no if - # conditions. This information is used to optimize inheritance - # situations. - self.rootlevel = False - - # in some dynamic inheritance situations the compiler needs to add - # write tests around output statements. - self.require_output_check = parent and parent.require_output_check - - # inside some tags we are using a buffer rather than yield statements. - # this for example affects {% filter %} or {% macro %}. If a frame - # is buffered this variable points to the name of the list used as - # buffer. - self.buffer = None - - # the name of the block we're in, otherwise None. - self.block = parent and parent.block or None - - # a set of actually assigned names - self.assigned_names = set() - - # the parent of this frame - self.parent = parent - - if parent is not None: - self.identifiers.declared.update( - parent.identifiers.declared | - parent.identifiers.declared_parameter | - parent.assigned_names - ) - self.identifiers.outer_undeclared.update( - parent.identifiers.undeclared - - self.identifiers.declared - ) - self.buffer = parent.buffer - - def copy(self): - """Create a copy of the current one.""" - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.identifiers = object.__new__(self.identifiers.__class__) - rv.identifiers.__dict__.update(self.identifiers.__dict__) - return rv - - def inspect(self, nodes, hard_scope=False): - """Walk the node and check for identifiers. If the scope is hard (eg: - enforce on a python level) overrides from outer scopes are tracked - differently. - """ - visitor = FrameIdentifierVisitor(self.identifiers, hard_scope) - for node in nodes: - visitor.visit(node) - - def find_shadowed(self, extra=()): - """Find all the shadowed names. extra is an iterable of variables - that may be defined with `add_special` which may occour scoped. - """ - i = self.identifiers - return (i.declared | i.outer_undeclared) & \ - (i.declared_locally | i.declared_parameter) | \ - set(x for x in extra if i.is_declared(x)) - - def inner(self): - """Return an inner frame.""" - return Frame(self.eval_ctx, self) - - def soft(self): - """Return a soft frame. A soft frame may not be modified as - standalone thing as it shares the resources with the frame it - was created of, but it's not a rootlevel frame any longer. - """ - rv = self.copy() - rv.rootlevel = False - return rv - - __copy__ = copy - - -class VisitorExit(RuntimeError): - """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" - - -class DependencyFinderVisitor(NodeVisitor): - """A visitor that collects filter and test calls.""" - - def __init__(self): - self.filters = set() - self.tests = set() - - def visit_Filter(self, node): - self.generic_visit(node) - self.filters.add(node.name) - - def visit_Test(self, node): - self.generic_visit(node) - self.tests.add(node.name) - - def visit_Block(self, node): - """Stop visiting at blocks.""" - - -class UndeclaredNameVisitor(NodeVisitor): - """A visitor that checks if a name is accessed without being - declared. This is different from the frame visitor as it will - not stop at closure frames. - """ - - def __init__(self, names): - self.names = set(names) - self.undeclared = set() - - def visit_Name(self, node): - if node.ctx == 'load' and node.name in self.names: - self.undeclared.add(node.name) - if self.undeclared == self.names: - raise VisitorExit() - else: - self.names.discard(node.name) - - def visit_Block(self, node): - """Stop visiting a blocks.""" - - -class FrameIdentifierVisitor(NodeVisitor): - """A visitor for `Frame.inspect`.""" - - def __init__(self, identifiers, hard_scope): - self.identifiers = identifiers - self.hard_scope = hard_scope - - def visit_Name(self, node): - """All assignments to names go through this function.""" - if node.ctx == 'store': - self.identifiers.declared_locally.add(node.name) - elif node.ctx == 'param': - self.identifiers.declared_parameter.add(node.name) - elif node.ctx == 'load' and not \ - self.identifiers.is_declared(node.name, self.hard_scope): - self.identifiers.undeclared.add(node.name) - - def visit_If(self, node): - self.visit(node.test) - real_identifiers = self.identifiers - - old_names = real_identifiers.declared_locally | \ - real_identifiers.declared_parameter - - def inner_visit(nodes): - if not nodes: - return set() - self.identifiers = real_identifiers.copy() - for subnode in nodes: - self.visit(subnode) - rv = self.identifiers.declared_locally - old_names - # we have to remember the undeclared variables of this branch - # because we will have to pull them. - real_identifiers.undeclared.update(self.identifiers.undeclared) - self.identifiers = real_identifiers - return rv - - body = inner_visit(node.body) - else_ = inner_visit(node.else_ or ()) - - # the differences between the two branches are also pulled as - # undeclared variables - real_identifiers.undeclared.update(body.symmetric_difference(else_) - - real_identifiers.declared) - - # remember those that are declared. - real_identifiers.declared_locally.update(body | else_) - - def visit_Macro(self, node): - self.identifiers.declared_locally.add(node.name) - - def visit_Import(self, node): - self.generic_visit(node) - self.identifiers.declared_locally.add(node.target) - - def visit_FromImport(self, node): - self.generic_visit(node) - for name in node.names: - if isinstance(name, tuple): - self.identifiers.declared_locally.add(name[1]) - else: - self.identifiers.declared_locally.add(name) - - def visit_Assign(self, node): - """Visit assignments in the correct order.""" - self.visit(node.node) - self.visit(node.target) - - def visit_For(self, node): - """Visiting stops at for blocks. However the block sequence - is visited as part of the outer scope. - """ - self.visit(node.iter) - - def visit_CallBlock(self, node): - self.visit(node.call) - - def visit_FilterBlock(self, node): - self.visit(node.filter) - - def visit_Scope(self, node): - """Stop visiting at scopes.""" - - def visit_Block(self, node): - """Stop visiting at blocks.""" - - -class CompilerExit(Exception): - """Raised if the compiler encountered a situation where it just - doesn't make sense to further process the code. Any block that - raises such an exception is not further processed. - """ - - -class CodeGenerator(NodeVisitor): - - def __init__(self, environment, name, filename, stream=None, - defer_init=False): - if stream is None: - stream = StringIO() - self.environment = environment - self.name = name - self.filename = filename - self.stream = stream - self.created_block_context = False - self.defer_init = defer_init - - # aliases for imports - self.import_aliases = {} - - # a registry for all blocks. Because blocks are moved out - # into the global python scope they are registered here - self.blocks = {} - - # the number of extends statements so far - self.extends_so_far = 0 - - # some templates have a rootlevel extends. In this case we - # can safely assume that we're a child template and do some - # more optimizations. - self.has_known_extends = False - - # the current line number - self.code_lineno = 1 - - # registry of all filters and tests (global, not block local) - self.tests = {} - self.filters = {} - - # the debug information - self.debug_info = [] - self._write_debug_info = None - - # the number of new lines before the next write() - self._new_lines = 0 - - # the line number of the last written statement - self._last_line = 0 - - # true if nothing was written so far. - self._first_write = True - - # used by the `temporary_identifier` method to get new - # unique, temporary identifier - self._last_identifier = 0 - - # the current indentation - self._indentation = 0 - - # -- Various compilation helpers - - def fail(self, msg, lineno): - """Fail with a :exc:`TemplateAssertionError`.""" - raise TemplateAssertionError(msg, lineno, self.name, self.filename) - - def temporary_identifier(self): - """Get a new unique identifier.""" - self._last_identifier += 1 - return 't_%d' % self._last_identifier - - def buffer(self, frame): - """Enable buffering for the frame from that point onwards.""" - frame.buffer = self.temporary_identifier() - self.writeline('%s = []' % frame.buffer) - - def return_buffer_contents(self, frame): - """Return the buffer contents of the frame.""" - if frame.eval_ctx.volatile: - self.writeline('if context.eval_ctx.autoescape:') - self.indent() - self.writeline('return Markup(concat(%s))' % frame.buffer) - self.outdent() - self.writeline('else:') - self.indent() - self.writeline('return concat(%s)' % frame.buffer) - self.outdent() - elif frame.eval_ctx.autoescape: - self.writeline('return Markup(concat(%s))' % frame.buffer) - else: - self.writeline('return concat(%s)' % frame.buffer) - - def indent(self): - """Indent by one.""" - self._indentation += 1 - - def outdent(self, step=1): - """Outdent by step.""" - self._indentation -= step - - def start_write(self, frame, node=None): - """Yield or write into the frame buffer.""" - if frame.buffer is None: - self.writeline('yield ', node) - else: - self.writeline('%s.append(' % frame.buffer, node) - - def end_write(self, frame): - """End the writing process started by `start_write`.""" - if frame.buffer is not None: - self.write(')') - - def simple_write(self, s, frame, node=None): - """Simple shortcut for start_write + write + end_write.""" - self.start_write(frame, node) - self.write(s) - self.end_write(frame) - - def blockvisit(self, nodes, frame): - """Visit a list of nodes as block in a frame. If the current frame - is no buffer a dummy ``if 0: yield None`` is written automatically - unless the force_generator parameter is set to False. - """ - if frame.buffer is None: - self.writeline('if 0: yield None') - else: - self.writeline('pass') - try: - for node in nodes: - self.visit(node, frame) - except CompilerExit: - pass - - def write(self, x): - """Write a string into the output stream.""" - if self._new_lines: - if not self._first_write: - self.stream.write('\n' * self._new_lines) - self.code_lineno += self._new_lines - if self._write_debug_info is not None: - self.debug_info.append((self._write_debug_info, - self.code_lineno)) - self._write_debug_info = None - self._first_write = False - self.stream.write(' ' * self._indentation) - self._new_lines = 0 - self.stream.write(x) - - def writeline(self, x, node=None, extra=0): - """Combination of newline and write.""" - self.newline(node, extra) - self.write(x) - - def newline(self, node=None, extra=0): - """Add one or more newlines before the next write.""" - self._new_lines = max(self._new_lines, 1 + extra) - if node is not None and node.lineno != self._last_line: - self._write_debug_info = node.lineno - self._last_line = node.lineno - - def signature(self, node, frame, extra_kwargs=None): - """Writes a function call to the stream for the current node. - A leading comma is added automatically. The extra keyword - arguments may not include python keywords otherwise a syntax - error could occour. The extra keyword arguments should be given - as python dict. - """ - # if any of the given keyword arguments is a python keyword - # we have to make sure that no invalid call is created. - kwarg_workaround = False - for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()): - if is_python_keyword(kwarg): - kwarg_workaround = True - break - - for arg in node.args: - self.write(', ') - self.visit(arg, frame) - - if not kwarg_workaround: - for kwarg in node.kwargs: - self.write(', ') - self.visit(kwarg, frame) - if extra_kwargs is not None: - for key, value in extra_kwargs.iteritems(): - self.write(', %s=%s' % (key, value)) - if node.dyn_args: - self.write(', *') - self.visit(node.dyn_args, frame) - - if kwarg_workaround: - if node.dyn_kwargs is not None: - self.write(', **dict({') - else: - self.write(', **{') - for kwarg in node.kwargs: - self.write('%r: ' % kwarg.key) - self.visit(kwarg.value, frame) - self.write(', ') - if extra_kwargs is not None: - for key, value in extra_kwargs.iteritems(): - self.write('%r: %s, ' % (key, value)) - if node.dyn_kwargs is not None: - self.write('}, **') - self.visit(node.dyn_kwargs, frame) - self.write(')') - else: - self.write('}') - - elif node.dyn_kwargs is not None: - self.write(', **') - self.visit(node.dyn_kwargs, frame) - - def pull_locals(self, frame): - """Pull all the references identifiers into the local scope.""" - for name in frame.identifiers.undeclared: - self.writeline('l_%s = context.resolve(%r)' % (name, name)) - - def pull_dependencies(self, nodes): - """Pull all the dependencies.""" - visitor = DependencyFinderVisitor() - for node in nodes: - visitor.visit(node) - for dependency in 'filters', 'tests': - mapping = getattr(self, dependency) - for name in getattr(visitor, dependency): - if name not in mapping: - mapping[name] = self.temporary_identifier() - self.writeline('%s = environment.%s[%r]' % - (mapping[name], dependency, name)) - - def unoptimize_scope(self, frame): - """Disable Python optimizations for the frame.""" - # XXX: this is not that nice but it has no real overhead. It - # mainly works because python finds the locals before dead code - # is removed. If that breaks we have to add a dummy function - # that just accepts the arguments and does nothing. - if frame.identifiers.declared: - self.writeline('%sdummy(%s)' % ( - unoptimize_before_dead_code and 'if 0: ' or '', - ', '.join('l_' + name for name in frame.identifiers.declared) - )) - - def push_scope(self, frame, extra_vars=()): - """This function returns all the shadowed variables in a dict - in the form name: alias and will write the required assignments - into the current scope. No indentation takes place. - - This also predefines locally declared variables from the loop - body because under some circumstances it may be the case that - - `extra_vars` is passed to `Frame.find_shadowed`. - """ - aliases = {} - for name in frame.find_shadowed(extra_vars): - aliases[name] = ident = self.temporary_identifier() - self.writeline('%s = l_%s' % (ident, name)) - to_declare = set() - for name in frame.identifiers.declared_locally: - if name not in aliases: - to_declare.add('l_' + name) - if to_declare: - self.writeline(' = '.join(to_declare) + ' = missing') - return aliases - - def pop_scope(self, aliases, frame): - """Restore all aliases and delete unused variables.""" - for name, alias in aliases.iteritems(): - self.writeline('l_%s = %s' % (name, alias)) - to_delete = set() - for name in frame.identifiers.declared_locally: - if name not in aliases: - to_delete.add('l_' + name) - if to_delete: - # we cannot use the del statement here because enclosed - # scopes can trigger a SyntaxError: - # a = 42; b = lambda: a; del a - self.writeline(' = '.join(to_delete) + ' = missing') - - def function_scoping(self, node, frame, children=None, - find_special=True): - """In Jinja a few statements require the help of anonymous - functions. Those are currently macros and call blocks and in - the future also recursive loops. As there is currently - technical limitation that doesn't allow reading and writing a - variable in a scope where the initial value is coming from an - outer scope, this function tries to fall back with a common - error message. Additionally the frame passed is modified so - that the argumetns are collected and callers are looked up. - - This will return the modified frame. - """ - # we have to iterate twice over it, make sure that works - if children is None: - children = node.iter_child_nodes() - children = list(children) - func_frame = frame.inner() - func_frame.inspect(children, hard_scope=True) - - # variables that are undeclared (accessed before declaration) and - # declared locally *and* part of an outside scope raise a template - # assertion error. Reason: we can't generate reasonable code from - # it without aliasing all the variables. - # this could be fixed in Python 3 where we have the nonlocal - # keyword or if we switch to bytecode generation - overriden_closure_vars = ( - func_frame.identifiers.undeclared & - func_frame.identifiers.declared & - (func_frame.identifiers.declared_locally | - func_frame.identifiers.declared_parameter) - ) - if overriden_closure_vars: - self.fail('It\'s not possible to set and access variables ' - 'derived from an outer scope! (affects: %s)' % - ', '.join(sorted(overriden_closure_vars)), node.lineno) - - # remove variables from a closure from the frame's undeclared - # identifiers. - func_frame.identifiers.undeclared -= ( - func_frame.identifiers.undeclared & - func_frame.identifiers.declared - ) - - # no special variables for this scope, abort early - if not find_special: - return func_frame - - func_frame.accesses_kwargs = False - func_frame.accesses_varargs = False - func_frame.accesses_caller = False - func_frame.arguments = args = ['l_' + x.name for x in node.args] - - undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs')) - - if 'caller' in undeclared: - func_frame.accesses_caller = True - func_frame.identifiers.add_special('caller') - args.append('l_caller') - if 'kwargs' in undeclared: - func_frame.accesses_kwargs = True - func_frame.identifiers.add_special('kwargs') - args.append('l_kwargs') - if 'varargs' in undeclared: - func_frame.accesses_varargs = True - func_frame.identifiers.add_special('varargs') - args.append('l_varargs') - return func_frame - - def macro_body(self, node, frame, children=None): - """Dump the function def of a macro or call block.""" - frame = self.function_scoping(node, frame, children) - # macros are delayed, they never require output checks - frame.require_output_check = False - args = frame.arguments - # XXX: this is an ugly fix for the loop nesting bug - # (tests.test_old_bugs.test_loop_call_bug). This works around - # a identifier nesting problem we have in general. It's just more - # likely to happen in loops which is why we work around it. The - # real solution would be "nonlocal" all the identifiers that are - # leaking into a new python frame and might be used both unassigned - # and assigned. - if 'loop' in frame.identifiers.declared: - args = args + ['l_loop=l_loop'] - self.writeline('def macro(%s):' % ', '.join(args), node) - self.indent() - self.buffer(frame) - self.pull_locals(frame) - self.blockvisit(node.body, frame) - self.return_buffer_contents(frame) - self.outdent() - return frame - - def macro_def(self, node, frame): - """Dump the macro definition for the def created by macro_body.""" - arg_tuple = ', '.join(repr(x.name) for x in node.args) - name = getattr(node, 'name', None) - if len(node.args) == 1: - arg_tuple += ',' - self.write('Macro(environment, macro, %r, (%s), (' % - (name, arg_tuple)) - for arg in node.defaults: - self.visit(arg, frame) - self.write(', ') - self.write('), %r, %r, %r)' % ( - bool(frame.accesses_kwargs), - bool(frame.accesses_varargs), - bool(frame.accesses_caller) - )) - - def position(self, node): - """Return a human readable position for the node.""" - rv = 'line %d' % node.lineno - if self.name is not None: - rv += ' in ' + repr(self.name) - return rv - - # -- Statement Visitors - - def visit_Template(self, node, frame=None): - assert frame is None, 'no root frame allowed' - eval_ctx = EvalContext(self.environment, self.name) - - from jinja2.runtime import __all__ as exported - self.writeline('from __future__ import division') - self.writeline('from jinja2.runtime import ' + ', '.join(exported)) - if not unoptimize_before_dead_code: - self.writeline('dummy = lambda *x: None') - - # if we want a deferred initialization we cannot move the - # environment into a local name - envenv = not self.defer_init and ', environment=environment' or '' - - # do we have an extends tag at all? If not, we can save some - # overhead by just not processing any inheritance code. - have_extends = node.find(nodes.Extends) is not None - - # find all blocks - for block in node.find_all(nodes.Block): - if block.name in self.blocks: - self.fail('block %r defined twice' % block.name, block.lineno) - self.blocks[block.name] = block - - # find all imports and import them - for import_ in node.find_all(nodes.ImportedName): - if import_.importname not in self.import_aliases: - imp = import_.importname - self.import_aliases[imp] = alias = self.temporary_identifier() - if '.' in imp: - module, obj = imp.rsplit('.', 1) - self.writeline('from %s import %s as %s' % - (module, obj, alias)) - else: - self.writeline('import %s as %s' % (imp, alias)) - - # add the load name - self.writeline('name = %r' % self.name) - - # generate the root render function. - self.writeline('def root(context%s):' % envenv, extra=1) - - # process the root - frame = Frame(eval_ctx) - frame.inspect(node.body) - frame.toplevel = frame.rootlevel = True - frame.require_output_check = have_extends and not self.has_known_extends - self.indent() - if have_extends: - self.writeline('parent_template = None') - if 'self' in find_undeclared(node.body, ('self',)): - frame.identifiers.add_special('self') - self.writeline('l_self = TemplateReference(context)') - self.pull_locals(frame) - self.pull_dependencies(node.body) - self.blockvisit(node.body, frame) - self.outdent() - - # make sure that the parent root is called. - if have_extends: - if not self.has_known_extends: - self.indent() - self.writeline('if parent_template is not None:') - self.indent() - self.writeline('for event in parent_template.' - 'root_render_func(context):') - self.indent() - self.writeline('yield event') - self.outdent(2 + (not self.has_known_extends)) - - # at this point we now have the blocks collected and can visit them too. - for name, block in self.blocks.iteritems(): - block_frame = Frame(eval_ctx) - block_frame.inspect(block.body) - block_frame.block = name - self.writeline('def block_%s(context%s):' % (name, envenv), - block, 1) - self.indent() - undeclared = find_undeclared(block.body, ('self', 'super')) - if 'self' in undeclared: - block_frame.identifiers.add_special('self') - self.writeline('l_self = TemplateReference(context)') - if 'super' in undeclared: - block_frame.identifiers.add_special('super') - self.writeline('l_super = context.super(%r, ' - 'block_%s)' % (name, name)) - self.pull_locals(block_frame) - self.pull_dependencies(block.body) - self.blockvisit(block.body, block_frame) - self.outdent() - - self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x) - for x in self.blocks), - extra=1) - - # add a function that returns the debug info - self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x - in self.debug_info)) - - def visit_Block(self, node, frame): - """Call a block and register it for the template.""" - level = 1 - if frame.toplevel: - # if we know that we are a child template, there is no need to - # check if we are one - if self.has_known_extends: - return - if self.extends_so_far > 0: - self.writeline('if parent_template is None:') - self.indent() - level += 1 - context = node.scoped and 'context.derived(locals())' or 'context' - self.writeline('for event in context.blocks[%r][0](%s):' % ( - node.name, context), node) - self.indent() - self.simple_write('event', frame) - self.outdent(level) - - def visit_Extends(self, node, frame): - """Calls the extender.""" - if not frame.toplevel: - self.fail('cannot use extend from a non top-level scope', - node.lineno) - - # if the number of extends statements in general is zero so - # far, we don't have to add a check if something extended - # the template before this one. - if self.extends_so_far > 0: - - # if we have a known extends we just add a template runtime - # error into the generated code. We could catch that at compile - # time too, but i welcome it not to confuse users by throwing the - # same error at different times just "because we can". - if not self.has_known_extends: - self.writeline('if parent_template is not None:') - self.indent() - self.writeline('raise TemplateRuntimeError(%r)' % - 'extended multiple times') - self.outdent() - - # if we have a known extends already we don't need that code here - # as we know that the template execution will end here. - if self.has_known_extends: - raise CompilerExit() - - self.writeline('parent_template = environment.get_template(', node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) - self.writeline('for name, parent_block in parent_template.' - 'blocks.%s():' % dict_item_iter) - self.indent() - self.writeline('context.blocks.setdefault(name, []).' - 'append(parent_block)') - self.outdent() - - # if this extends statement was in the root level we can take - # advantage of that information and simplify the generated code - # in the top level from this point onwards - if frame.rootlevel: - self.has_known_extends = True - - # and now we have one more - self.extends_so_far += 1 - - def visit_Include(self, node, frame): - """Handles includes.""" - if node.with_context: - self.unoptimize_scope(frame) - if node.ignore_missing: - self.writeline('try:') - self.indent() - - func_name = 'get_or_select_template' - if isinstance(node.template, nodes.Const): - if isinstance(node.template.value, basestring): - func_name = 'get_template' - elif isinstance(node.template.value, (tuple, list)): - func_name = 'select_template' - elif isinstance(node.template, (nodes.Tuple, nodes.List)): - func_name = 'select_template' - - self.writeline('template = environment.%s(' % func_name, node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) - if node.ignore_missing: - self.outdent() - self.writeline('except TemplateNotFound:') - self.indent() - self.writeline('pass') - self.outdent() - self.writeline('else:') - self.indent() - - if node.with_context: - self.writeline('for event in template.root_render_func(' - 'template.new_context(context.parent, True, ' - 'locals())):') - else: - self.writeline('for event in template.module._body_stream:') - - self.indent() - self.simple_write('event', frame) - self.outdent() - - if node.ignore_missing: - self.outdent() - - def visit_Import(self, node, frame): - """Visit regular imports.""" - if node.with_context: - self.unoptimize_scope(frame) - self.writeline('l_%s = ' % node.target, node) - if frame.toplevel: - self.write('context.vars[%r] = ' % node.target) - self.write('environment.get_template(') - self.visit(node.template, frame) - self.write(', %r).' % self.name) - if node.with_context: - self.write('make_module(context.parent, True, locals())') - else: - self.write('module') - if frame.toplevel and not node.target.startswith('_'): - self.writeline('context.exported_vars.discard(%r)' % node.target) - frame.assigned_names.add(node.target) - - def visit_FromImport(self, node, frame): - """Visit named imports.""" - self.newline(node) - self.write('included_template = environment.get_template(') - self.visit(node.template, frame) - self.write(', %r).' % self.name) - if node.with_context: - self.write('make_module(context.parent, True)') - else: - self.write('module') - - var_names = [] - discarded_names = [] - for name in node.names: - if isinstance(name, tuple): - name, alias = name - else: - alias = name - self.writeline('l_%s = getattr(included_template, ' - '%r, missing)' % (alias, name)) - self.writeline('if l_%s is missing:' % alias) - self.indent() - self.writeline('l_%s = environment.undefined(%r %% ' - 'included_template.__name__, ' - 'name=%r)' % - (alias, 'the template %%r (imported on %s) does ' - 'not export the requested name %s' % ( - self.position(node), - repr(name) - ), name)) - self.outdent() - if frame.toplevel: - var_names.append(alias) - if not alias.startswith('_'): - discarded_names.append(alias) - frame.assigned_names.add(alias) - - if var_names: - if len(var_names) == 1: - name = var_names[0] - self.writeline('context.vars[%r] = l_%s' % (name, name)) - else: - self.writeline('context.vars.update({%s})' % ', '.join( - '%r: l_%s' % (name, name) for name in var_names - )) - if discarded_names: - if len(discarded_names) == 1: - self.writeline('context.exported_vars.discard(%r)' % - discarded_names[0]) - else: - self.writeline('context.exported_vars.difference_' - 'update((%s))' % ', '.join(map(repr, discarded_names))) - - def visit_For(self, node, frame): - # when calculating the nodes for the inner frame we have to exclude - # the iterator contents from it - children = node.iter_child_nodes(exclude=('iter',)) - if node.recursive: - loop_frame = self.function_scoping(node, frame, children, - find_special=False) - else: - loop_frame = frame.inner() - loop_frame.inspect(children) - - # try to figure out if we have an extended loop. An extended loop - # is necessary if the loop is in recursive mode if the special loop - # variable is accessed in the body. - extended_loop = node.recursive or 'loop' in \ - find_undeclared(node.iter_child_nodes( - only=('body',)), ('loop',)) - - # if we don't have an recursive loop we have to find the shadowed - # variables at that point. Because loops can be nested but the loop - # variable is a special one we have to enforce aliasing for it. - if not node.recursive: - aliases = self.push_scope(loop_frame, ('loop',)) - - # otherwise we set up a buffer and add a function def - else: - self.writeline('def loop(reciter, loop_render_func):', node) - self.indent() - self.buffer(loop_frame) - aliases = {} - - # make sure the loop variable is a special one and raise a template - # assertion error if a loop tries to write to loop - if extended_loop: - loop_frame.identifiers.add_special('loop') - for name in node.find_all(nodes.Name): - if name.ctx == 'store' and name.name == 'loop': - self.fail('Can\'t assign to special loop variable ' - 'in for-loop target', name.lineno) - - self.pull_locals(loop_frame) - if node.else_: - iteration_indicator = self.temporary_identifier() - self.writeline('%s = 1' % iteration_indicator) - - # Create a fake parent loop if the else or test section of a - # loop is accessing the special loop variable and no parent loop - # exists. - if 'loop' not in aliases and 'loop' in find_undeclared( - node.iter_child_nodes(only=('else_', 'test')), ('loop',)): - self.writeline("l_loop = environment.undefined(%r, name='loop')" % - ("'loop' is undefined. the filter section of a loop as well " - "as the else block doesn't have access to the special 'loop'" - " variable of the current loop. Because there is no parent " - "loop it's undefined. Happened in loop on %s" % - self.position(node))) - - self.writeline('for ', node) - self.visit(node.target, loop_frame) - self.write(extended_loop and ', l_loop in LoopContext(' or ' in ') - - # if we have an extened loop and a node test, we filter in the - # "outer frame". - if extended_loop and node.test is not None: - self.write('(') - self.visit(node.target, loop_frame) - self.write(' for ') - self.visit(node.target, loop_frame) - self.write(' in ') - if node.recursive: - self.write('reciter') - else: - self.visit(node.iter, loop_frame) - self.write(' if (') - test_frame = loop_frame.copy() - self.visit(node.test, test_frame) - self.write('))') - - elif node.recursive: - self.write('reciter') - else: - self.visit(node.iter, loop_frame) - - if node.recursive: - self.write(', recurse=loop_render_func):') - else: - self.write(extended_loop and '):' or ':') - - # tests in not extended loops become a continue - if not extended_loop and node.test is not None: - self.indent() - self.writeline('if not ') - self.visit(node.test, loop_frame) - self.write(':') - self.indent() - self.writeline('continue') - self.outdent(2) - - self.indent() - self.blockvisit(node.body, loop_frame) - if node.else_: - self.writeline('%s = 0' % iteration_indicator) - self.outdent() - - if node.else_: - self.writeline('if %s:' % iteration_indicator) - self.indent() - self.blockvisit(node.else_, loop_frame) - self.outdent() - - # reset the aliases if there are any. - if not node.recursive: - self.pop_scope(aliases, loop_frame) - - # if the node was recursive we have to return the buffer contents - # and start the iteration code - if node.recursive: - self.return_buffer_contents(loop_frame) - self.outdent() - self.start_write(frame, node) - self.write('loop(') - self.visit(node.iter, frame) - self.write(', loop)') - self.end_write(frame) - - def visit_If(self, node, frame): - if_frame = frame.soft() - self.writeline('if ', node) - self.visit(node.test, if_frame) - self.write(':') - self.indent() - self.blockvisit(node.body, if_frame) - self.outdent() - if node.else_: - self.writeline('else:') - self.indent() - self.blockvisit(node.else_, if_frame) - self.outdent() - - def visit_Macro(self, node, frame): - macro_frame = self.macro_body(node, frame) - self.newline() - if frame.toplevel: - if not node.name.startswith('_'): - self.write('context.exported_vars.add(%r)' % node.name) - self.writeline('context.vars[%r] = ' % node.name) - self.write('l_%s = ' % node.name) - self.macro_def(node, macro_frame) - frame.assigned_names.add(node.name) - - def visit_CallBlock(self, node, frame): - children = node.iter_child_nodes(exclude=('call',)) - call_frame = self.macro_body(node, frame, children) - self.writeline('caller = ') - self.macro_def(node, call_frame) - self.start_write(frame, node) - self.visit_Call(node.call, call_frame, forward_caller=True) - self.end_write(frame) - - def visit_FilterBlock(self, node, frame): - filter_frame = frame.inner() - filter_frame.inspect(node.iter_child_nodes()) - aliases = self.push_scope(filter_frame) - self.pull_locals(filter_frame) - self.buffer(filter_frame) - self.blockvisit(node.body, filter_frame) - self.start_write(frame, node) - self.visit_Filter(node.filter, filter_frame) - self.end_write(frame) - self.pop_scope(aliases, filter_frame) - - def visit_ExprStmt(self, node, frame): - self.newline(node) - self.visit(node.node, frame) - - def visit_Output(self, node, frame): - # if we have a known extends statement, we don't output anything - # if we are in a require_output_check section - if self.has_known_extends and frame.require_output_check: - return - - if self.environment.finalize: - finalize = lambda x: unicode(self.environment.finalize(x)) - else: - finalize = unicode - - # if we are inside a frame that requires output checking, we do so - outdent_later = False - if frame.require_output_check: - self.writeline('if parent_template is None:') - self.indent() - outdent_later = True - - # try to evaluate as many chunks as possible into a static - # string at compile time. - body = [] - for child in node.nodes: - try: - const = child.as_const(frame.eval_ctx) - except nodes.Impossible: - body.append(child) - continue - # the frame can't be volatile here, becaus otherwise the - # as_const() function would raise an Impossible exception - # at that point. - try: - if frame.eval_ctx.autoescape: - if hasattr(const, '__html__'): - const = const.__html__() - else: - const = escape(const) - const = finalize(const) - except: - # if something goes wrong here we evaluate the node - # at runtime for easier debugging - body.append(child) - continue - if body and isinstance(body[-1], list): - body[-1].append(const) - else: - body.append([const]) - - # if we have less than 3 nodes or a buffer we yield or extend/append - if len(body) < 3 or frame.buffer is not None: - if frame.buffer is not None: - # for one item we append, for more we extend - if len(body) == 1: - self.writeline('%s.append(' % frame.buffer) - else: - self.writeline('%s.extend((' % frame.buffer) - self.indent() - for item in body: - if isinstance(item, list): - val = repr(concat(item)) - if frame.buffer is None: - self.writeline('yield ' + val) - else: - self.writeline(val + ', ') - else: - if frame.buffer is None: - self.writeline('yield ', item) - else: - self.newline(item) - close = 1 - if frame.eval_ctx.volatile: - self.write('(context.eval_ctx.autoescape and' - ' escape or to_string)(') - elif frame.eval_ctx.autoescape: - self.write('escape(') - else: - self.write('to_string(') - if self.environment.finalize is not None: - self.write('environment.finalize(') - close += 1 - self.visit(item, frame) - self.write(')' * close) - if frame.buffer is not None: - self.write(', ') - if frame.buffer is not None: - # close the open parentheses - self.outdent() - self.writeline(len(body) == 1 and ')' or '))') - - # otherwise we create a format string as this is faster in that case - else: - format = [] - arguments = [] - for item in body: - if isinstance(item, list): - format.append(concat(item).replace('%', '%%')) - else: - format.append('%s') - arguments.append(item) - self.writeline('yield ') - self.write(repr(concat(format)) + ' % (') - idx = -1 - self.indent() - for argument in arguments: - self.newline(argument) - close = 0 - if frame.eval_ctx.volatile: - self.write('(context.eval_ctx.autoescape and' - ' escape or to_string)(') - close += 1 - elif frame.eval_ctx.autoescape: - self.write('escape(') - close += 1 - if self.environment.finalize is not None: - self.write('environment.finalize(') - close += 1 - self.visit(argument, frame) - self.write(')' * close + ', ') - self.outdent() - self.writeline(')') - - if outdent_later: - self.outdent() - - def visit_Assign(self, node, frame): - self.newline(node) - # toplevel assignments however go into the local namespace and - # the current template's context. We create a copy of the frame - # here and add a set so that the Name visitor can add the assigned - # names here. - if frame.toplevel: - assignment_frame = frame.copy() - assignment_frame.toplevel_assignments = set() - else: - assignment_frame = frame - self.visit(node.target, assignment_frame) - self.write(' = ') - self.visit(node.node, frame) - - # make sure toplevel assignments are added to the context. - if frame.toplevel: - public_names = [x for x in assignment_frame.toplevel_assignments - if not x.startswith('_')] - if len(assignment_frame.toplevel_assignments) == 1: - name = next(iter(assignment_frame.toplevel_assignments)) - self.writeline('context.vars[%r] = l_%s' % (name, name)) - else: - self.writeline('context.vars.update({') - for idx, name in enumerate(assignment_frame.toplevel_assignments): - if idx: - self.write(', ') - self.write('%r: l_%s' % (name, name)) - self.write('})') - if public_names: - if len(public_names) == 1: - self.writeline('context.exported_vars.add(%r)' % - public_names[0]) - else: - self.writeline('context.exported_vars.update((%s))' % - ', '.join(map(repr, public_names))) - - # -- Expression Visitors - - def visit_Name(self, node, frame): - if node.ctx == 'store' and frame.toplevel: - frame.toplevel_assignments.add(node.name) - self.write('l_' + node.name) - frame.assigned_names.add(node.name) - - def visit_Const(self, node, frame): - val = node.value - if isinstance(val, float): - self.write(str(val)) - else: - self.write(repr(val)) - - def visit_TemplateData(self, node, frame): - try: - self.write(repr(node.as_const(frame.eval_ctx))) - except nodes.Impossible: - self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)' - % node.data) - - def visit_Tuple(self, node, frame): - self.write('(') - idx = -1 - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item, frame) - self.write(idx == 0 and ',)' or ')') - - def visit_List(self, node, frame): - self.write('[') - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item, frame) - self.write(']') - - def visit_Dict(self, node, frame): - self.write('{') - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item.key, frame) - self.write(': ') - self.visit(item.value, frame) - self.write('}') - - def binop(operator): - def visitor(self, node, frame): - self.write('(') - self.visit(node.left, frame) - self.write(' %s ' % operator) - self.visit(node.right, frame) - self.write(')') - return visitor - - def uaop(operator): - def visitor(self, node, frame): - self.write('(' + operator) - self.visit(node.node, frame) - self.write(')') - return visitor - - visit_Add = binop('+') - visit_Sub = binop('-') - visit_Mul = binop('*') - visit_Div = binop('/') - visit_FloorDiv = binop('//') - visit_Pow = binop('**') - visit_Mod = binop('%') - visit_And = binop('and') - visit_Or = binop('or') - visit_Pos = uaop('+') - visit_Neg = uaop('-') - visit_Not = uaop('not ') - del binop, uaop - - def visit_Concat(self, node, frame): - if frame.eval_ctx.volatile: - func_name = '(context.eval_ctx.volatile and' \ - ' markup_join or unicode_join)' - elif frame.eval_ctx.autoescape: - func_name = 'markup_join' - else: - func_name = 'unicode_join' - self.write('%s((' % func_name) - for arg in node.nodes: - self.visit(arg, frame) - self.write(', ') - self.write('))') - - def visit_Compare(self, node, frame): - self.visit(node.expr, frame) - for op in node.ops: - self.visit(op, frame) - - def visit_Operand(self, node, frame): - self.write(' %s ' % operators[node.op]) - self.visit(node.expr, frame) - - def visit_Getattr(self, node, frame): - self.write('environment.getattr(') - self.visit(node.node, frame) - self.write(', %r)' % node.attr) - - def visit_Getitem(self, node, frame): - # slices bypass the environment getitem method. - if isinstance(node.arg, nodes.Slice): - self.visit(node.node, frame) - self.write('[') - self.visit(node.arg, frame) - self.write(']') - else: - self.write('environment.getitem(') - self.visit(node.node, frame) - self.write(', ') - self.visit(node.arg, frame) - self.write(')') - - def visit_Slice(self, node, frame): - if node.start is not None: - self.visit(node.start, frame) - self.write(':') - if node.stop is not None: - self.visit(node.stop, frame) - if node.step is not None: - self.write(':') - self.visit(node.step, frame) - - def visit_Filter(self, node, frame): - self.write(self.filters[node.name] + '(') - func = self.environment.filters.get(node.name) - if func is None: - self.fail('no filter named %r' % node.name, node.lineno) - if getattr(func, 'contextfilter', False): - self.write('context, ') - elif getattr(func, 'evalcontextfilter', False): - self.write('context.eval_ctx, ') - elif getattr(func, 'environmentfilter', False): - self.write('environment, ') - - # if the filter node is None we are inside a filter block - # and want to write to the current buffer - if node.node is not None: - self.visit(node.node, frame) - elif frame.eval_ctx.volatile: - self.write('(context.eval_ctx.autoescape and' - ' Markup(concat(%s)) or concat(%s))' % - (frame.buffer, frame.buffer)) - elif frame.eval_ctx.autoescape: - self.write('Markup(concat(%s))' % frame.buffer) - else: - self.write('concat(%s)' % frame.buffer) - self.signature(node, frame) - self.write(')') - - def visit_Test(self, node, frame): - self.write(self.tests[node.name] + '(') - if node.name not in self.environment.tests: - self.fail('no test named %r' % node.name, node.lineno) - self.visit(node.node, frame) - self.signature(node, frame) - self.write(')') - - def visit_CondExpr(self, node, frame): - def write_expr2(): - if node.expr2 is not None: - return self.visit(node.expr2, frame) - self.write('environment.undefined(%r)' % ('the inline if-' - 'expression on %s evaluated to false and ' - 'no else section was defined.' % self.position(node))) - - if not have_condexpr: - self.write('((') - self.visit(node.test, frame) - self.write(') and (') - self.visit(node.expr1, frame) - self.write(',) or (') - write_expr2() - self.write(',))[0]') - else: - self.write('(') - self.visit(node.expr1, frame) - self.write(' if ') - self.visit(node.test, frame) - self.write(' else ') - write_expr2() - self.write(')') - - def visit_Call(self, node, frame, forward_caller=False): - if self.environment.sandboxed: - self.write('environment.call(context, ') - else: - self.write('context.call(') - self.visit(node.node, frame) - extra_kwargs = forward_caller and {'caller': 'caller'} or None - self.signature(node, frame, extra_kwargs) - self.write(')') - - def visit_Keyword(self, node, frame): - self.write(node.key + '=') - self.visit(node.value, frame) - - # -- Unused nodes for extensions - - def visit_MarkSafe(self, node, frame): - self.write('Markup(') - self.visit(node.expr, frame) - self.write(')') - - def visit_MarkSafeIfAutoescape(self, node, frame): - self.write('(context.eval_ctx.autoescape and Markup or identity)(') - self.visit(node.expr, frame) - self.write(')') - - def visit_EnvironmentAttribute(self, node, frame): - self.write('environment.' + node.name) - - def visit_ExtensionAttribute(self, node, frame): - self.write('environment.extensions[%r].%s' % (node.identifier, node.name)) - - def visit_ImportedName(self, node, frame): - self.write(self.import_aliases[node.importname]) - - def visit_InternalName(self, node, frame): - self.write(node.name) - - def visit_ContextReference(self, node, frame): - self.write('context') - - def visit_Continue(self, node, frame): - self.writeline('continue', node) - - def visit_Break(self, node, frame): - self.writeline('break', node) - - def visit_Scope(self, node, frame): - scope_frame = frame.inner() - scope_frame.inspect(node.iter_child_nodes()) - aliases = self.push_scope(scope_frame) - self.pull_locals(scope_frame) - self.blockvisit(node.body, scope_frame) - self.pop_scope(aliases, scope_frame) - - def visit_EvalContextModifier(self, node, frame): - for keyword in node.options: - self.writeline('context.eval_ctx.%s = ' % keyword.key) - self.visit(keyword.value, frame) - try: - val = keyword.value.as_const(frame.eval_ctx) - except nodes.Impossible: - frame.eval_ctx.volatile = True - else: - setattr(frame.eval_ctx, keyword.key, val) - - def visit_ScopedEvalContextModifier(self, node, frame): - old_ctx_name = self.temporary_identifier() - safed_ctx = frame.eval_ctx.save() - self.writeline('%s = context.eval_ctx.save()' % old_ctx_name) - self.visit_EvalContextModifier(node, frame) - for child in node.body: - self.visit(child, frame) - frame.eval_ctx.revert(safed_ctx) - self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name) diff --git a/module/lib/jinja2/constants.py b/module/lib/jinja2/constants.py deleted file mode 100644 index cab203cc7..000000000 --- a/module/lib/jinja2/constants.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja.constants - ~~~~~~~~~~~~~~~ - - Various constants. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" - - -#: list of lorem ipsum words used by the lipsum() helper function -LOREM_IPSUM_WORDS = u'''\ -a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at -auctor augue bibendum blandit class commodo condimentum congue consectetuer -consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus -diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend -elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames -faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac -hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum -justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem -luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie -mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non -nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque -penatibus per pharetra phasellus placerat platea porta porttitor posuere -potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus -ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit -sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor -tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices -ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus -viverra volutpat vulputate''' diff --git a/module/lib/jinja2/debug.py b/module/lib/jinja2/debug.py deleted file mode 100644 index eb15456d1..000000000 --- a/module/lib/jinja2/debug.py +++ /dev/null @@ -1,308 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.debug - ~~~~~~~~~~~~ - - Implements the debug interface for Jinja. This module does some pretty - ugly stuff with the Python traceback system in order to achieve tracebacks - with correct line numbers, locals and contents. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import sys -import traceback -from jinja2.utils import CodeType, missing, internal_code -from jinja2.exceptions import TemplateSyntaxError - - -# how does the raise helper look like? -try: - exec "raise TypeError, 'foo'" -except SyntaxError: - raise_helper = 'raise __jinja_exception__[1]' -except TypeError: - raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' - - -class TracebackFrameProxy(object): - """Proxies a traceback frame.""" - - def __init__(self, tb): - self.tb = tb - - def _set_tb_next(self, next): - if tb_set_next is not None: - tb_set_next(self.tb, next and next.tb or None) - self._tb_next = next - - def _get_tb_next(self): - return self._tb_next - - tb_next = property(_get_tb_next, _set_tb_next) - del _get_tb_next, _set_tb_next - - @property - def is_jinja_frame(self): - return '__jinja_template__' in self.tb.tb_frame.f_globals - - def __getattr__(self, name): - return getattr(self.tb, name) - - -class ProcessedTraceback(object): - """Holds a Jinja preprocessed traceback for priting or reraising.""" - - def __init__(self, exc_type, exc_value, frames): - assert frames, 'no frames for this traceback?' - self.exc_type = exc_type - self.exc_value = exc_value - self.frames = frames - - def chain_frames(self): - """Chains the frames. Requires ctypes or the debugsupport extension.""" - prev_tb = None - for tb in self.frames: - if prev_tb is not None: - prev_tb.tb_next = tb - prev_tb = tb - prev_tb.tb_next = None - - def render_as_text(self, limit=None): - """Return a string with the traceback.""" - lines = traceback.format_exception(self.exc_type, self.exc_value, - self.frames[0], limit=limit) - return ''.join(lines).rstrip() - - def render_as_html(self, full=False): - """Return a unicode string with the traceback as rendered HTML.""" - from jinja2.debugrenderer import render_traceback - return u'%s\n\n<!--\n%s\n-->' % ( - render_traceback(self, full=full), - self.render_as_text().decode('utf-8', 'replace') - ) - - @property - def is_template_syntax_error(self): - """`True` if this is a template syntax error.""" - return isinstance(self.exc_value, TemplateSyntaxError) - - @property - def exc_info(self): - """Exception info tuple with a proxy around the frame objects.""" - return self.exc_type, self.exc_value, self.frames[0] - - @property - def standard_exc_info(self): - """Standard python exc_info for re-raising""" - return self.exc_type, self.exc_value, self.frames[0].tb - - -def make_traceback(exc_info, source_hint=None): - """Creates a processed traceback object from the exc_info.""" - exc_type, exc_value, tb = exc_info - if isinstance(exc_value, TemplateSyntaxError): - exc_info = translate_syntax_error(exc_value, source_hint) - initial_skip = 0 - else: - initial_skip = 1 - return translate_exception(exc_info, initial_skip) - - -def translate_syntax_error(error, source=None): - """Rewrites a syntax error to please traceback systems.""" - error.source = source - error.translated = True - exc_info = (error.__class__, error, None) - filename = error.filename - if filename is None: - filename = '<unknown>' - return fake_exc_info(exc_info, filename, error.lineno) - - -def translate_exception(exc_info, initial_skip=0): - """If passed an exc_info it will automatically rewrite the exceptions - all the way down to the correct line numbers and frames. - """ - tb = exc_info[2] - frames = [] - - # skip some internal frames if wanted - for x in xrange(initial_skip): - if tb is not None: - tb = tb.tb_next - initial_tb = tb - - while tb is not None: - # skip frames decorated with @internalcode. These are internal - # calls we can't avoid and that are useless in template debugging - # output. - if tb.tb_frame.f_code in internal_code: - tb = tb.tb_next - continue - - # save a reference to the next frame if we override the current - # one with a faked one. - next = tb.tb_next - - # fake template exceptions - template = tb.tb_frame.f_globals.get('__jinja_template__') - if template is not None: - lineno = template.get_corresponding_lineno(tb.tb_lineno) - tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, - lineno)[2] - - frames.append(TracebackFrameProxy(tb)) - tb = next - - # if we don't have any exceptions in the frames left, we have to - # reraise it unchanged. - # XXX: can we backup here? when could this happen? - if not frames: - raise exc_info[0], exc_info[1], exc_info[2] - - traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames) - if tb_set_next is not None: - traceback.chain_frames() - return traceback - - -def fake_exc_info(exc_info, filename, lineno): - """Helper for `translate_exception`.""" - exc_type, exc_value, tb = exc_info - - # figure the real context out - if tb is not None: - real_locals = tb.tb_frame.f_locals.copy() - ctx = real_locals.get('context') - if ctx: - locals = ctx.get_all() - else: - locals = {} - for name, value in real_locals.iteritems(): - if name.startswith('l_') and value is not missing: - locals[name[2:]] = value - - # if there is a local called __jinja_exception__, we get - # rid of it to not break the debug functionality. - locals.pop('__jinja_exception__', None) - else: - locals = {} - - # assamble fake globals we need - globals = { - '__name__': filename, - '__file__': filename, - '__jinja_exception__': exc_info[:2], - - # we don't want to keep the reference to the template around - # to not cause circular dependencies, but we mark it as Jinja - # frame for the ProcessedTraceback - '__jinja_template__': None - } - - # and fake the exception - code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') - - # if it's possible, change the name of the code. This won't work - # on some python environments such as google appengine - try: - if tb is None: - location = 'template' - else: - function = tb.tb_frame.f_code.co_name - if function == 'root': - location = 'top-level template code' - elif function.startswith('block_'): - location = 'block "%s"' % function[6:] - else: - location = 'template' - code = CodeType(0, code.co_nlocals, code.co_stacksize, - code.co_flags, code.co_code, code.co_consts, - code.co_names, code.co_varnames, filename, - location, code.co_firstlineno, - code.co_lnotab, (), ()) - except: - pass - - # execute the code and catch the new traceback - try: - exec code in globals, locals - except: - exc_info = sys.exc_info() - new_tb = exc_info[2].tb_next - - # return without this frame - return exc_info[:2] + (new_tb,) - - -def _init_ugly_crap(): - """This function implements a few ugly things so that we can patch the - traceback objects. The function returned allows resetting `tb_next` on - any python traceback object. - """ - import ctypes - from types import TracebackType - - # figure out side of _Py_ssize_t - if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): - _Py_ssize_t = ctypes.c_int64 - else: - _Py_ssize_t = ctypes.c_int - - # regular python - class _PyObject(ctypes.Structure): - pass - _PyObject._fields_ = [ - ('ob_refcnt', _Py_ssize_t), - ('ob_type', ctypes.POINTER(_PyObject)) - ] - - # python with trace - if hasattr(sys, 'getobjects'): - class _PyObject(ctypes.Structure): - pass - _PyObject._fields_ = [ - ('_ob_next', ctypes.POINTER(_PyObject)), - ('_ob_prev', ctypes.POINTER(_PyObject)), - ('ob_refcnt', _Py_ssize_t), - ('ob_type', ctypes.POINTER(_PyObject)) - ] - - class _Traceback(_PyObject): - pass - _Traceback._fields_ = [ - ('tb_next', ctypes.POINTER(_Traceback)), - ('tb_frame', ctypes.POINTER(_PyObject)), - ('tb_lasti', ctypes.c_int), - ('tb_lineno', ctypes.c_int) - ] - - def tb_set_next(tb, next): - """Set the tb_next attribute of a traceback object.""" - if not (isinstance(tb, TracebackType) and - (next is None or isinstance(next, TracebackType))): - raise TypeError('tb_set_next arguments must be traceback objects') - obj = _Traceback.from_address(id(tb)) - if tb.tb_next is not None: - old = _Traceback.from_address(id(tb.tb_next)) - old.ob_refcnt -= 1 - if next is None: - obj.tb_next = ctypes.POINTER(_Traceback)() - else: - next = _Traceback.from_address(id(next)) - next.ob_refcnt += 1 - obj.tb_next = ctypes.pointer(next) - - return tb_set_next - - -# try to get a tb_set_next implementation -try: - from jinja2._debugsupport import tb_set_next -except ImportError: - try: - tb_set_next = _init_ugly_crap() - except: - tb_set_next = None -del _init_ugly_crap diff --git a/module/lib/jinja2/defaults.py b/module/lib/jinja2/defaults.py deleted file mode 100644 index d2d45443a..000000000 --- a/module/lib/jinja2/defaults.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.defaults - ~~~~~~~~~~~~~~~ - - Jinja default filters and tags. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner - - -# defaults for the parser / lexer -BLOCK_START_STRING = '{%' -BLOCK_END_STRING = '%}' -VARIABLE_START_STRING = '{{' -VARIABLE_END_STRING = '}}' -COMMENT_START_STRING = '{#' -COMMENT_END_STRING = '#}' -LINE_STATEMENT_PREFIX = None -LINE_COMMENT_PREFIX = None -TRIM_BLOCKS = False -NEWLINE_SEQUENCE = '\n' - - -# default filters, tests and namespace -from jinja2.filters import FILTERS as DEFAULT_FILTERS -from jinja2.tests import TESTS as DEFAULT_TESTS -DEFAULT_NAMESPACE = { - 'range': xrange, - 'dict': lambda **kw: kw, - 'lipsum': generate_lorem_ipsum, - 'cycler': Cycler, - 'joiner': Joiner -} - - -# export all constants -__all__ = tuple(x for x in locals().keys() if x.isupper()) diff --git a/module/lib/jinja2/environment.py b/module/lib/jinja2/environment.py deleted file mode 100644 index ac74a5c68..000000000 --- a/module/lib/jinja2/environment.py +++ /dev/null @@ -1,1118 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.environment - ~~~~~~~~~~~~~~~~~~ - - Provides a class that holds runtime and parsing time options. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import os -import sys -from jinja2 import nodes -from jinja2.defaults import * -from jinja2.lexer import get_lexer, TokenStream -from jinja2.parser import Parser -from jinja2.optimizer import optimize -from jinja2.compiler import generate -from jinja2.runtime import Undefined, new_context -from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \ - TemplatesNotFound -from jinja2.utils import import_string, LRUCache, Markup, missing, \ - concat, consume, internalcode, _encode_filename - - -# for direct template usage we have up to ten living environments -_spontaneous_environments = LRUCache(10) - -# the function to create jinja traceback objects. This is dynamically -# imported on the first exception in the exception handler. -_make_traceback = None - - -def get_spontaneous_environment(*args): - """Return a new spontaneous environment. A spontaneous environment is an - unnamed and unaccessible (in theory) environment that is used for - templates generated from a string and not from the file system. - """ - try: - env = _spontaneous_environments.get(args) - except TypeError: - return Environment(*args) - if env is not None: - return env - _spontaneous_environments[args] = env = Environment(*args) - env.shared = True - return env - - -def create_cache(size): - """Return the cache class for the given size.""" - if size == 0: - return None - if size < 0: - return {} - return LRUCache(size) - - -def copy_cache(cache): - """Create an empty copy of the given cache.""" - if cache is None: - return None - elif type(cache) is dict: - return {} - return LRUCache(cache.capacity) - - -def load_extensions(environment, extensions): - """Load the extensions from the list and bind it to the environment. - Returns a dict of instanciated environments. - """ - result = {} - for extension in extensions: - if isinstance(extension, basestring): - extension = import_string(extension) - result[extension.identifier] = extension(environment) - return result - - -def _environment_sanity_check(environment): - """Perform a sanity check on the environment.""" - assert issubclass(environment.undefined, Undefined), 'undefined must ' \ - 'be a subclass of undefined because filters depend on it.' - assert environment.block_start_string != \ - environment.variable_start_string != \ - environment.comment_start_string, 'block, variable and comment ' \ - 'start strings must be different' - assert environment.newline_sequence in ('\r', '\r\n', '\n'), \ - 'newline_sequence set to unknown line ending string.' - return environment - - -class Environment(object): - r"""The core component of Jinja is the `Environment`. It contains - important shared variables like configuration, filters, tests, - globals and others. Instances of this class may be modified if - they are not shared and if no template was loaded so far. - Modifications on environments after the first template was loaded - will lead to surprising effects and undefined behavior. - - Here the possible initialization parameters: - - `block_start_string` - The string marking the begin of a block. Defaults to ``'{%'``. - - `block_end_string` - The string marking the end of a block. Defaults to ``'%}'``. - - `variable_start_string` - The string marking the begin of a print statement. - Defaults to ``'{{'``. - - `variable_end_string` - The string marking the end of a print statement. Defaults to - ``'}}'``. - - `comment_start_string` - The string marking the begin of a comment. Defaults to ``'{#'``. - - `comment_end_string` - The string marking the end of a comment. Defaults to ``'#}'``. - - `line_statement_prefix` - If given and a string, this will be used as prefix for line based - statements. See also :ref:`line-statements`. - - `line_comment_prefix` - If given and a string, this will be used as prefix for line based - based comments. See also :ref:`line-statements`. - - .. versionadded:: 2.2 - - `trim_blocks` - If this is set to ``True`` the first newline after a block is - removed (block, not variable tag!). Defaults to `False`. - - `newline_sequence` - The sequence that starts a newline. Must be one of ``'\r'``, - ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a - useful default for Linux and OS X systems as well as web - applications. - - `extensions` - List of Jinja extensions to use. This can either be import paths - as strings or extension classes. For more information have a - look at :ref:`the extensions documentation <jinja-extensions>`. - - `optimized` - should the optimizer be enabled? Default is `True`. - - `undefined` - :class:`Undefined` or a subclass of it that is used to represent - undefined values in the template. - - `finalize` - A callable that can be used to process the result of a variable - expression before it is output. For example one can convert - `None` implicitly into an empty string here. - - `autoescape` - If set to true the XML/HTML autoescaping feature is enabled by - default. For more details about auto escaping see - :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also - be a callable that is passed the template name and has to - return `True` or `False` depending on autoescape should be - enabled by default. - - .. versionchanged:: 2.4 - `autoescape` can now be a function - - `loader` - The template loader for this environment. - - `cache_size` - The size of the cache. Per default this is ``50`` which means - that if more than 50 templates are loaded the loader will clean - out the least recently used template. If the cache size is set to - ``0`` templates are recompiled all the time, if the cache size is - ``-1`` the cache will not be cleaned. - - `auto_reload` - Some loaders load templates from locations where the template - sources may change (ie: file system or database). If - `auto_reload` is set to `True` (default) every time a template is - requested the loader checks if the source changed and if yes, it - will reload the template. For higher performance it's possible to - disable that. - - `bytecode_cache` - If set to a bytecode cache object, this object will provide a - cache for the internal Jinja bytecode so that templates don't - have to be parsed if they were not changed. - - See :ref:`bytecode-cache` for more information. - """ - - #: if this environment is sandboxed. Modifying this variable won't make - #: the environment sandboxed though. For a real sandboxed environment - #: have a look at jinja2.sandbox - sandboxed = False - - #: True if the environment is just an overlay - overlayed = False - - #: the environment this environment is linked to if it is an overlay - linked_to = None - - #: shared environments have this set to `True`. A shared environment - #: must not be modified - shared = False - - #: these are currently EXPERIMENTAL undocumented features. - exception_handler = None - exception_formatter = None - - def __init__(self, - block_start_string=BLOCK_START_STRING, - block_end_string=BLOCK_END_STRING, - variable_start_string=VARIABLE_START_STRING, - variable_end_string=VARIABLE_END_STRING, - comment_start_string=COMMENT_START_STRING, - comment_end_string=COMMENT_END_STRING, - line_statement_prefix=LINE_STATEMENT_PREFIX, - line_comment_prefix=LINE_COMMENT_PREFIX, - trim_blocks=TRIM_BLOCKS, - newline_sequence=NEWLINE_SEQUENCE, - extensions=(), - optimized=True, - undefined=Undefined, - finalize=None, - autoescape=False, - loader=None, - cache_size=50, - auto_reload=True, - bytecode_cache=None): - # !!Important notice!! - # The constructor accepts quite a few arguments that should be - # passed by keyword rather than position. However it's important to - # not change the order of arguments because it's used at least - # internally in those cases: - # - spontaneus environments (i18n extension and Template) - # - unittests - # If parameter changes are required only add parameters at the end - # and don't change the arguments (or the defaults!) of the arguments - # existing already. - - # lexer / parser information - self.block_start_string = block_start_string - self.block_end_string = block_end_string - self.variable_start_string = variable_start_string - self.variable_end_string = variable_end_string - self.comment_start_string = comment_start_string - self.comment_end_string = comment_end_string - self.line_statement_prefix = line_statement_prefix - self.line_comment_prefix = line_comment_prefix - self.trim_blocks = trim_blocks - self.newline_sequence = newline_sequence - - # runtime information - self.undefined = undefined - self.optimized = optimized - self.finalize = finalize - self.autoescape = autoescape - - # defaults - self.filters = DEFAULT_FILTERS.copy() - self.tests = DEFAULT_TESTS.copy() - self.globals = DEFAULT_NAMESPACE.copy() - - # set the loader provided - self.loader = loader - self.bytecode_cache = None - self.cache = create_cache(cache_size) - self.bytecode_cache = bytecode_cache - self.auto_reload = auto_reload - - # load extensions - self.extensions = load_extensions(self, extensions) - - _environment_sanity_check(self) - - def add_extension(self, extension): - """Adds an extension after the environment was created. - - .. versionadded:: 2.5 - """ - self.extensions.update(load_extensions(self, [extension])) - - def extend(self, **attributes): - """Add the items to the instance of the environment if they do not exist - yet. This is used by :ref:`extensions <writing-extensions>` to register - callbacks and configuration values without breaking inheritance. - """ - for key, value in attributes.iteritems(): - if not hasattr(self, key): - setattr(self, key, value) - - def overlay(self, block_start_string=missing, block_end_string=missing, - variable_start_string=missing, variable_end_string=missing, - comment_start_string=missing, comment_end_string=missing, - line_statement_prefix=missing, line_comment_prefix=missing, - trim_blocks=missing, extensions=missing, optimized=missing, - undefined=missing, finalize=missing, autoescape=missing, - loader=missing, cache_size=missing, auto_reload=missing, - bytecode_cache=missing): - """Create a new overlay environment that shares all the data with the - current environment except of cache and the overridden attributes. - Extensions cannot be removed for an overlayed environment. An overlayed - environment automatically gets all the extensions of the environment it - is linked to plus optional extra extensions. - - Creating overlays should happen after the initial environment was set - up completely. Not all attributes are truly linked, some are just - copied over so modifications on the original environment may not shine - through. - """ - args = dict(locals()) - del args['self'], args['cache_size'], args['extensions'] - - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.overlayed = True - rv.linked_to = self - - for key, value in args.iteritems(): - if value is not missing: - setattr(rv, key, value) - - if cache_size is not missing: - rv.cache = create_cache(cache_size) - else: - rv.cache = copy_cache(self.cache) - - rv.extensions = {} - for key, value in self.extensions.iteritems(): - rv.extensions[key] = value.bind(rv) - if extensions is not missing: - rv.extensions.update(load_extensions(rv, extensions)) - - return _environment_sanity_check(rv) - - lexer = property(get_lexer, doc="The lexer for this environment.") - - def iter_extensions(self): - """Iterates over the extensions by priority.""" - return iter(sorted(self.extensions.values(), - key=lambda x: x.priority)) - - def getitem(self, obj, argument): - """Get an item or attribute of an object but prefer the item.""" - try: - return obj[argument] - except (TypeError, LookupError): - if isinstance(argument, basestring): - try: - attr = str(argument) - except: - pass - else: - try: - return getattr(obj, attr) - except AttributeError: - pass - return self.undefined(obj=obj, name=argument) - - def getattr(self, obj, attribute): - """Get an item or attribute of an object but prefer the attribute. - Unlike :meth:`getitem` the attribute *must* be a bytestring. - """ - try: - return getattr(obj, attribute) - except AttributeError: - pass - try: - return obj[attribute] - except (TypeError, LookupError, AttributeError): - return self.undefined(obj=obj, name=attribute) - - @internalcode - def parse(self, source, name=None, filename=None): - """Parse the sourcecode and return the abstract syntax tree. This - tree of nodes is used by the compiler to convert the template into - executable source- or bytecode. This is useful for debugging or to - extract information from templates. - - If you are :ref:`developing Jinja2 extensions <writing-extensions>` - this gives you a good overview of the node tree generated. - """ - try: - return self._parse(source, name, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source) - - def _parse(self, source, name, filename): - """Internal parsing function used by `parse` and `compile`.""" - return Parser(self, source, name, _encode_filename(filename)).parse() - - def lex(self, source, name=None, filename=None): - """Lex the given sourcecode and return a generator that yields - tokens as tuples in the form ``(lineno, token_type, value)``. - This can be useful for :ref:`extension development <writing-extensions>` - and debugging templates. - - This does not perform preprocessing. If you want the preprocessing - of the extensions to be applied you have to filter source through - the :meth:`preprocess` method. - """ - source = unicode(source) - try: - return self.lexer.tokeniter(source, name, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source) - - def preprocess(self, source, name=None, filename=None): - """Preprocesses the source with all extensions. This is automatically - called for all parsing and compiling methods but *not* for :meth:`lex` - because there you usually only want the actual source tokenized. - """ - return reduce(lambda s, e: e.preprocess(s, name, filename), - self.iter_extensions(), unicode(source)) - - def _tokenize(self, source, name, filename=None, state=None): - """Called by the parser to do the preprocessing and filtering - for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. - """ - source = self.preprocess(source, name, filename) - stream = self.lexer.tokenize(source, name, filename, state) - for ext in self.iter_extensions(): - stream = ext.filter_stream(stream) - if not isinstance(stream, TokenStream): - stream = TokenStream(stream, name, filename) - return stream - - def _generate(self, source, name, filename, defer_init=False): - """Internal hook that can be overriden to hook a different generate - method in. - - .. versionadded:: 2.5 - """ - return generate(source, self, name, filename, defer_init=defer_init) - - def _compile(self, source, filename): - """Internal hook that can be overriden to hook a different compile - method in. - - .. versionadded:: 2.5 - """ - return compile(source, filename, 'exec') - - @internalcode - def compile(self, source, name=None, filename=None, raw=False, - defer_init=False): - """Compile a node or template source code. The `name` parameter is - the load name of the template after it was joined using - :meth:`join_path` if necessary, not the filename on the file system. - the `filename` parameter is the estimated filename of the template on - the file system. If the template came from a database or memory this - can be omitted. - - The return value of this method is a python code object. If the `raw` - parameter is `True` the return value will be a string with python - code equivalent to the bytecode returned otherwise. This method is - mainly used internally. - - `defer_init` is use internally to aid the module code generator. This - causes the generated code to be able to import without the global - environment variable to be set. - - .. versionadded:: 2.4 - `defer_init` parameter added. - """ - source_hint = None - try: - if isinstance(source, basestring): - source_hint = source - source = self._parse(source, name, filename) - if self.optimized: - source = optimize(source, self) - source = self._generate(source, name, filename, - defer_init=defer_init) - if raw: - return source - if filename is None: - filename = '<template>' - else: - filename = _encode_filename(filename) - return self._compile(source, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source) - - def compile_expression(self, source, undefined_to_none=True): - """A handy helper method that returns a callable that accepts keyword - arguments that appear as variables in the expression. If called it - returns the result of the expression. - - This is useful if applications want to use the same rules as Jinja - in template "configuration files" or similar situations. - - Example usage: - - >>> env = Environment() - >>> expr = env.compile_expression('foo == 42') - >>> expr(foo=23) - False - >>> expr(foo=42) - True - - Per default the return value is converted to `None` if the - expression returns an undefined value. This can be changed - by setting `undefined_to_none` to `False`. - - >>> env.compile_expression('var')() is None - True - >>> env.compile_expression('var', undefined_to_none=False)() - Undefined - - .. versionadded:: 2.1 - """ - parser = Parser(self, source, state='variable') - exc_info = None - try: - expr = parser.parse_expression() - if not parser.stream.eos: - raise TemplateSyntaxError('chunk after expression', - parser.stream.current.lineno, - None, None) - expr.set_environment(self) - except TemplateSyntaxError: - exc_info = sys.exc_info() - if exc_info is not None: - self.handle_exception(exc_info, source_hint=source) - body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)] - template = self.from_string(nodes.Template(body, lineno=1)) - return TemplateExpression(template, undefined_to_none) - - def compile_templates(self, target, extensions=None, filter_func=None, - zip='deflated', log_function=None, - ignore_errors=True, py_compile=False): - """Compiles all the templates the loader can find, compiles them - and stores them in `target`. If `zip` is `None`, instead of in a - zipfile, the templates will be will be stored in a directory. - By default a deflate zip algorithm is used, to switch to - the stored algorithm, `zip` can be set to ``'stored'``. - - `extensions` and `filter_func` are passed to :meth:`list_templates`. - Each template returned will be compiled to the target folder or - zipfile. - - By default template compilation errors are ignored. In case a - log function is provided, errors are logged. If you want template - syntax errors to abort the compilation you can set `ignore_errors` - to `False` and you will get an exception on syntax errors. - - If `py_compile` is set to `True` .pyc files will be written to the - target instead of standard .py files. - - .. versionadded:: 2.4 - """ - from jinja2.loaders import ModuleLoader - - if log_function is None: - log_function = lambda x: None - - if py_compile: - import imp, struct, marshal - py_header = imp.get_magic() + \ - u'\xff\xff\xff\xff'.encode('iso-8859-15') - - def write_file(filename, data, mode): - if zip: - info = ZipInfo(filename) - info.external_attr = 0755 << 16L - zip_file.writestr(info, data) - else: - f = open(os.path.join(target, filename), mode) - try: - f.write(data) - finally: - f.close() - - if zip is not None: - from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED - zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED, - stored=ZIP_STORED)[zip]) - log_function('Compiling into Zip archive "%s"' % target) - else: - if not os.path.isdir(target): - os.makedirs(target) - log_function('Compiling into folder "%s"' % target) - - try: - for name in self.list_templates(extensions, filter_func): - source, filename, _ = self.loader.get_source(self, name) - try: - code = self.compile(source, name, filename, True, True) - except TemplateSyntaxError, e: - if not ignore_errors: - raise - log_function('Could not compile "%s": %s' % (name, e)) - continue - - filename = ModuleLoader.get_module_filename(name) - - if py_compile: - c = self._compile(code, _encode_filename(filename)) - write_file(filename + 'c', py_header + - marshal.dumps(c), 'wb') - log_function('Byte-compiled "%s" as %s' % - (name, filename + 'c')) - else: - write_file(filename, code, 'w') - log_function('Compiled "%s" as %s' % (name, filename)) - finally: - if zip: - zip_file.close() - - log_function('Finished compiling templates') - - def list_templates(self, extensions=None, filter_func=None): - """Returns a list of templates for this environment. This requires - that the loader supports the loader's - :meth:`~BaseLoader.list_templates` method. - - If there are other files in the template folder besides the - actual templates, the returned list can be filtered. There are two - ways: either `extensions` is set to a list of file extensions for - templates, or a `filter_func` can be provided which is a callable that - is passed a template name and should return `True` if it should end up - in the result list. - - If the loader does not support that, a :exc:`TypeError` is raised. - """ - x = self.loader.list_templates() - if extensions is not None: - if filter_func is not None: - raise TypeError('either extensions or filter_func ' - 'can be passed, but not both') - filter_func = lambda x: '.' in x and \ - x.rsplit('.', 1)[1] in extensions - if filter_func is not None: - x = filter(filter_func, x) - return x - - def handle_exception(self, exc_info=None, rendered=False, source_hint=None): - """Exception handling helper. This is used internally to either raise - rewritten exceptions or return a rendered traceback for the template. - """ - global _make_traceback - if exc_info is None: - exc_info = sys.exc_info() - - # the debugging module is imported when it's used for the first time. - # we're doing a lot of stuff there and for applications that do not - # get any exceptions in template rendering there is no need to load - # all of that. - if _make_traceback is None: - from jinja2.debug import make_traceback as _make_traceback - traceback = _make_traceback(exc_info, source_hint) - if rendered and self.exception_formatter is not None: - return self.exception_formatter(traceback) - if self.exception_handler is not None: - self.exception_handler(traceback) - exc_type, exc_value, tb = traceback.standard_exc_info - raise exc_type, exc_value, tb - - def join_path(self, template, parent): - """Join a template with the parent. By default all the lookups are - relative to the loader root so this method returns the `template` - parameter unchanged, but if the paths should be relative to the - parent template, this function can be used to calculate the real - template name. - - Subclasses may override this method and implement template path - joining here. - """ - return template - - @internalcode - def _load_template(self, name, globals): - if self.loader is None: - raise TypeError('no loader for this environment specified') - if self.cache is not None: - template = self.cache.get(name) - if template is not None and (not self.auto_reload or \ - template.is_up_to_date): - return template - template = self.loader.load(self, name, globals) - if self.cache is not None: - self.cache[name] = template - return template - - @internalcode - def get_template(self, name, parent=None, globals=None): - """Load a template from the loader. If a loader is configured this - method ask the loader for the template and returns a :class:`Template`. - If the `parent` parameter is not `None`, :meth:`join_path` is called - to get the real template name before loading. - - The `globals` parameter can be used to provide template wide globals. - These variables are available in the context at render time. - - If the template does not exist a :exc:`TemplateNotFound` exception is - raised. - - .. versionchanged:: 2.4 - If `name` is a :class:`Template` object it is returned from the - function unchanged. - """ - if isinstance(name, Template): - return name - if parent is not None: - name = self.join_path(name, parent) - return self._load_template(name, self.make_globals(globals)) - - @internalcode - def select_template(self, names, parent=None, globals=None): - """Works like :meth:`get_template` but tries a number of templates - before it fails. If it cannot find any of the templates, it will - raise a :exc:`TemplatesNotFound` exception. - - .. versionadded:: 2.3 - - .. versionchanged:: 2.4 - If `names` contains a :class:`Template` object it is returned - from the function unchanged. - """ - if not names: - raise TemplatesNotFound(message=u'Tried to select from an empty list ' - u'of templates.') - globals = self.make_globals(globals) - for name in names: - if isinstance(name, Template): - return name - if parent is not None: - name = self.join_path(name, parent) - try: - return self._load_template(name, globals) - except TemplateNotFound: - pass - raise TemplatesNotFound(names) - - @internalcode - def get_or_select_template(self, template_name_or_list, - parent=None, globals=None): - """Does a typecheck and dispatches to :meth:`select_template` - if an iterable of template names is given, otherwise to - :meth:`get_template`. - - .. versionadded:: 2.3 - """ - if isinstance(template_name_or_list, basestring): - return self.get_template(template_name_or_list, parent, globals) - elif isinstance(template_name_or_list, Template): - return template_name_or_list - return self.select_template(template_name_or_list, parent, globals) - - def from_string(self, source, globals=None, template_class=None): - """Load a template from a string. This parses the source given and - returns a :class:`Template` object. - """ - globals = self.make_globals(globals) - cls = template_class or self.template_class - return cls.from_code(self, self.compile(source), globals, None) - - def make_globals(self, d): - """Return a dict for the globals.""" - if not d: - return self.globals - return dict(self.globals, **d) - - -class Template(object): - """The central template object. This class represents a compiled template - and is used to evaluate it. - - Normally the template object is generated from an :class:`Environment` but - it also has a constructor that makes it possible to create a template - instance directly using the constructor. It takes the same arguments as - the environment constructor but it's not possible to specify a loader. - - Every template object has a few methods and members that are guaranteed - to exist. However it's important that a template object should be - considered immutable. Modifications on the object are not supported. - - Template objects created from the constructor rather than an environment - do have an `environment` attribute that points to a temporary environment - that is probably shared with other templates created with the constructor - and compatible settings. - - >>> template = Template('Hello {{ name }}!') - >>> template.render(name='John Doe') - u'Hello John Doe!' - - >>> stream = template.stream(name='John Doe') - >>> stream.next() - u'Hello John Doe!' - >>> stream.next() - Traceback (most recent call last): - ... - StopIteration - """ - - def __new__(cls, source, - block_start_string=BLOCK_START_STRING, - block_end_string=BLOCK_END_STRING, - variable_start_string=VARIABLE_START_STRING, - variable_end_string=VARIABLE_END_STRING, - comment_start_string=COMMENT_START_STRING, - comment_end_string=COMMENT_END_STRING, - line_statement_prefix=LINE_STATEMENT_PREFIX, - line_comment_prefix=LINE_COMMENT_PREFIX, - trim_blocks=TRIM_BLOCKS, - newline_sequence=NEWLINE_SEQUENCE, - extensions=(), - optimized=True, - undefined=Undefined, - finalize=None, - autoescape=False): - env = get_spontaneous_environment( - block_start_string, block_end_string, variable_start_string, - variable_end_string, comment_start_string, comment_end_string, - line_statement_prefix, line_comment_prefix, trim_blocks, - newline_sequence, frozenset(extensions), optimized, undefined, - finalize, autoescape, None, 0, False, None) - return env.from_string(source, template_class=cls) - - @classmethod - def from_code(cls, environment, code, globals, uptodate=None): - """Creates a template object from compiled code and the globals. This - is used by the loaders and environment to create a template object. - """ - namespace = { - 'environment': environment, - '__file__': code.co_filename - } - exec code in namespace - rv = cls._from_namespace(environment, namespace, globals) - rv._uptodate = uptodate - return rv - - @classmethod - def from_module_dict(cls, environment, module_dict, globals): - """Creates a template object from a module. This is used by the - module loader to create a template object. - - .. versionadded:: 2.4 - """ - return cls._from_namespace(environment, module_dict, globals) - - @classmethod - def _from_namespace(cls, environment, namespace, globals): - t = object.__new__(cls) - t.environment = environment - t.globals = globals - t.name = namespace['name'] - t.filename = namespace['__file__'] - t.blocks = namespace['blocks'] - - # render function and module - t.root_render_func = namespace['root'] - t._module = None - - # debug and loader helpers - t._debug_info = namespace['debug_info'] - t._uptodate = None - - # store the reference - namespace['environment'] = environment - namespace['__jinja_template__'] = t - - return t - - def render(self, *args, **kwargs): - """This method accepts the same arguments as the `dict` constructor: - A dict, a dict subclass or some keyword arguments. If no arguments - are given the context will be empty. These two calls do the same:: - - template.render(knights='that say nih') - template.render({'knights': 'that say nih'}) - - This will return the rendered template as unicode string. - """ - vars = dict(*args, **kwargs) - try: - return concat(self.root_render_func(self.new_context(vars))) - except: - exc_info = sys.exc_info() - return self.environment.handle_exception(exc_info, True) - - def stream(self, *args, **kwargs): - """Works exactly like :meth:`generate` but returns a - :class:`TemplateStream`. - """ - return TemplateStream(self.generate(*args, **kwargs)) - - def generate(self, *args, **kwargs): - """For very large templates it can be useful to not render the whole - template at once but evaluate each statement after another and yield - piece for piece. This method basically does exactly that and returns - a generator that yields one item after another as unicode strings. - - It accepts the same arguments as :meth:`render`. - """ - vars = dict(*args, **kwargs) - try: - for event in self.root_render_func(self.new_context(vars)): - yield event - except: - exc_info = sys.exc_info() - else: - return - yield self.environment.handle_exception(exc_info, True) - - def new_context(self, vars=None, shared=False, locals=None): - """Create a new :class:`Context` for this template. The vars - provided will be passed to the template. Per default the globals - are added to the context. If shared is set to `True` the data - is passed as it to the context without adding the globals. - - `locals` can be a dict of local variables for internal usage. - """ - return new_context(self.environment, self.name, self.blocks, - vars, shared, self.globals, locals) - - def make_module(self, vars=None, shared=False, locals=None): - """This method works like the :attr:`module` attribute when called - without arguments but it will evaluate the template on every call - rather than caching it. It's also possible to provide - a dict which is then used as context. The arguments are the same - as for the :meth:`new_context` method. - """ - return TemplateModule(self, self.new_context(vars, shared, locals)) - - @property - def module(self): - """The template as module. This is used for imports in the - template runtime but is also useful if one wants to access - exported template variables from the Python layer: - - >>> t = Template('{% macro foo() %}42{% endmacro %}23') - >>> unicode(t.module) - u'23' - >>> t.module.foo() - u'42' - """ - if self._module is not None: - return self._module - self._module = rv = self.make_module() - return rv - - def get_corresponding_lineno(self, lineno): - """Return the source line number of a line number in the - generated bytecode as they are not in sync. - """ - for template_line, code_line in reversed(self.debug_info): - if code_line <= lineno: - return template_line - return 1 - - @property - def is_up_to_date(self): - """If this variable is `False` there is a newer version available.""" - if self._uptodate is None: - return True - return self._uptodate() - - @property - def debug_info(self): - """The debug info mapping.""" - return [tuple(map(int, x.split('='))) for x in - self._debug_info.split('&')] - - def __repr__(self): - if self.name is None: - name = 'memory:%x' % id(self) - else: - name = repr(self.name) - return '<%s %s>' % (self.__class__.__name__, name) - - -class TemplateModule(object): - """Represents an imported template. All the exported names of the - template are available as attributes on this object. Additionally - converting it into an unicode- or bytestrings renders the contents. - """ - - def __init__(self, template, context): - self._body_stream = list(template.root_render_func(context)) - self.__dict__.update(context.get_exported()) - self.__name__ = template.name - - def __html__(self): - return Markup(concat(self._body_stream)) - - def __str__(self): - return unicode(self).encode('utf-8') - - # unicode goes after __str__ because we configured 2to3 to rename - # __unicode__ to __str__. because the 2to3 tree is not designed to - # remove nodes from it, we leave the above __str__ around and let - # it override at runtime. - def __unicode__(self): - return concat(self._body_stream) - - def __repr__(self): - if self.__name__ is None: - name = 'memory:%x' % id(self) - else: - name = repr(self.__name__) - return '<%s %s>' % (self.__class__.__name__, name) - - -class TemplateExpression(object): - """The :meth:`jinja2.Environment.compile_expression` method returns an - instance of this object. It encapsulates the expression-like access - to the template with an expression it wraps. - """ - - def __init__(self, template, undefined_to_none): - self._template = template - self._undefined_to_none = undefined_to_none - - def __call__(self, *args, **kwargs): - context = self._template.new_context(dict(*args, **kwargs)) - consume(self._template.root_render_func(context)) - rv = context.vars['result'] - if self._undefined_to_none and isinstance(rv, Undefined): - rv = None - return rv - - -class TemplateStream(object): - """A template stream works pretty much like an ordinary python generator - but it can buffer multiple items to reduce the number of total iterations. - Per default the output is unbuffered which means that for every unbuffered - instruction in the template one unicode string is yielded. - - If buffering is enabled with a buffer size of 5, five items are combined - into a new unicode string. This is mainly useful if you are streaming - big templates to a client via WSGI which flushes after each iteration. - """ - - def __init__(self, gen): - self._gen = gen - self.disable_buffering() - - def dump(self, fp, encoding=None, errors='strict'): - """Dump the complete stream into a file or file-like object. - Per default unicode strings are written, if you want to encode - before writing specifiy an `encoding`. - - Example usage:: - - Template('Hello {{ name }}!').stream(name='foo').dump('hello.html') - """ - close = False - if isinstance(fp, basestring): - fp = file(fp, 'w') - close = True - try: - if encoding is not None: - iterable = (x.encode(encoding, errors) for x in self) - else: - iterable = self - if hasattr(fp, 'writelines'): - fp.writelines(iterable) - else: - for item in iterable: - fp.write(item) - finally: - if close: - fp.close() - - def disable_buffering(self): - """Disable the output buffering.""" - self._next = self._gen.next - self.buffered = False - - def enable_buffering(self, size=5): - """Enable buffering. Buffer `size` items before yielding them.""" - if size <= 1: - raise ValueError('buffer size too small') - - def generator(next): - buf = [] - c_size = 0 - push = buf.append - - while 1: - try: - while c_size < size: - c = next() - push(c) - if c: - c_size += 1 - except StopIteration: - if not c_size: - return - yield concat(buf) - del buf[:] - c_size = 0 - - self.buffered = True - self._next = generator(self._gen.next).next - - def __iter__(self): - return self - - def next(self): - return self._next() - - -# hook in default template class. if anyone reads this comment: ignore that -# it's possible to use custom templates ;-) -Environment.template_class = Template diff --git a/module/lib/jinja2/exceptions.py b/module/lib/jinja2/exceptions.py deleted file mode 100644 index 771f6a8d7..000000000 --- a/module/lib/jinja2/exceptions.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.exceptions - ~~~~~~~~~~~~~~~~~ - - Jinja exceptions. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" - - -class TemplateError(Exception): - """Baseclass for all template errors.""" - - def __init__(self, message=None): - if message is not None: - message = unicode(message).encode('utf-8') - Exception.__init__(self, message) - - @property - def message(self): - if self.args: - message = self.args[0] - if message is not None: - return message.decode('utf-8', 'replace') - - -class TemplateNotFound(IOError, LookupError, TemplateError): - """Raised if a template does not exist.""" - - # looks weird, but removes the warning descriptor that just - # bogusly warns us about message being deprecated - message = None - - def __init__(self, name, message=None): - IOError.__init__(self) - if message is None: - message = name - self.message = message - self.name = name - self.templates = [name] - - def __str__(self): - return self.message.encode('utf-8') - - # unicode goes after __str__ because we configured 2to3 to rename - # __unicode__ to __str__. because the 2to3 tree is not designed to - # remove nodes from it, we leave the above __str__ around and let - # it override at runtime. - def __unicode__(self): - return self.message - - -class TemplatesNotFound(TemplateNotFound): - """Like :class:`TemplateNotFound` but raised if multiple templates - are selected. This is a subclass of :class:`TemplateNotFound` - exception, so just catching the base exception will catch both. - - .. versionadded:: 2.2 - """ - - def __init__(self, names=(), message=None): - if message is None: - message = u'non of the templates given were found: ' + \ - u', '.join(map(unicode, names)) - TemplateNotFound.__init__(self, names and names[-1] or None, message) - self.templates = list(names) - - -class TemplateSyntaxError(TemplateError): - """Raised to tell the user that there is a problem with the template.""" - - def __init__(self, message, lineno, name=None, filename=None): - TemplateError.__init__(self, message) - self.lineno = lineno - self.name = name - self.filename = filename - self.source = None - - # this is set to True if the debug.translate_syntax_error - # function translated the syntax error into a new traceback - self.translated = False - - def __str__(self): - return unicode(self).encode('utf-8') - - # unicode goes after __str__ because we configured 2to3 to rename - # __unicode__ to __str__. because the 2to3 tree is not designed to - # remove nodes from it, we leave the above __str__ around and let - # it override at runtime. - def __unicode__(self): - # for translated errors we only return the message - if self.translated: - return self.message - - # otherwise attach some stuff - location = 'line %d' % self.lineno - name = self.filename or self.name - if name: - location = 'File "%s", %s' % (name, location) - lines = [self.message, ' ' + location] - - # if the source is set, add the line to the output - if self.source is not None: - try: - line = self.source.splitlines()[self.lineno - 1] - except IndexError: - line = None - if line: - lines.append(' ' + line.strip()) - - return u'\n'.join(lines) - - -class TemplateAssertionError(TemplateSyntaxError): - """Like a template syntax error, but covers cases where something in the - template caused an error at compile time that wasn't necessarily caused - by a syntax error. However it's a direct subclass of - :exc:`TemplateSyntaxError` and has the same attributes. - """ - - -class TemplateRuntimeError(TemplateError): - """A generic runtime error in the template engine. Under some situations - Jinja may raise this exception. - """ - - -class UndefinedError(TemplateRuntimeError): - """Raised if a template tries to operate on :class:`Undefined`.""" - - -class SecurityError(TemplateRuntimeError): - """Raised if a template tries to do something insecure if the - sandbox is enabled. - """ - - -class FilterArgumentError(TemplateRuntimeError): - """This error is raised if a filter was called with inappropriate - arguments - """ diff --git a/module/lib/jinja2/ext.py b/module/lib/jinja2/ext.py deleted file mode 100644 index ceb38953a..000000000 --- a/module/lib/jinja2/ext.py +++ /dev/null @@ -1,610 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.ext - ~~~~~~~~~~ - - Jinja extensions allow to add custom tags similar to the way django custom - tags work. By default two example extensions exist: an i18n and a cache - extension. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -from collections import deque -from jinja2 import nodes -from jinja2.defaults import * -from jinja2.environment import Environment -from jinja2.runtime import Undefined, concat -from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError -from jinja2.utils import contextfunction, import_string, Markup, next - - -# the only real useful gettext functions for a Jinja template. Note -# that ugettext must be assigned to gettext as Jinja doesn't support -# non unicode strings. -GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') - - -class ExtensionRegistry(type): - """Gives the extension an unique identifier.""" - - def __new__(cls, name, bases, d): - rv = type.__new__(cls, name, bases, d) - rv.identifier = rv.__module__ + '.' + rv.__name__ - return rv - - -class Extension(object): - """Extensions can be used to add extra functionality to the Jinja template - system at the parser level. Custom extensions are bound to an environment - but may not store environment specific data on `self`. The reason for - this is that an extension can be bound to another environment (for - overlays) by creating a copy and reassigning the `environment` attribute. - - As extensions are created by the environment they cannot accept any - arguments for configuration. One may want to work around that by using - a factory function, but that is not possible as extensions are identified - by their import name. The correct way to configure the extension is - storing the configuration values on the environment. Because this way the - environment ends up acting as central configuration storage the - attributes may clash which is why extensions have to ensure that the names - they choose for configuration are not too generic. ``prefix`` for example - is a terrible name, ``fragment_cache_prefix`` on the other hand is a good - name as includes the name of the extension (fragment cache). - """ - __metaclass__ = ExtensionRegistry - - #: if this extension parses this is the list of tags it's listening to. - tags = set() - - #: the priority of that extension. This is especially useful for - #: extensions that preprocess values. A lower value means higher - #: priority. - #: - #: .. versionadded:: 2.4 - priority = 100 - - def __init__(self, environment): - self.environment = environment - - def bind(self, environment): - """Create a copy of this extension bound to another environment.""" - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.environment = environment - return rv - - def preprocess(self, source, name, filename=None): - """This method is called before the actual lexing and can be used to - preprocess the source. The `filename` is optional. The return value - must be the preprocessed source. - """ - return source - - def filter_stream(self, stream): - """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used - to filter tokens returned. This method has to return an iterable of - :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a - :class:`~jinja2.lexer.TokenStream`. - - In the `ext` folder of the Jinja2 source distribution there is a file - called `inlinegettext.py` which implements a filter that utilizes this - method. - """ - return stream - - def parse(self, parser): - """If any of the :attr:`tags` matched this method is called with the - parser as first argument. The token the parser stream is pointing at - is the name token that matched. This method has to return one or a - list of multiple nodes. - """ - raise NotImplementedError() - - def attr(self, name, lineno=None): - """Return an attribute node for the current extension. This is useful - to pass constants on extensions to generated template code:: - - self.attr('_my_attribute', lineno=lineno) - """ - return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) - - def call_method(self, name, args=None, kwargs=None, dyn_args=None, - dyn_kwargs=None, lineno=None): - """Call a method of the extension. This is a shortcut for - :meth:`attr` + :class:`jinja2.nodes.Call`. - """ - if args is None: - args = [] - if kwargs is None: - kwargs = [] - return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, - dyn_args, dyn_kwargs, lineno=lineno) - - -@contextfunction -def _gettext_alias(__context, *args, **kwargs): - return __context.call(__context.resolve('gettext'), *args, **kwargs) - - -def _make_new_gettext(func): - @contextfunction - def gettext(__context, __string, **variables): - rv = __context.call(func, __string) - if __context.eval_ctx.autoescape: - rv = Markup(rv) - return rv % variables - return gettext - - -def _make_new_ngettext(func): - @contextfunction - def ngettext(__context, __singular, __plural, __num, **variables): - variables.setdefault('num', __num) - rv = __context.call(func, __singular, __plural, __num) - if __context.eval_ctx.autoescape: - rv = Markup(rv) - return rv % variables - return ngettext - - -class InternationalizationExtension(Extension): - """This extension adds gettext support to Jinja2.""" - tags = set(['trans']) - - # TODO: the i18n extension is currently reevaluating values in a few - # situations. Take this example: - # {% trans count=something() %}{{ count }} foo{% pluralize - # %}{{ count }} fooss{% endtrans %} - # something is called twice here. One time for the gettext value and - # the other time for the n-parameter of the ngettext function. - - def __init__(self, environment): - Extension.__init__(self, environment) - environment.globals['_'] = _gettext_alias - environment.extend( - install_gettext_translations=self._install, - install_null_translations=self._install_null, - install_gettext_callables=self._install_callables, - uninstall_gettext_translations=self._uninstall, - extract_translations=self._extract, - newstyle_gettext=False - ) - - def _install(self, translations, newstyle=None): - gettext = getattr(translations, 'ugettext', None) - if gettext is None: - gettext = translations.gettext - ngettext = getattr(translations, 'ungettext', None) - if ngettext is None: - ngettext = translations.ngettext - self._install_callables(gettext, ngettext, newstyle) - - def _install_null(self, newstyle=None): - self._install_callables( - lambda x: x, - lambda s, p, n: (n != 1 and (p,) or (s,))[0], - newstyle - ) - - def _install_callables(self, gettext, ngettext, newstyle=None): - if newstyle is not None: - self.environment.newstyle_gettext = newstyle - if self.environment.newstyle_gettext: - gettext = _make_new_gettext(gettext) - ngettext = _make_new_ngettext(ngettext) - self.environment.globals.update( - gettext=gettext, - ngettext=ngettext - ) - - def _uninstall(self, translations): - for key in 'gettext', 'ngettext': - self.environment.globals.pop(key, None) - - def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): - if isinstance(source, basestring): - source = self.environment.parse(source) - return extract_from_ast(source, gettext_functions) - - def parse(self, parser): - """Parse a translatable tag.""" - lineno = next(parser.stream).lineno - num_called_num = False - - # find all the variables referenced. Additionally a variable can be - # defined in the body of the trans block too, but this is checked at - # a later state. - plural_expr = None - variables = {} - while parser.stream.current.type != 'block_end': - if variables: - parser.stream.expect('comma') - - # skip colon for python compatibility - if parser.stream.skip_if('colon'): - break - - name = parser.stream.expect('name') - if name.value in variables: - parser.fail('translatable variable %r defined twice.' % - name.value, name.lineno, - exc=TemplateAssertionError) - - # expressions - if parser.stream.current.type == 'assign': - next(parser.stream) - variables[name.value] = var = parser.parse_expression() - else: - variables[name.value] = var = nodes.Name(name.value, 'load') - - if plural_expr is None: - plural_expr = var - num_called_num = name.value == 'num' - - parser.stream.expect('block_end') - - plural = plural_names = None - have_plural = False - referenced = set() - - # now parse until endtrans or pluralize - singular_names, singular = self._parse_block(parser, True) - if singular_names: - referenced.update(singular_names) - if plural_expr is None: - plural_expr = nodes.Name(singular_names[0], 'load') - num_called_num = singular_names[0] == 'num' - - # if we have a pluralize block, we parse that too - if parser.stream.current.test('name:pluralize'): - have_plural = True - next(parser.stream) - if parser.stream.current.type != 'block_end': - name = parser.stream.expect('name') - if name.value not in variables: - parser.fail('unknown variable %r for pluralization' % - name.value, name.lineno, - exc=TemplateAssertionError) - plural_expr = variables[name.value] - num_called_num = name.value == 'num' - parser.stream.expect('block_end') - plural_names, plural = self._parse_block(parser, False) - next(parser.stream) - referenced.update(plural_names) - else: - next(parser.stream) - - # register free names as simple name expressions - for var in referenced: - if var not in variables: - variables[var] = nodes.Name(var, 'load') - - if not have_plural: - plural_expr = None - elif plural_expr is None: - parser.fail('pluralize without variables', lineno) - - node = self._make_node(singular, plural, variables, plural_expr, - bool(referenced), - num_called_num and have_plural) - node.set_lineno(lineno) - return node - - def _parse_block(self, parser, allow_pluralize): - """Parse until the next block tag with a given name.""" - referenced = [] - buf = [] - while 1: - if parser.stream.current.type == 'data': - buf.append(parser.stream.current.value.replace('%', '%%')) - next(parser.stream) - elif parser.stream.current.type == 'variable_begin': - next(parser.stream) - name = parser.stream.expect('name').value - referenced.append(name) - buf.append('%%(%s)s' % name) - parser.stream.expect('variable_end') - elif parser.stream.current.type == 'block_begin': - next(parser.stream) - if parser.stream.current.test('name:endtrans'): - break - elif parser.stream.current.test('name:pluralize'): - if allow_pluralize: - break - parser.fail('a translatable section can have only one ' - 'pluralize section') - parser.fail('control structures in translatable sections are ' - 'not allowed') - elif parser.stream.eos: - parser.fail('unclosed translation block') - else: - assert False, 'internal parser error' - - return referenced, concat(buf) - - def _make_node(self, singular, plural, variables, plural_expr, - vars_referenced, num_called_num): - """Generates a useful node from the data provided.""" - # no variables referenced? no need to escape for old style - # gettext invocations only if there are vars. - if not vars_referenced and not self.environment.newstyle_gettext: - singular = singular.replace('%%', '%') - if plural: - plural = plural.replace('%%', '%') - - # singular only: - if plural_expr is None: - gettext = nodes.Name('gettext', 'load') - node = nodes.Call(gettext, [nodes.Const(singular)], - [], None, None) - - # singular and plural - else: - ngettext = nodes.Name('ngettext', 'load') - node = nodes.Call(ngettext, [ - nodes.Const(singular), - nodes.Const(plural), - plural_expr - ], [], None, None) - - # in case newstyle gettext is used, the method is powerful - # enough to handle the variable expansion and autoescape - # handling itself - if self.environment.newstyle_gettext: - for key, value in variables.iteritems(): - # the function adds that later anyways in case num was - # called num, so just skip it. - if num_called_num and key == 'num': - continue - node.kwargs.append(nodes.Keyword(key, value)) - - # otherwise do that here - else: - # mark the return value as safe if we are in an - # environment with autoescaping turned on - node = nodes.MarkSafeIfAutoescape(node) - if variables: - node = nodes.Mod(node, nodes.Dict([ - nodes.Pair(nodes.Const(key), value) - for key, value in variables.items() - ])) - return nodes.Output([node]) - - -class ExprStmtExtension(Extension): - """Adds a `do` tag to Jinja2 that works like the print statement just - that it doesn't print the return value. - """ - tags = set(['do']) - - def parse(self, parser): - node = nodes.ExprStmt(lineno=next(parser.stream).lineno) - node.node = parser.parse_tuple() - return node - - -class LoopControlExtension(Extension): - """Adds break and continue to the template engine.""" - tags = set(['break', 'continue']) - - def parse(self, parser): - token = next(parser.stream) - if token.value == 'break': - return nodes.Break(lineno=token.lineno) - return nodes.Continue(lineno=token.lineno) - - -class WithExtension(Extension): - """Adds support for a django-like with block.""" - tags = set(['with']) - - def parse(self, parser): - node = nodes.Scope(lineno=next(parser.stream).lineno) - assignments = [] - while parser.stream.current.type != 'block_end': - lineno = parser.stream.current.lineno - if assignments: - parser.stream.expect('comma') - target = parser.parse_assign_target() - parser.stream.expect('assign') - expr = parser.parse_expression() - assignments.append(nodes.Assign(target, expr, lineno=lineno)) - node.body = assignments + \ - list(parser.parse_statements(('name:endwith',), - drop_needle=True)) - return node - - -class AutoEscapeExtension(Extension): - """Changes auto escape rules for a scope.""" - tags = set(['autoescape']) - - def parse(self, parser): - node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno) - node.options = [ - nodes.Keyword('autoescape', parser.parse_expression()) - ] - node.body = parser.parse_statements(('name:endautoescape',), - drop_needle=True) - return nodes.Scope([node]) - - -def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, - babel_style=True): - """Extract localizable strings from the given template node. Per - default this function returns matches in babel style that means non string - parameters as well as keyword arguments are returned as `None`. This - allows Babel to figure out what you really meant if you are using - gettext functions that allow keyword arguments for placeholder expansion. - If you don't want that behavior set the `babel_style` parameter to `False` - which causes only strings to be returned and parameters are always stored - in tuples. As a consequence invalid gettext calls (calls without a single - string parameter or string parameters after non-string parameters) are - skipped. - - This example explains the behavior: - - >>> from jinja2 import Environment - >>> env = Environment() - >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') - >>> list(extract_from_ast(node)) - [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] - >>> list(extract_from_ast(node, babel_style=False)) - [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] - - For every string found this function yields a ``(lineno, function, - message)`` tuple, where: - - * ``lineno`` is the number of the line on which the string was found, - * ``function`` is the name of the ``gettext`` function used (if the - string was extracted from embedded Python code), and - * ``message`` is the string itself (a ``unicode`` object, or a tuple - of ``unicode`` objects for functions with multiple string arguments). - - This extraction function operates on the AST and is because of that unable - to extract any comments. For comment support you have to use the babel - extraction interface or extract comments yourself. - """ - for node in node.find_all(nodes.Call): - if not isinstance(node.node, nodes.Name) or \ - node.node.name not in gettext_functions: - continue - - strings = [] - for arg in node.args: - if isinstance(arg, nodes.Const) and \ - isinstance(arg.value, basestring): - strings.append(arg.value) - else: - strings.append(None) - - for arg in node.kwargs: - strings.append(None) - if node.dyn_args is not None: - strings.append(None) - if node.dyn_kwargs is not None: - strings.append(None) - - if not babel_style: - strings = tuple(x for x in strings if x is not None) - if not strings: - continue - else: - if len(strings) == 1: - strings = strings[0] - else: - strings = tuple(strings) - yield node.lineno, node.node.name, strings - - -class _CommentFinder(object): - """Helper class to find comments in a token stream. Can only - find comments for gettext calls forwards. Once the comment - from line 4 is found, a comment for line 1 will not return a - usable value. - """ - - def __init__(self, tokens, comment_tags): - self.tokens = tokens - self.comment_tags = comment_tags - self.offset = 0 - self.last_lineno = 0 - - def find_backwards(self, offset): - try: - for _, token_type, token_value in \ - reversed(self.tokens[self.offset:offset]): - if token_type in ('comment', 'linecomment'): - try: - prefix, comment = token_value.split(None, 1) - except ValueError: - continue - if prefix in self.comment_tags: - return [comment.rstrip()] - return [] - finally: - self.offset = offset - - def find_comments(self, lineno): - if not self.comment_tags or self.last_lineno > lineno: - return [] - for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): - if token_lineno > lineno: - return self.find_backwards(self.offset + idx) - return self.find_backwards(len(self.tokens)) - - -def babel_extract(fileobj, keywords, comment_tags, options): - """Babel extraction method for Jinja templates. - - .. versionchanged:: 2.3 - Basic support for translation comments was added. If `comment_tags` - is now set to a list of keywords for extraction, the extractor will - try to find the best preceeding comment that begins with one of the - keywords. For best results, make sure to not have more than one - gettext call in one line of code and the matching comment in the - same line or the line before. - - .. versionchanged:: 2.5.1 - The `newstyle_gettext` flag can be set to `True` to enable newstyle - gettext calls. - - :param fileobj: the file-like object the messages should be extracted from - :param keywords: a list of keywords (i.e. function names) that should be - recognized as translation functions - :param comment_tags: a list of translator tags to search for and include - in the results. - :param options: a dictionary of additional options (optional) - :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. - (comments will be empty currently) - """ - extensions = set() - for extension in options.get('extensions', '').split(','): - extension = extension.strip() - if not extension: - continue - extensions.add(import_string(extension)) - if InternationalizationExtension not in extensions: - extensions.add(InternationalizationExtension) - - def getbool(options, key, default=False): - options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true') - - environment = Environment( - options.get('block_start_string', BLOCK_START_STRING), - options.get('block_end_string', BLOCK_END_STRING), - options.get('variable_start_string', VARIABLE_START_STRING), - options.get('variable_end_string', VARIABLE_END_STRING), - options.get('comment_start_string', COMMENT_START_STRING), - options.get('comment_end_string', COMMENT_END_STRING), - options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, - options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, - getbool(options, 'trim_blocks', TRIM_BLOCKS), - NEWLINE_SEQUENCE, frozenset(extensions), - cache_size=0, - auto_reload=False - ) - - if getbool(options, 'newstyle_gettext'): - environment.newstyle_gettext = True - - source = fileobj.read().decode(options.get('encoding', 'utf-8')) - try: - node = environment.parse(source) - tokens = list(environment.lex(environment.preprocess(source))) - except TemplateSyntaxError, e: - # skip templates with syntax errors - return - - finder = _CommentFinder(tokens, comment_tags) - for lineno, func, message in extract_from_ast(node, keywords): - yield lineno, func, message, finder.find_comments(lineno) - - -#: nicer import names -i18n = InternationalizationExtension -do = ExprStmtExtension -loopcontrols = LoopControlExtension -with_ = WithExtension -autoescape = AutoEscapeExtension diff --git a/module/lib/jinja2/filters.py b/module/lib/jinja2/filters.py deleted file mode 100644 index d1848e434..000000000 --- a/module/lib/jinja2/filters.py +++ /dev/null @@ -1,719 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.filters - ~~~~~~~~~~~~~~ - - Bundled jinja filters. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -import math -from random import choice -from operator import itemgetter -from itertools import imap, groupby -from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode -from jinja2.runtime import Undefined -from jinja2.exceptions import FilterArgumentError, SecurityError - - -_word_re = re.compile(r'\w+(?u)') - - -def contextfilter(f): - """Decorator for marking context dependent filters. The current - :class:`Context` will be passed as first argument. - """ - f.contextfilter = True - return f - - -def evalcontextfilter(f): - """Decorator for marking eval-context dependent filters. An eval - context object is passed as first argument. For more information - about the eval context, see :ref:`eval-context`. - - .. versionadded:: 2.4 - """ - f.evalcontextfilter = True - return f - - -def environmentfilter(f): - """Decorator for marking evironment dependent filters. The current - :class:`Environment` is passed to the filter as first argument. - """ - f.environmentfilter = True - return f - - -def do_forceescape(value): - """Enforce HTML escaping. This will probably double escape variables.""" - if hasattr(value, '__html__'): - value = value.__html__() - return escape(unicode(value)) - - -@evalcontextfilter -def do_replace(eval_ctx, s, old, new, count=None): - """Return a copy of the value with all occurrences of a substring - replaced with a new one. The first argument is the substring - that should be replaced, the second is the replacement string. - If the optional third argument ``count`` is given, only the first - ``count`` occurrences are replaced: - - .. sourcecode:: jinja - - {{ "Hello World"|replace("Hello", "Goodbye") }} - -> Goodbye World - - {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} - -> d'oh, d'oh, aaargh - """ - if count is None: - count = -1 - if not eval_ctx.autoescape: - return unicode(s).replace(unicode(old), unicode(new), count) - if hasattr(old, '__html__') or hasattr(new, '__html__') and \ - not hasattr(s, '__html__'): - s = escape(s) - else: - s = soft_unicode(s) - return s.replace(soft_unicode(old), soft_unicode(new), count) - - -def do_upper(s): - """Convert a value to uppercase.""" - return soft_unicode(s).upper() - - -def do_lower(s): - """Convert a value to lowercase.""" - return soft_unicode(s).lower() - - -@evalcontextfilter -def do_xmlattr(_eval_ctx, d, autospace=True): - """Create an SGML/XML attribute string based on the items in a dict. - All values that are neither `none` nor `undefined` are automatically - escaped: - - .. sourcecode:: html+jinja - - <ul{{ {'class': 'my_list', 'missing': none, - 'id': 'list-%d'|format(variable)}|xmlattr }}> - ... - </ul> - - Results in something like this: - - .. sourcecode:: html - - <ul class="my_list" id="list-42"> - ... - </ul> - - As you can see it automatically prepends a space in front of the item - if the filter returned something unless the second parameter is false. - """ - rv = u' '.join( - u'%s="%s"' % (escape(key), escape(value)) - for key, value in d.iteritems() - if value is not None and not isinstance(value, Undefined) - ) - if autospace and rv: - rv = u' ' + rv - if _eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -def do_capitalize(s): - """Capitalize a value. The first character will be uppercase, all others - lowercase. - """ - return soft_unicode(s).capitalize() - - -def do_title(s): - """Return a titlecased version of the value. I.e. words will start with - uppercase letters, all remaining characters are lowercase. - """ - return soft_unicode(s).title() - - -def do_dictsort(value, case_sensitive=False, by='key'): - """Sort a dict and yield (key, value) pairs. Because python dicts are - unsorted you may want to use this function to order them by either - key or value: - - .. sourcecode:: jinja - - {% for item in mydict|dictsort %} - sort the dict by key, case insensitive - - {% for item in mydict|dicsort(true) %} - sort the dict by key, case sensitive - - {% for item in mydict|dictsort(false, 'value') %} - sort the dict by key, case insensitive, sorted - normally and ordered by value. - """ - if by == 'key': - pos = 0 - elif by == 'value': - pos = 1 - else: - raise FilterArgumentError('You can only sort by either ' - '"key" or "value"') - def sort_func(item): - value = item[pos] - if isinstance(value, basestring) and not case_sensitive: - value = value.lower() - return value - - return sorted(value.items(), key=sort_func) - - -def do_sort(value, reverse=False, case_sensitive=False): - """Sort an iterable. Per default it sorts ascending, if you pass it - true as first argument it will reverse the sorting. - - If the iterable is made of strings the third parameter can be used to - control the case sensitiveness of the comparison which is disabled by - default. - - .. sourcecode:: jinja - - {% for item in iterable|sort %} - ... - {% endfor %} - """ - if not case_sensitive: - def sort_func(item): - if isinstance(item, basestring): - item = item.lower() - return item - else: - sort_func = None - return sorted(value, key=sort_func, reverse=reverse) - - -def do_default(value, default_value=u'', boolean=False): - """If the value is undefined it will return the passed default value, - otherwise the value of the variable: - - .. sourcecode:: jinja - - {{ my_variable|default('my_variable is not defined') }} - - This will output the value of ``my_variable`` if the variable was - defined, otherwise ``'my_variable is not defined'``. If you want - to use default with variables that evaluate to false you have to - set the second parameter to `true`: - - .. sourcecode:: jinja - - {{ ''|default('the string was empty', true) }} - """ - if (boolean and not value) or isinstance(value, Undefined): - return default_value - return value - - -@evalcontextfilter -def do_join(eval_ctx, value, d=u''): - """Return a string which is the concatenation of the strings in the - sequence. The separator between elements is an empty string per - default, you can define it with the optional parameter: - - .. sourcecode:: jinja - - {{ [1, 2, 3]|join('|') }} - -> 1|2|3 - - {{ [1, 2, 3]|join }} - -> 123 - """ - # no automatic escaping? joining is a lot eaiser then - if not eval_ctx.autoescape: - return unicode(d).join(imap(unicode, value)) - - # if the delimiter doesn't have an html representation we check - # if any of the items has. If yes we do a coercion to Markup - if not hasattr(d, '__html__'): - value = list(value) - do_escape = False - for idx, item in enumerate(value): - if hasattr(item, '__html__'): - do_escape = True - else: - value[idx] = unicode(item) - if do_escape: - d = escape(d) - else: - d = unicode(d) - return d.join(value) - - # no html involved, to normal joining - return soft_unicode(d).join(imap(soft_unicode, value)) - - -def do_center(value, width=80): - """Centers the value in a field of a given width.""" - return unicode(value).center(width) - - -@environmentfilter -def do_first(environment, seq): - """Return the first item of a sequence.""" - try: - return iter(seq).next() - except StopIteration: - return environment.undefined('No first item, sequence was empty.') - - -@environmentfilter -def do_last(environment, seq): - """Return the last item of a sequence.""" - try: - return iter(reversed(seq)).next() - except StopIteration: - return environment.undefined('No last item, sequence was empty.') - - -@environmentfilter -def do_random(environment, seq): - """Return a random item from the sequence.""" - try: - return choice(seq) - except IndexError: - return environment.undefined('No random item, sequence was empty.') - - -def do_filesizeformat(value, binary=False): - """Format the value like a 'human-readable' file size (i.e. 13 KB, - 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega, - giga, etc.), if the second parameter is set to `True` the binary - prefixes are used (mebi, gibi). - """ - bytes = float(value) - base = binary and 1024 or 1000 - middle = binary and 'i' or '' - if bytes < base: - return "%d Byte%s" % (bytes, bytes != 1 and 's' or '') - elif bytes < base * base: - return "%.1f K%sB" % (bytes / base, middle) - elif bytes < base * base * base: - return "%.1f M%sB" % (bytes / (base * base), middle) - return "%.1f G%sB" % (bytes / (base * base * base), middle) - - -def do_pprint(value, verbose=False): - """Pretty print a variable. Useful for debugging. - - With Jinja 1.2 onwards you can pass it a parameter. If this parameter - is truthy the output will be more verbose (this requires `pretty`) - """ - return pformat(value, verbose=verbose) - - -@evalcontextfilter -def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False): - """Converts URLs in plain text into clickable links. - - If you pass the filter an additional integer it will shorten the urls - to that number. Also a third argument exists that makes the urls - "nofollow": - - .. sourcecode:: jinja - - {{ mytext|urlize(40, true) }} - links are shortened to 40 chars and defined with rel="nofollow" - """ - rv = urlize(value, trim_url_limit, nofollow) - if eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -def do_indent(s, width=4, indentfirst=False): - """Return a copy of the passed string, each line indented by - 4 spaces. The first line is not indented. If you want to - change the number of spaces or indent the first line too - you can pass additional parameters to the filter: - - .. sourcecode:: jinja - - {{ mytext|indent(2, true) }} - indent by two spaces and indent the first line too. - """ - indention = u' ' * width - rv = (u'\n' + indention).join(s.splitlines()) - if indentfirst: - rv = indention + rv - return rv - - -def do_truncate(s, length=255, killwords=False, end='...'): - """Return a truncated copy of the string. The length is specified - with the first parameter which defaults to ``255``. If the second - parameter is ``true`` the filter will cut the text at length. Otherwise - it will try to save the last word. If the text was in fact - truncated it will append an ellipsis sign (``"..."``). If you want a - different ellipsis sign than ``"..."`` you can specify it using the - third parameter. - - .. sourcecode jinja:: - - {{ mytext|truncate(300, false, '»') }} - truncate mytext to 300 chars, don't split up words, use a - right pointing double arrow as ellipsis sign. - """ - if len(s) <= length: - return s - elif killwords: - return s[:length] + end - words = s.split(' ') - result = [] - m = 0 - for word in words: - m += len(word) + 1 - if m > length: - break - result.append(word) - result.append(end) - return u' '.join(result) - - -def do_wordwrap(s, width=79, break_long_words=True): - """ - Return a copy of the string passed to the filter wrapped after - ``79`` characters. You can override this default using the first - parameter. If you set the second parameter to `false` Jinja will not - split words apart if they are longer than `width`. - """ - import textwrap - return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False, - replace_whitespace=False, - break_long_words=break_long_words)) - - -def do_wordcount(s): - """Count the words in that string.""" - return len(_word_re.findall(s)) - - -def do_int(value, default=0): - """Convert the value into an integer. If the - conversion doesn't work it will return ``0``. You can - override this default using the first parameter. - """ - try: - return int(value) - except (TypeError, ValueError): - # this quirk is necessary so that "42.23"|int gives 42. - try: - return int(float(value)) - except (TypeError, ValueError): - return default - - -def do_float(value, default=0.0): - """Convert the value into a floating point number. If the - conversion doesn't work it will return ``0.0``. You can - override this default using the first parameter. - """ - try: - return float(value) - except (TypeError, ValueError): - return default - - -def do_format(value, *args, **kwargs): - """ - Apply python string formatting on an object: - - .. sourcecode:: jinja - - {{ "%s - %s"|format("Hello?", "Foo!") }} - -> Hello? - Foo! - """ - if args and kwargs: - raise FilterArgumentError('can\'t handle positional and keyword ' - 'arguments at the same time') - return soft_unicode(value) % (kwargs or args) - - -def do_trim(value): - """Strip leading and trailing whitespace.""" - return soft_unicode(value).strip() - - -def do_striptags(value): - """Strip SGML/XML tags and replace adjacent whitespace by one space. - """ - if hasattr(value, '__html__'): - value = value.__html__() - return Markup(unicode(value)).striptags() - - -def do_slice(value, slices, fill_with=None): - """Slice an iterator and return a list of lists containing - those items. Useful if you want to create a div containing - three ul tags that represent columns: - - .. sourcecode:: html+jinja - - <div class="columwrapper"> - {%- for column in items|slice(3) %} - <ul class="column-{{ loop.index }}"> - {%- for item in column %} - <li>{{ item }}</li> - {%- endfor %} - </ul> - {%- endfor %} - </div> - - If you pass it a second argument it's used to fill missing - values on the last iteration. - """ - seq = list(value) - length = len(seq) - items_per_slice = length // slices - slices_with_extra = length % slices - offset = 0 - for slice_number in xrange(slices): - start = offset + slice_number * items_per_slice - if slice_number < slices_with_extra: - offset += 1 - end = offset + (slice_number + 1) * items_per_slice - tmp = seq[start:end] - if fill_with is not None and slice_number >= slices_with_extra: - tmp.append(fill_with) - yield tmp - - -def do_batch(value, linecount, fill_with=None): - """ - A filter that batches items. It works pretty much like `slice` - just the other way round. It returns a list of lists with the - given number of items. If you provide a second parameter this - is used to fill missing items. See this example: - - .. sourcecode:: html+jinja - - <table> - {%- for row in items|batch(3, ' ') %} - <tr> - {%- for column in row %} - <td>{{ column }}</td> - {%- endfor %} - </tr> - {%- endfor %} - </table> - """ - result = [] - tmp = [] - for item in value: - if len(tmp) == linecount: - yield tmp - tmp = [] - tmp.append(item) - if tmp: - if fill_with is not None and len(tmp) < linecount: - tmp += [fill_with] * (linecount - len(tmp)) - yield tmp - - -def do_round(value, precision=0, method='common'): - """Round the number to a given precision. The first - parameter specifies the precision (default is ``0``), the - second the rounding method: - - - ``'common'`` rounds either up or down - - ``'ceil'`` always rounds up - - ``'floor'`` always rounds down - - If you don't specify a method ``'common'`` is used. - - .. sourcecode:: jinja - - {{ 42.55|round }} - -> 43.0 - {{ 42.55|round(1, 'floor') }} - -> 42.5 - - Note that even if rounded to 0 precision, a float is returned. If - you need a real integer, pipe it through `int`: - - .. sourcecode:: jinja - - {{ 42.55|round|int }} - -> 43 - """ - if not method in ('common', 'ceil', 'floor'): - raise FilterArgumentError('method must be common, ceil or floor') - if method == 'common': - return round(value, precision) - func = getattr(math, method) - return func(value * (10 ** precision)) / (10 ** precision) - - -@environmentfilter -def do_groupby(environment, value, attribute): - """Group a sequence of objects by a common attribute. - - If you for example have a list of dicts or objects that represent persons - with `gender`, `first_name` and `last_name` attributes and you want to - group all users by genders you can do something like the following - snippet: - - .. sourcecode:: html+jinja - - <ul> - {% for group in persons|groupby('gender') %} - <li>{{ group.grouper }}<ul> - {% for person in group.list %} - <li>{{ person.first_name }} {{ person.last_name }}</li> - {% endfor %}</ul></li> - {% endfor %} - </ul> - - Additionally it's possible to use tuple unpacking for the grouper and - list: - - .. sourcecode:: html+jinja - - <ul> - {% for grouper, list in persons|groupby('gender') %} - ... - {% endfor %} - </ul> - - As you can see the item we're grouping by is stored in the `grouper` - attribute and the `list` contains all the objects that have this grouper - in common. - """ - expr = lambda x: environment.getitem(x, attribute) - return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr))) - - -class _GroupTuple(tuple): - __slots__ = () - grouper = property(itemgetter(0)) - list = property(itemgetter(1)) - - def __new__(cls, (key, value)): - return tuple.__new__(cls, (key, list(value))) - - -def do_list(value): - """Convert the value into a list. If it was a string the returned list - will be a list of characters. - """ - return list(value) - - -def do_mark_safe(value): - """Mark the value as safe which means that in an environment with automatic - escaping enabled this variable will not be escaped. - """ - return Markup(value) - - -def do_mark_unsafe(value): - """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" - return unicode(value) - - -def do_reverse(value): - """Reverse the object or return an iterator the iterates over it the other - way round. - """ - if isinstance(value, basestring): - return value[::-1] - try: - return reversed(value) - except TypeError: - try: - rv = list(value) - rv.reverse() - return rv - except TypeError: - raise FilterArgumentError('argument must be iterable') - - -@environmentfilter -def do_attr(environment, obj, name): - """Get an attribute of an object. ``foo|attr("bar")`` works like - ``foo["bar"]`` just that always an attribute is returned and items are not - looked up. - - See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. - """ - try: - name = str(name) - except UnicodeError: - pass - else: - try: - value = getattr(obj, name) - except AttributeError: - pass - else: - if environment.sandboxed and not \ - environment.is_safe_attribute(obj, name, value): - return environment.unsafe_undefined(obj, name) - return value - return environment.undefined(obj=obj, name=name) - - -FILTERS = { - 'attr': do_attr, - 'replace': do_replace, - 'upper': do_upper, - 'lower': do_lower, - 'escape': escape, - 'e': escape, - 'forceescape': do_forceescape, - 'capitalize': do_capitalize, - 'title': do_title, - 'default': do_default, - 'd': do_default, - 'join': do_join, - 'count': len, - 'dictsort': do_dictsort, - 'sort': do_sort, - 'length': len, - 'reverse': do_reverse, - 'center': do_center, - 'indent': do_indent, - 'title': do_title, - 'capitalize': do_capitalize, - 'first': do_first, - 'last': do_last, - 'random': do_random, - 'filesizeformat': do_filesizeformat, - 'pprint': do_pprint, - 'truncate': do_truncate, - 'wordwrap': do_wordwrap, - 'wordcount': do_wordcount, - 'int': do_int, - 'float': do_float, - 'string': soft_unicode, - 'list': do_list, - 'urlize': do_urlize, - 'format': do_format, - 'trim': do_trim, - 'striptags': do_striptags, - 'slice': do_slice, - 'batch': do_batch, - 'sum': sum, - 'abs': abs, - 'round': do_round, - 'groupby': do_groupby, - 'safe': do_mark_safe, - 'xmlattr': do_xmlattr -} diff --git a/module/lib/jinja2/lexer.py b/module/lib/jinja2/lexer.py deleted file mode 100644 index 0d3f69617..000000000 --- a/module/lib/jinja2/lexer.py +++ /dev/null @@ -1,681 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.lexer - ~~~~~~~~~~~~ - - This module implements a Jinja / Python combination lexer. The - `Lexer` class provided by this module is used to do some preprocessing - for Jinja. - - On the one hand it filters out invalid operators like the bitshift - operators we don't allow in templates. On the other hand it separates - template code and python code in expressions. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -from operator import itemgetter -from collections import deque -from jinja2.exceptions import TemplateSyntaxError -from jinja2.utils import LRUCache, next - - -# cache for the lexers. Exists in order to be able to have multiple -# environments with the same lexer -_lexer_cache = LRUCache(50) - -# static regular expressions -whitespace_re = re.compile(r'\s+', re.U) -string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" - r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) -integer_re = re.compile(r'\d+') - -# we use the unicode identifier rule if this python version is able -# to handle unicode identifiers, otherwise the standard ASCII one. -try: - compile('föö', '<unknown>', 'eval') -except SyntaxError: - name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b') -else: - from jinja2 import _stringdefs - name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start, - _stringdefs.xid_continue)) - -float_re = re.compile(r'(?<!\.)\d+\.\d+') -newline_re = re.compile(r'(\r\n|\r|\n)') - -# internal the tokens and keep references to them -TOKEN_ADD = intern('add') -TOKEN_ASSIGN = intern('assign') -TOKEN_COLON = intern('colon') -TOKEN_COMMA = intern('comma') -TOKEN_DIV = intern('div') -TOKEN_DOT = intern('dot') -TOKEN_EQ = intern('eq') -TOKEN_FLOORDIV = intern('floordiv') -TOKEN_GT = intern('gt') -TOKEN_GTEQ = intern('gteq') -TOKEN_LBRACE = intern('lbrace') -TOKEN_LBRACKET = intern('lbracket') -TOKEN_LPAREN = intern('lparen') -TOKEN_LT = intern('lt') -TOKEN_LTEQ = intern('lteq') -TOKEN_MOD = intern('mod') -TOKEN_MUL = intern('mul') -TOKEN_NE = intern('ne') -TOKEN_PIPE = intern('pipe') -TOKEN_POW = intern('pow') -TOKEN_RBRACE = intern('rbrace') -TOKEN_RBRACKET = intern('rbracket') -TOKEN_RPAREN = intern('rparen') -TOKEN_SEMICOLON = intern('semicolon') -TOKEN_SUB = intern('sub') -TOKEN_TILDE = intern('tilde') -TOKEN_WHITESPACE = intern('whitespace') -TOKEN_FLOAT = intern('float') -TOKEN_INTEGER = intern('integer') -TOKEN_NAME = intern('name') -TOKEN_STRING = intern('string') -TOKEN_OPERATOR = intern('operator') -TOKEN_BLOCK_BEGIN = intern('block_begin') -TOKEN_BLOCK_END = intern('block_end') -TOKEN_VARIABLE_BEGIN = intern('variable_begin') -TOKEN_VARIABLE_END = intern('variable_end') -TOKEN_RAW_BEGIN = intern('raw_begin') -TOKEN_RAW_END = intern('raw_end') -TOKEN_COMMENT_BEGIN = intern('comment_begin') -TOKEN_COMMENT_END = intern('comment_end') -TOKEN_COMMENT = intern('comment') -TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin') -TOKEN_LINESTATEMENT_END = intern('linestatement_end') -TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin') -TOKEN_LINECOMMENT_END = intern('linecomment_end') -TOKEN_LINECOMMENT = intern('linecomment') -TOKEN_DATA = intern('data') -TOKEN_INITIAL = intern('initial') -TOKEN_EOF = intern('eof') - -# bind operators to token types -operators = { - '+': TOKEN_ADD, - '-': TOKEN_SUB, - '/': TOKEN_DIV, - '//': TOKEN_FLOORDIV, - '*': TOKEN_MUL, - '%': TOKEN_MOD, - '**': TOKEN_POW, - '~': TOKEN_TILDE, - '[': TOKEN_LBRACKET, - ']': TOKEN_RBRACKET, - '(': TOKEN_LPAREN, - ')': TOKEN_RPAREN, - '{': TOKEN_LBRACE, - '}': TOKEN_RBRACE, - '==': TOKEN_EQ, - '!=': TOKEN_NE, - '>': TOKEN_GT, - '>=': TOKEN_GTEQ, - '<': TOKEN_LT, - '<=': TOKEN_LTEQ, - '=': TOKEN_ASSIGN, - '.': TOKEN_DOT, - ':': TOKEN_COLON, - '|': TOKEN_PIPE, - ',': TOKEN_COMMA, - ';': TOKEN_SEMICOLON -} - -reverse_operators = dict([(v, k) for k, v in operators.iteritems()]) -assert len(operators) == len(reverse_operators), 'operators dropped' -operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in - sorted(operators, key=lambda x: -len(x)))) - -ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT, - TOKEN_COMMENT_END, TOKEN_WHITESPACE, - TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN, - TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT]) -ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA, - TOKEN_COMMENT, TOKEN_LINECOMMENT]) - - -def _describe_token_type(token_type): - if token_type in reverse_operators: - return reverse_operators[token_type] - return { - TOKEN_COMMENT_BEGIN: 'begin of comment', - TOKEN_COMMENT_END: 'end of comment', - TOKEN_COMMENT: 'comment', - TOKEN_LINECOMMENT: 'comment', - TOKEN_BLOCK_BEGIN: 'begin of statement block', - TOKEN_BLOCK_END: 'end of statement block', - TOKEN_VARIABLE_BEGIN: 'begin of print statement', - TOKEN_VARIABLE_END: 'end of print statement', - TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement', - TOKEN_LINESTATEMENT_END: 'end of line statement', - TOKEN_DATA: 'template data / text', - TOKEN_EOF: 'end of template' - }.get(token_type, token_type) - - -def describe_token(token): - """Returns a description of the token.""" - if token.type == 'name': - return token.value - return _describe_token_type(token.type) - - -def describe_token_expr(expr): - """Like `describe_token` but for token expressions.""" - if ':' in expr: - type, value = expr.split(':', 1) - if type == 'name': - return value - else: - type = expr - return _describe_token_type(type) - - -def count_newlines(value): - """Count the number of newline characters in the string. This is - useful for extensions that filter a stream. - """ - return len(newline_re.findall(value)) - - -def compile_rules(environment): - """Compiles all the rules from the environment into a list of rules.""" - e = re.escape - rules = [ - (len(environment.comment_start_string), 'comment', - e(environment.comment_start_string)), - (len(environment.block_start_string), 'block', - e(environment.block_start_string)), - (len(environment.variable_start_string), 'variable', - e(environment.variable_start_string)) - ] - - if environment.line_statement_prefix is not None: - rules.append((len(environment.line_statement_prefix), 'linestatement', - r'^\s*' + e(environment.line_statement_prefix))) - if environment.line_comment_prefix is not None: - rules.append((len(environment.line_comment_prefix), 'linecomment', - r'(?:^|(?<=\S))[^\S\r\n]*' + - e(environment.line_comment_prefix))) - - return [x[1:] for x in sorted(rules, reverse=True)] - - -class Failure(object): - """Class that raises a `TemplateSyntaxError` if called. - Used by the `Lexer` to specify known errors. - """ - - def __init__(self, message, cls=TemplateSyntaxError): - self.message = message - self.error_class = cls - - def __call__(self, lineno, filename): - raise self.error_class(self.message, lineno, filename) - - -class Token(tuple): - """Token class.""" - __slots__ = () - lineno, type, value = (property(itemgetter(x)) for x in range(3)) - - def __new__(cls, lineno, type, value): - return tuple.__new__(cls, (lineno, intern(str(type)), value)) - - def __str__(self): - if self.type in reverse_operators: - return reverse_operators[self.type] - elif self.type == 'name': - return self.value - return self.type - - def test(self, expr): - """Test a token against a token expression. This can either be a - token type or ``'token_type:token_value'``. This can only test - against string values and types. - """ - # here we do a regular string equality check as test_any is usually - # passed an iterable of not interned strings. - if self.type == expr: - return True - elif ':' in expr: - return expr.split(':', 1) == [self.type, self.value] - return False - - def test_any(self, *iterable): - """Test against multiple token expressions.""" - for expr in iterable: - if self.test(expr): - return True - return False - - def __repr__(self): - return 'Token(%r, %r, %r)' % ( - self.lineno, - self.type, - self.value - ) - - -class TokenStreamIterator(object): - """The iterator for tokenstreams. Iterate over the stream - until the eof token is reached. - """ - - def __init__(self, stream): - self.stream = stream - - def __iter__(self): - return self - - def next(self): - token = self.stream.current - if token.type is TOKEN_EOF: - self.stream.close() - raise StopIteration() - next(self.stream) - return token - - -class TokenStream(object): - """A token stream is an iterable that yields :class:`Token`\s. The - parser however does not iterate over it but calls :meth:`next` to go - one token ahead. The current active token is stored as :attr:`current`. - """ - - def __init__(self, generator, name, filename): - self._next = iter(generator).next - self._pushed = deque() - self.name = name - self.filename = filename - self.closed = False - self.current = Token(1, TOKEN_INITIAL, '') - next(self) - - def __iter__(self): - return TokenStreamIterator(self) - - def __nonzero__(self): - return bool(self._pushed) or self.current.type is not TOKEN_EOF - - eos = property(lambda x: not x, doc="Are we at the end of the stream?") - - def push(self, token): - """Push a token back to the stream.""" - self._pushed.append(token) - - def look(self): - """Look at the next token.""" - old_token = next(self) - result = self.current - self.push(result) - self.current = old_token - return result - - def skip(self, n=1): - """Got n tokens ahead.""" - for x in xrange(n): - next(self) - - def next_if(self, expr): - """Perform the token test and return the token if it matched. - Otherwise the return value is `None`. - """ - if self.current.test(expr): - return next(self) - - def skip_if(self, expr): - """Like :meth:`next_if` but only returns `True` or `False`.""" - return self.next_if(expr) is not None - - def next(self): - """Go one token ahead and return the old one""" - rv = self.current - if self._pushed: - self.current = self._pushed.popleft() - elif self.current.type is not TOKEN_EOF: - try: - self.current = self._next() - except StopIteration: - self.close() - return rv - - def close(self): - """Close the stream.""" - self.current = Token(self.current.lineno, TOKEN_EOF, '') - self._next = None - self.closed = True - - def expect(self, expr): - """Expect a given token type and return it. This accepts the same - argument as :meth:`jinja2.lexer.Token.test`. - """ - if not self.current.test(expr): - expr = describe_token_expr(expr) - if self.current.type is TOKEN_EOF: - raise TemplateSyntaxError('unexpected end of template, ' - 'expected %r.' % expr, - self.current.lineno, - self.name, self.filename) - raise TemplateSyntaxError("expected token %r, got %r" % - (expr, describe_token(self.current)), - self.current.lineno, - self.name, self.filename) - try: - return self.current - finally: - next(self) - - -def get_lexer(environment): - """Return a lexer which is probably cached.""" - key = (environment.block_start_string, - environment.block_end_string, - environment.variable_start_string, - environment.variable_end_string, - environment.comment_start_string, - environment.comment_end_string, - environment.line_statement_prefix, - environment.line_comment_prefix, - environment.trim_blocks, - environment.newline_sequence) - lexer = _lexer_cache.get(key) - if lexer is None: - lexer = Lexer(environment) - _lexer_cache[key] = lexer - return lexer - - -class Lexer(object): - """Class that implements a lexer for a given environment. Automatically - created by the environment class, usually you don't have to do that. - - Note that the lexer is not automatically bound to an environment. - Multiple environments can share the same lexer. - """ - - def __init__(self, environment): - # shortcuts - c = lambda x: re.compile(x, re.M | re.S) - e = re.escape - - # lexing rules for tags - tag_rules = [ - (whitespace_re, TOKEN_WHITESPACE, None), - (float_re, TOKEN_FLOAT, None), - (integer_re, TOKEN_INTEGER, None), - (name_re, TOKEN_NAME, None), - (string_re, TOKEN_STRING, None), - (operator_re, TOKEN_OPERATOR, None) - ] - - # assamble the root lexing rule. because "|" is ungreedy - # we have to sort by length so that the lexer continues working - # as expected when we have parsing rules like <% for block and - # <%= for variables. (if someone wants asp like syntax) - # variables are just part of the rules if variable processing - # is required. - root_tag_rules = compile_rules(environment) - - # block suffix if trimming is enabled - block_suffix_re = environment.trim_blocks and '\\n?' or '' - - self.newline_sequence = environment.newline_sequence - - # global lexing rules - self.rules = { - 'root': [ - # directives - (c('(.*?)(?:%s)' % '|'.join( - [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % ( - e(environment.block_start_string), - e(environment.block_start_string), - e(environment.block_end_string), - e(environment.block_end_string) - )] + [ - r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, r) - for n, r in root_tag_rules - ])), (TOKEN_DATA, '#bygroup'), '#bygroup'), - # data - (c('.+'), TOKEN_DATA, None) - ], - # comments - TOKEN_COMMENT_BEGIN: [ - (c(r'(.*?)((?:\-%s\s*|%s)%s)' % ( - e(environment.comment_end_string), - e(environment.comment_end_string), - block_suffix_re - )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'), - (c('(.)'), (Failure('Missing end of comment tag'),), None) - ], - # blocks - TOKEN_BLOCK_BEGIN: [ - (c('(?:\-%s\s*|%s)%s' % ( - e(environment.block_end_string), - e(environment.block_end_string), - block_suffix_re - )), TOKEN_BLOCK_END, '#pop'), - ] + tag_rules, - # variables - TOKEN_VARIABLE_BEGIN: [ - (c('\-%s\s*|%s' % ( - e(environment.variable_end_string), - e(environment.variable_end_string) - )), TOKEN_VARIABLE_END, '#pop') - ] + tag_rules, - # raw block - TOKEN_RAW_BEGIN: [ - (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % ( - e(environment.block_start_string), - e(environment.block_start_string), - e(environment.block_end_string), - e(environment.block_end_string), - block_suffix_re - )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'), - (c('(.)'), (Failure('Missing end of raw directive'),), None) - ], - # line statements - TOKEN_LINESTATEMENT_BEGIN: [ - (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop') - ] + tag_rules, - # line comments - TOKEN_LINECOMMENT_BEGIN: [ - (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT, - TOKEN_LINECOMMENT_END), '#pop') - ] - } - - def _normalize_newlines(self, value): - """Called for strings and template data to normlize it to unicode.""" - return newline_re.sub(self.newline_sequence, value) - - def tokenize(self, source, name=None, filename=None, state=None): - """Calls tokeniter + tokenize and wraps it in a token stream. - """ - stream = self.tokeniter(source, name, filename, state) - return TokenStream(self.wrap(stream, name, filename), name, filename) - - def wrap(self, stream, name=None, filename=None): - """This is called with the stream as returned by `tokenize` and wraps - every token in a :class:`Token` and converts the value. - """ - for lineno, token, value in stream: - if token in ignored_tokens: - continue - elif token == 'linestatement_begin': - token = 'block_begin' - elif token == 'linestatement_end': - token = 'block_end' - # we are not interested in those tokens in the parser - elif token in ('raw_begin', 'raw_end'): - continue - elif token == 'data': - value = self._normalize_newlines(value) - elif token == 'keyword': - token = value - elif token == 'name': - value = str(value) - elif token == 'string': - # try to unescape string - try: - value = self._normalize_newlines(value[1:-1]) \ - .encode('ascii', 'backslashreplace') \ - .decode('unicode-escape') - except Exception, e: - msg = str(e).split(':')[-1].strip() - raise TemplateSyntaxError(msg, lineno, name, filename) - # if we can express it as bytestring (ascii only) - # we do that for support of semi broken APIs - # as datetime.datetime.strftime. On python 3 this - # call becomes a noop thanks to 2to3 - try: - value = str(value) - except UnicodeError: - pass - elif token == 'integer': - value = int(value) - elif token == 'float': - value = float(value) - elif token == 'operator': - token = operators[value] - yield Token(lineno, token, value) - - def tokeniter(self, source, name, filename=None, state=None): - """This method tokenizes the text and returns the tokens in a - generator. Use this method if you just want to tokenize a template. - """ - source = '\n'.join(unicode(source).splitlines()) - pos = 0 - lineno = 1 - stack = ['root'] - if state is not None and state != 'root': - assert state in ('variable', 'block'), 'invalid state' - stack.append(state + '_begin') - else: - state = 'root' - statetokens = self.rules[stack[-1]] - source_length = len(source) - - balancing_stack = [] - - while 1: - # tokenizer loop - for regex, tokens, new_state in statetokens: - m = regex.match(source, pos) - # if no match we try again with the next rule - if m is None: - continue - - # we only match blocks and variables if brances / parentheses - # are balanced. continue parsing with the lower rule which - # is the operator rule. do this only if the end tags look - # like operators - if balancing_stack and \ - tokens in ('variable_end', 'block_end', - 'linestatement_end'): - continue - - # tuples support more options - if isinstance(tokens, tuple): - for idx, token in enumerate(tokens): - # failure group - if token.__class__ is Failure: - raise token(lineno, filename) - # bygroup is a bit more complex, in that case we - # yield for the current token the first named - # group that matched - elif token == '#bygroup': - for key, value in m.groupdict().iteritems(): - if value is not None: - yield lineno, key, value - lineno += value.count('\n') - break - else: - raise RuntimeError('%r wanted to resolve ' - 'the token dynamically' - ' but no group matched' - % regex) - # normal group - else: - data = m.group(idx + 1) - if data or token not in ignore_if_empty: - yield lineno, token, data - lineno += data.count('\n') - - # strings as token just are yielded as it. - else: - data = m.group() - # update brace/parentheses balance - if tokens == 'operator': - if data == '{': - balancing_stack.append('}') - elif data == '(': - balancing_stack.append(')') - elif data == '[': - balancing_stack.append(']') - elif data in ('}', ')', ']'): - if not balancing_stack: - raise TemplateSyntaxError('unexpected \'%s\'' % - data, lineno, name, - filename) - expected_op = balancing_stack.pop() - if expected_op != data: - raise TemplateSyntaxError('unexpected \'%s\', ' - 'expected \'%s\'' % - (data, expected_op), - lineno, name, - filename) - # yield items - if data or tokens not in ignore_if_empty: - yield lineno, tokens, data - lineno += data.count('\n') - - # fetch new position into new variable so that we can check - # if there is a internal parsing error which would result - # in an infinite loop - pos2 = m.end() - - # handle state changes - if new_state is not None: - # remove the uppermost state - if new_state == '#pop': - stack.pop() - # resolve the new state by group checking - elif new_state == '#bygroup': - for key, value in m.groupdict().iteritems(): - if value is not None: - stack.append(key) - break - else: - raise RuntimeError('%r wanted to resolve the ' - 'new state dynamically but' - ' no group matched' % - regex) - # direct state name given - else: - stack.append(new_state) - statetokens = self.rules[stack[-1]] - # we are still at the same position and no stack change. - # this means a loop without break condition, avoid that and - # raise error - elif pos2 == pos: - raise RuntimeError('%r yielded empty string without ' - 'stack change' % regex) - # publish new function and start again - pos = pos2 - break - # if loop terminated without break we havn't found a single match - # either we are at the end of the file or we have a problem - else: - # end of text - if pos >= source_length: - return - # something went wrong - raise TemplateSyntaxError('unexpected char %r at %d' % - (source[pos], pos), lineno, - name, filename) diff --git a/module/lib/jinja2/loaders.py b/module/lib/jinja2/loaders.py deleted file mode 100644 index bd435e8b0..000000000 --- a/module/lib/jinja2/loaders.py +++ /dev/null @@ -1,449 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.loaders - ~~~~~~~~~~~~~~ - - Jinja loader classes. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import os -import sys -import weakref -from types import ModuleType -from os import path -try: - from hashlib import sha1 -except ImportError: - from sha import new as sha1 -from jinja2.exceptions import TemplateNotFound -from jinja2.utils import LRUCache, open_if_exists, internalcode - - -def split_template_path(template): - """Split a path into segments and perform a sanity check. If it detects - '..' in the path it will raise a `TemplateNotFound` error. - """ - pieces = [] - for piece in template.split('/'): - if path.sep in piece \ - or (path.altsep and path.altsep in piece) or \ - piece == path.pardir: - raise TemplateNotFound(template) - elif piece and piece != '.': - pieces.append(piece) - return pieces - - -class BaseLoader(object): - """Baseclass for all loaders. Subclass this and override `get_source` to - implement a custom loading mechanism. The environment provides a - `get_template` method that calls the loader's `load` method to get the - :class:`Template` object. - - A very basic example for a loader that looks up templates on the file - system could look like this:: - - from jinja2 import BaseLoader, TemplateNotFound - from os.path import join, exists, getmtime - - class MyLoader(BaseLoader): - - def __init__(self, path): - self.path = path - - def get_source(self, environment, template): - path = join(self.path, template) - if not exists(path): - raise TemplateNotFound(template) - mtime = getmtime(path) - with file(path) as f: - source = f.read().decode('utf-8') - return source, path, lambda: mtime == getmtime(path) - """ - - #: if set to `False` it indicates that the loader cannot provide access - #: to the source of templates. - #: - #: .. versionadded:: 2.4 - has_source_access = True - - def get_source(self, environment, template): - """Get the template source, filename and reload helper for a template. - It's passed the environment and template name and has to return a - tuple in the form ``(source, filename, uptodate)`` or raise a - `TemplateNotFound` error if it can't locate the template. - - The source part of the returned tuple must be the source of the - template as unicode string or a ASCII bytestring. The filename should - be the name of the file on the filesystem if it was loaded from there, - otherwise `None`. The filename is used by python for the tracebacks - if no loader extension is used. - - The last item in the tuple is the `uptodate` function. If auto - reloading is enabled it's always called to check if the template - changed. No arguments are passed so the function must store the - old state somewhere (for example in a closure). If it returns `False` - the template will be reloaded. - """ - if not self.has_source_access: - raise RuntimeError('%s cannot provide access to the source' % - self.__class__.__name__) - raise TemplateNotFound(template) - - def list_templates(self): - """Iterates over all templates. If the loader does not support that - it should raise a :exc:`TypeError` which is the default behavior. - """ - raise TypeError('this loader cannot iterate over all templates') - - @internalcode - def load(self, environment, name, globals=None): - """Loads a template. This method looks up the template in the cache - or loads one by calling :meth:`get_source`. Subclasses should not - override this method as loaders working on collections of other - loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) - will not call this method but `get_source` directly. - """ - code = None - if globals is None: - globals = {} - - # first we try to get the source for this template together - # with the filename and the uptodate function. - source, filename, uptodate = self.get_source(environment, name) - - # try to load the code from the bytecode cache if there is a - # bytecode cache configured. - bcc = environment.bytecode_cache - if bcc is not None: - bucket = bcc.get_bucket(environment, name, filename, source) - code = bucket.code - - # if we don't have code so far (not cached, no longer up to - # date) etc. we compile the template - if code is None: - code = environment.compile(source, name, filename) - - # if the bytecode cache is available and the bucket doesn't - # have a code so far, we give the bucket the new code and put - # it back to the bytecode cache. - if bcc is not None and bucket.code is None: - bucket.code = code - bcc.set_bucket(bucket) - - return environment.template_class.from_code(environment, code, - globals, uptodate) - - -class FileSystemLoader(BaseLoader): - """Loads templates from the file system. This loader can find templates - in folders on the file system and is the preferred way to load them. - - The loader takes the path to the templates as string, or if multiple - locations are wanted a list of them which is then looked up in the - given order: - - >>> loader = FileSystemLoader('/path/to/templates') - >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) - - Per default the template encoding is ``'utf-8'`` which can be changed - by setting the `encoding` parameter to something else. - """ - - def __init__(self, searchpath, encoding='utf-8'): - if isinstance(searchpath, basestring): - searchpath = [searchpath] - self.searchpath = list(searchpath) - self.encoding = encoding - - def get_source(self, environment, template): - pieces = split_template_path(template) - for searchpath in self.searchpath: - filename = path.join(searchpath, *pieces) - f = open_if_exists(filename) - if f is None: - continue - try: - contents = f.read().decode(self.encoding) - finally: - f.close() - - mtime = path.getmtime(filename) - def uptodate(): - try: - return path.getmtime(filename) == mtime - except OSError: - return False - return contents, filename, uptodate - raise TemplateNotFound(template) - - def list_templates(self): - found = set() - for searchpath in self.searchpath: - for dirpath, dirnames, filenames in os.walk(searchpath): - for filename in filenames: - template = os.path.join(dirpath, filename) \ - [len(searchpath):].strip(os.path.sep) \ - .replace(os.path.sep, '/') - if template[:2] == './': - template = template[2:] - if template not in found: - found.add(template) - return sorted(found) - - -class PackageLoader(BaseLoader): - """Load templates from python eggs or packages. It is constructed with - the name of the python package and the path to the templates in that - package:: - - loader = PackageLoader('mypackage', 'views') - - If the package path is not given, ``'templates'`` is assumed. - - Per default the template encoding is ``'utf-8'`` which can be changed - by setting the `encoding` parameter to something else. Due to the nature - of eggs it's only possible to reload templates if the package was loaded - from the file system and not a zip file. - """ - - def __init__(self, package_name, package_path='templates', - encoding='utf-8'): - from pkg_resources import DefaultProvider, ResourceManager, \ - get_provider - provider = get_provider(package_name) - self.encoding = encoding - self.manager = ResourceManager() - self.filesystem_bound = isinstance(provider, DefaultProvider) - self.provider = provider - self.package_path = package_path - - def get_source(self, environment, template): - pieces = split_template_path(template) - p = '/'.join((self.package_path,) + tuple(pieces)) - if not self.provider.has_resource(p): - raise TemplateNotFound(template) - - filename = uptodate = None - if self.filesystem_bound: - filename = self.provider.get_resource_filename(self.manager, p) - mtime = path.getmtime(filename) - def uptodate(): - try: - return path.getmtime(filename) == mtime - except OSError: - return False - - source = self.provider.get_resource_string(self.manager, p) - return source.decode(self.encoding), filename, uptodate - - def list_templates(self): - path = self.package_path - if path[:2] == './': - path = path[2:] - elif path == '.': - path = '' - offset = len(path) - results = [] - def _walk(path): - for filename in self.provider.resource_listdir(path): - fullname = path + '/' + filename - if self.provider.resource_isdir(fullname): - for item in _walk(fullname): - results.append(item) - else: - results.append(fullname[offset:].lstrip('/')) - _walk(path) - results.sort() - return results - - -class DictLoader(BaseLoader): - """Loads a template from a python dict. It's passed a dict of unicode - strings bound to template names. This loader is useful for unittesting: - - >>> loader = DictLoader({'index.html': 'source here'}) - - Because auto reloading is rarely useful this is disabled per default. - """ - - def __init__(self, mapping): - self.mapping = mapping - - def get_source(self, environment, template): - if template in self.mapping: - source = self.mapping[template] - return source, None, lambda: source != self.mapping.get(template) - raise TemplateNotFound(template) - - def list_templates(self): - return sorted(self.mapping) - - -class FunctionLoader(BaseLoader): - """A loader that is passed a function which does the loading. The - function becomes the name of the template passed and has to return either - an unicode string with the template source, a tuple in the form ``(source, - filename, uptodatefunc)`` or `None` if the template does not exist. - - >>> def load_template(name): - ... if name == 'index.html': - ... return '...' - ... - >>> loader = FunctionLoader(load_template) - - The `uptodatefunc` is a function that is called if autoreload is enabled - and has to return `True` if the template is still up to date. For more - details have a look at :meth:`BaseLoader.get_source` which has the same - return value. - """ - - def __init__(self, load_func): - self.load_func = load_func - - def get_source(self, environment, template): - rv = self.load_func(template) - if rv is None: - raise TemplateNotFound(template) - elif isinstance(rv, basestring): - return rv, None, None - return rv - - -class PrefixLoader(BaseLoader): - """A loader that is passed a dict of loaders where each loader is bound - to a prefix. The prefix is delimited from the template by a slash per - default, which can be changed by setting the `delimiter` argument to - something else:: - - loader = PrefixLoader({ - 'app1': PackageLoader('mypackage.app1'), - 'app2': PackageLoader('mypackage.app2') - }) - - By loading ``'app1/index.html'`` the file from the app1 package is loaded, - by loading ``'app2/index.html'`` the file from the second. - """ - - def __init__(self, mapping, delimiter='/'): - self.mapping = mapping - self.delimiter = delimiter - - def get_source(self, environment, template): - try: - prefix, name = template.split(self.delimiter, 1) - loader = self.mapping[prefix] - except (ValueError, KeyError): - raise TemplateNotFound(template) - try: - return loader.get_source(environment, name) - except TemplateNotFound: - # re-raise the exception with the correct fileame here. - # (the one that includes the prefix) - raise TemplateNotFound(template) - - def list_templates(self): - result = [] - for prefix, loader in self.mapping.iteritems(): - for template in loader.list_templates(): - result.append(prefix + self.delimiter + template) - return result - - -class ChoiceLoader(BaseLoader): - """This loader works like the `PrefixLoader` just that no prefix is - specified. If a template could not be found by one loader the next one - is tried. - - >>> loader = ChoiceLoader([ - ... FileSystemLoader('/path/to/user/templates'), - ... FileSystemLoader('/path/to/system/templates') - ... ]) - - This is useful if you want to allow users to override builtin templates - from a different location. - """ - - def __init__(self, loaders): - self.loaders = loaders - - def get_source(self, environment, template): - for loader in self.loaders: - try: - return loader.get_source(environment, template) - except TemplateNotFound: - pass - raise TemplateNotFound(template) - - def list_templates(self): - found = set() - for loader in self.loaders: - found.update(loader.list_templates()) - return sorted(found) - - -class _TemplateModule(ModuleType): - """Like a normal module but with support for weak references""" - - -class ModuleLoader(BaseLoader): - """This loader loads templates from precompiled templates. - - Example usage: - - >>> loader = ChoiceLoader([ - ... ModuleLoader('/path/to/compiled/templates'), - ... FileSystemLoader('/path/to/templates') - ... ]) - """ - - has_source_access = False - - def __init__(self, path): - package_name = '_jinja2_module_templates_%x' % id(self) - - # create a fake module that looks for the templates in the - # path given. - mod = _TemplateModule(package_name) - if isinstance(path, basestring): - path = [path] - else: - path = list(path) - mod.__path__ = path - - sys.modules[package_name] = weakref.proxy(mod, - lambda x: sys.modules.pop(package_name, None)) - - # the only strong reference, the sys.modules entry is weak - # so that the garbage collector can remove it once the - # loader that created it goes out of business. - self.module = mod - self.package_name = package_name - - @staticmethod - def get_template_key(name): - return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest() - - @staticmethod - def get_module_filename(name): - return ModuleLoader.get_template_key(name) + '.py' - - @internalcode - def load(self, environment, name, globals=None): - key = self.get_template_key(name) - module = '%s.%s' % (self.package_name, key) - mod = getattr(self.module, module, None) - if mod is None: - try: - mod = __import__(module, None, None, ['root']) - except ImportError: - raise TemplateNotFound(name) - - # remove the entry from sys.modules, we only want the attribute - # on the module object we have stored on the loader. - sys.modules.pop(module, None) - - return environment.template_class.from_module_dict( - environment, mod.__dict__, globals) diff --git a/module/lib/jinja2/meta.py b/module/lib/jinja2/meta.py deleted file mode 100644 index 3a779a5e9..000000000 --- a/module/lib/jinja2/meta.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.meta - ~~~~~~~~~~~ - - This module implements various functions that exposes information about - templates that might be interesting for various kinds of applications. - - :copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from jinja2 import nodes -from jinja2.compiler import CodeGenerator - - -class TrackingCodeGenerator(CodeGenerator): - """We abuse the code generator for introspection.""" - - def __init__(self, environment): - CodeGenerator.__init__(self, environment, '<introspection>', - '<introspection>') - self.undeclared_identifiers = set() - - def write(self, x): - """Don't write.""" - - def pull_locals(self, frame): - """Remember all undeclared identifiers.""" - self.undeclared_identifiers.update(frame.identifiers.undeclared) - - -def find_undeclared_variables(ast): - """Returns a set of all variables in the AST that will be looked up from - the context at runtime. Because at compile time it's not known which - variables will be used depending on the path the execution takes at - runtime, all variables are returned. - - >>> from jinja2 import Environment, meta - >>> env = Environment() - >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') - >>> meta.find_undeclared_variables(ast) - set(['bar']) - - .. admonition:: Implementation - - Internally the code generator is used for finding undeclared variables. - This is good to know because the code generator might raise a - :exc:`TemplateAssertionError` during compilation and as a matter of - fact this function can currently raise that exception as well. - """ - codegen = TrackingCodeGenerator(ast.environment) - codegen.visit(ast) - return codegen.undeclared_identifiers - - -def find_referenced_templates(ast): - """Finds all the referenced templates from the AST. This will return an - iterator over all the hardcoded template extensions, inclusions and - imports. If dynamic inheritance or inclusion is used, `None` will be - yielded. - - >>> from jinja2 import Environment, meta - >>> env = Environment() - >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') - >>> list(meta.find_referenced_templates(ast)) - ['layout.html', None] - - This function is useful for dependency tracking. For example if you want - to rebuild parts of the website after a layout template has changed. - """ - for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import, - nodes.Include)): - if not isinstance(node.template, nodes.Const): - # a tuple with some non consts in there - if isinstance(node.template, (nodes.Tuple, nodes.List)): - for template_name in node.template.items: - # something const, only yield the strings and ignore - # non-string consts that really just make no sense - if isinstance(template_name, nodes.Const): - if isinstance(template_name.value, basestring): - yield template_name.value - # something dynamic in there - else: - yield None - # something dynamic we don't know about here - else: - yield None - continue - # constant is a basestring, direct template name - if isinstance(node.template.value, basestring): - yield node.template.value - # a tuple or list (latter *should* not happen) made of consts, - # yield the consts that are strings. We could warn here for - # non string values - elif isinstance(node, nodes.Include) and \ - isinstance(node.template.value, (tuple, list)): - for template_name in node.template.value: - if isinstance(template_name, basestring): - yield template_name - # something else we don't care about, we could warn here - else: - yield None diff --git a/module/lib/jinja2/nodes.py b/module/lib/jinja2/nodes.py deleted file mode 100644 index 6446c70ea..000000000 --- a/module/lib/jinja2/nodes.py +++ /dev/null @@ -1,901 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.nodes - ~~~~~~~~~~~~ - - This module implements additional nodes derived from the ast base node. - - It also provides some node tree helper functions like `in_lineno` and - `get_nodes` used by the parser and translator in order to normalize - python and jinja nodes. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import operator -from itertools import chain, izip -from collections import deque -from jinja2.utils import Markup, MethodType, FunctionType - - -#: the types we support for context functions -_context_function_types = (FunctionType, MethodType) - - -_binop_to_func = { - '*': operator.mul, - '/': operator.truediv, - '//': operator.floordiv, - '**': operator.pow, - '%': operator.mod, - '+': operator.add, - '-': operator.sub -} - -_uaop_to_func = { - 'not': operator.not_, - '+': operator.pos, - '-': operator.neg -} - -_cmpop_to_func = { - 'eq': operator.eq, - 'ne': operator.ne, - 'gt': operator.gt, - 'gteq': operator.ge, - 'lt': operator.lt, - 'lteq': operator.le, - 'in': lambda a, b: a in b, - 'notin': lambda a, b: a not in b -} - - -class Impossible(Exception): - """Raised if the node could not perform a requested action.""" - - -class NodeType(type): - """A metaclass for nodes that handles the field and attribute - inheritance. fields and attributes from the parent class are - automatically forwarded to the child.""" - - def __new__(cls, name, bases, d): - for attr in 'fields', 'attributes': - storage = [] - storage.extend(getattr(bases[0], attr, ())) - storage.extend(d.get(attr, ())) - assert len(bases) == 1, 'multiple inheritance not allowed' - assert len(storage) == len(set(storage)), 'layout conflict' - d[attr] = tuple(storage) - d.setdefault('abstract', False) - return type.__new__(cls, name, bases, d) - - -class EvalContext(object): - """Holds evaluation time information. Custom attributes can be attached - to it in extensions. - """ - - def __init__(self, environment, template_name=None): - if callable(environment.autoescape): - self.autoescape = environment.autoescape(template_name) - else: - self.autoescape = environment.autoescape - self.volatile = False - - def save(self): - return self.__dict__.copy() - - def revert(self, old): - self.__dict__.clear() - self.__dict__.update(old) - - -def get_eval_context(node, ctx): - if ctx is None: - if node.environment is None: - raise RuntimeError('if no eval context is passed, the ' - 'node must have an attached ' - 'environment.') - return EvalContext(node.environment) - return ctx - - -class Node(object): - """Baseclass for all Jinja2 nodes. There are a number of nodes available - of different types. There are three major types: - - - :class:`Stmt`: statements - - :class:`Expr`: expressions - - :class:`Helper`: helper nodes - - :class:`Template`: the outermost wrapper node - - All nodes have fields and attributes. Fields may be other nodes, lists, - or arbitrary values. Fields are passed to the constructor as regular - positional arguments, attributes as keyword arguments. Each node has - two attributes: `lineno` (the line number of the node) and `environment`. - The `environment` attribute is set at the end of the parsing process for - all nodes automatically. - """ - __metaclass__ = NodeType - fields = () - attributes = ('lineno', 'environment') - abstract = True - - def __init__(self, *fields, **attributes): - if self.abstract: - raise TypeError('abstract nodes are not instanciable') - if fields: - if len(fields) != len(self.fields): - if not self.fields: - raise TypeError('%r takes 0 arguments' % - self.__class__.__name__) - raise TypeError('%r takes 0 or %d argument%s' % ( - self.__class__.__name__, - len(self.fields), - len(self.fields) != 1 and 's' or '' - )) - for name, arg in izip(self.fields, fields): - setattr(self, name, arg) - for attr in self.attributes: - setattr(self, attr, attributes.pop(attr, None)) - if attributes: - raise TypeError('unknown attribute %r' % - iter(attributes).next()) - - def iter_fields(self, exclude=None, only=None): - """This method iterates over all fields that are defined and yields - ``(key, value)`` tuples. Per default all fields are returned, but - it's possible to limit that to some fields by providing the `only` - parameter or to exclude some using the `exclude` parameter. Both - should be sets or tuples of field names. - """ - for name in self.fields: - if (exclude is only is None) or \ - (exclude is not None and name not in exclude) or \ - (only is not None and name in only): - try: - yield name, getattr(self, name) - except AttributeError: - pass - - def iter_child_nodes(self, exclude=None, only=None): - """Iterates over all direct child nodes of the node. This iterates - over all fields and yields the values of they are nodes. If the value - of a field is a list all the nodes in that list are returned. - """ - for field, item in self.iter_fields(exclude, only): - if isinstance(item, list): - for n in item: - if isinstance(n, Node): - yield n - elif isinstance(item, Node): - yield item - - def find(self, node_type): - """Find the first node of a given type. If no such node exists the - return value is `None`. - """ - for result in self.find_all(node_type): - return result - - def find_all(self, node_type): - """Find all the nodes of a given type. If the type is a tuple, - the check is performed for any of the tuple items. - """ - for child in self.iter_child_nodes(): - if isinstance(child, node_type): - yield child - for result in child.find_all(node_type): - yield result - - def set_ctx(self, ctx): - """Reset the context of a node and all child nodes. Per default the - parser will all generate nodes that have a 'load' context as it's the - most common one. This method is used in the parser to set assignment - targets and other nodes to a store context. - """ - todo = deque([self]) - while todo: - node = todo.popleft() - if 'ctx' in node.fields: - node.ctx = ctx - todo.extend(node.iter_child_nodes()) - return self - - def set_lineno(self, lineno, override=False): - """Set the line numbers of the node and children.""" - todo = deque([self]) - while todo: - node = todo.popleft() - if 'lineno' in node.attributes: - if node.lineno is None or override: - node.lineno = lineno - todo.extend(node.iter_child_nodes()) - return self - - def set_environment(self, environment): - """Set the environment for all nodes.""" - todo = deque([self]) - while todo: - node = todo.popleft() - node.environment = environment - todo.extend(node.iter_child_nodes()) - return self - - def __eq__(self, other): - return type(self) is type(other) and \ - tuple(self.iter_fields()) == tuple(other.iter_fields()) - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for - arg in self.fields) - ) - - -class Stmt(Node): - """Base node for all statements.""" - abstract = True - - -class Helper(Node): - """Nodes that exist in a specific context only.""" - abstract = True - - -class Template(Node): - """Node that represents a template. This must be the outermost node that - is passed to the compiler. - """ - fields = ('body',) - - -class Output(Stmt): - """A node that holds multiple expressions which are then printed out. - This is used both for the `print` statement and the regular template data. - """ - fields = ('nodes',) - - -class Extends(Stmt): - """Represents an extends statement.""" - fields = ('template',) - - -class For(Stmt): - """The for loop. `target` is the target for the iteration (usually a - :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list - of nodes that are used as loop-body, and `else_` a list of nodes for the - `else` block. If no else node exists it has to be an empty list. - - For filtered nodes an expression can be stored as `test`, otherwise `None`. - """ - fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive') - - -class If(Stmt): - """If `test` is true, `body` is rendered, else `else_`.""" - fields = ('test', 'body', 'else_') - - -class Macro(Stmt): - """A macro definition. `name` is the name of the macro, `args` a list of - arguments and `defaults` a list of defaults if there are any. `body` is - a list of nodes for the macro body. - """ - fields = ('name', 'args', 'defaults', 'body') - - -class CallBlock(Stmt): - """Like a macro without a name but a call instead. `call` is called with - the unnamed macro as `caller` argument this node holds. - """ - fields = ('call', 'args', 'defaults', 'body') - - -class FilterBlock(Stmt): - """Node for filter sections.""" - fields = ('body', 'filter') - - -class Block(Stmt): - """A node that represents a block.""" - fields = ('name', 'body', 'scoped') - - -class Include(Stmt): - """A node that represents the include tag.""" - fields = ('template', 'with_context', 'ignore_missing') - - -class Import(Stmt): - """A node that represents the import tag.""" - fields = ('template', 'target', 'with_context') - - -class FromImport(Stmt): - """A node that represents the from import tag. It's important to not - pass unsafe names to the name attribute. The compiler translates the - attribute lookups directly into getattr calls and does *not* use the - subscript callback of the interface. As exported variables may not - start with double underscores (which the parser asserts) this is not a - problem for regular Jinja code, but if this node is used in an extension - extra care must be taken. - - The list of names may contain tuples if aliases are wanted. - """ - fields = ('template', 'names', 'with_context') - - -class ExprStmt(Stmt): - """A statement that evaluates an expression and discards the result.""" - fields = ('node',) - - -class Assign(Stmt): - """Assigns an expression to a target.""" - fields = ('target', 'node') - - -class Expr(Node): - """Baseclass for all expressions.""" - abstract = True - - def as_const(self, eval_ctx=None): - """Return the value of the expression as constant or raise - :exc:`Impossible` if this was not possible. - - An :class:`EvalContext` can be provided, if none is given - a default context is created which requires the nodes to have - an attached environment. - - .. versionchanged:: 2.4 - the `eval_ctx` parameter was added. - """ - raise Impossible() - - def can_assign(self): - """Check if it's possible to assign something to this node.""" - return False - - -class BinExpr(Expr): - """Baseclass for all binary expressions.""" - fields = ('left', 'right') - operator = None - abstract = True - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - f = _binop_to_func[self.operator] - try: - return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx)) - except: - raise Impossible() - - -class UnaryExpr(Expr): - """Baseclass for all unary expressions.""" - fields = ('node',) - operator = None - abstract = True - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - f = _uaop_to_func[self.operator] - try: - return f(self.node.as_const(eval_ctx)) - except: - raise Impossible() - - -class Name(Expr): - """Looks up a name or stores a value in a name. - The `ctx` of the node can be one of the following values: - - - `store`: store a value in the name - - `load`: load that name - - `param`: like `store` but if the name was defined as function parameter. - """ - fields = ('name', 'ctx') - - def can_assign(self): - return self.name not in ('true', 'false', 'none', - 'True', 'False', 'None') - - -class Literal(Expr): - """Baseclass for literals.""" - abstract = True - - -class Const(Literal): - """All constant values. The parser will return this node for simple - constants such as ``42`` or ``"foo"`` but it can be used to store more - complex values such as lists too. Only constants with a safe - representation (objects where ``eval(repr(x)) == x`` is true). - """ - fields = ('value',) - - def as_const(self, eval_ctx=None): - return self.value - - @classmethod - def from_untrusted(cls, value, lineno=None, environment=None): - """Return a const object if the value is representable as - constant value in the generated code, otherwise it will raise - an `Impossible` exception. - """ - from compiler import has_safe_repr - if not has_safe_repr(value): - raise Impossible() - return cls(value, lineno=lineno, environment=environment) - - -class TemplateData(Literal): - """A constant template string.""" - fields = ('data',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() - if eval_ctx.autoescape: - return Markup(self.data) - return self.data - - -class Tuple(Literal): - """For loop unpacking and some other things like multiple arguments - for subscripts. Like for :class:`Name` `ctx` specifies if the tuple - is used for loading the names or storing. - """ - fields = ('items', 'ctx') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return tuple(x.as_const(eval_ctx) for x in self.items) - - def can_assign(self): - for item in self.items: - if not item.can_assign(): - return False - return True - - -class List(Literal): - """Any list literal such as ``[1, 2, 3]``""" - fields = ('items',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return [x.as_const(eval_ctx) for x in self.items] - - -class Dict(Literal): - """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of - :class:`Pair` nodes. - """ - fields = ('items',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return dict(x.as_const(eval_ctx) for x in self.items) - - -class Pair(Helper): - """A key, value pair for dicts.""" - fields = ('key', 'value') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx) - - -class Keyword(Helper): - """A key, value pair for keyword arguments where key is a string.""" - fields = ('key', 'value') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.key, self.value.as_const(eval_ctx) - - -class CondExpr(Expr): - """A conditional expression (inline if expression). (``{{ - foo if bar else baz }}``) - """ - fields = ('test', 'expr1', 'expr2') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if self.test.as_const(eval_ctx): - return self.expr1.as_const(eval_ctx) - - # if we evaluate to an undefined object, we better do that at runtime - if self.expr2 is None: - raise Impossible() - - return self.expr2.as_const(eval_ctx) - - -class Filter(Expr): - """This node applies a filter on an expression. `name` is the name of - the filter, the rest of the fields are the same as for :class:`Call`. - - If the `node` of a filter is `None` the contents of the last buffer are - filtered. Buffers are created by macros and filter blocks. - """ - fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile or self.node is None: - raise Impossible() - # we have to be careful here because we call filter_ below. - # if this variable would be called filter, 2to3 would wrap the - # call in a list beause it is assuming we are talking about the - # builtin filter function here which no longer returns a list in - # python 3. because of that, do not rename filter_ to filter! - filter_ = self.environment.filters.get(self.name) - if filter_ is None or getattr(filter_, 'contextfilter', False): - raise Impossible() - obj = self.node.as_const(eval_ctx) - args = [x.as_const(eval_ctx) for x in self.args] - if getattr(filter_, 'evalcontextfilter', False): - args.insert(0, eval_ctx) - elif getattr(filter_, 'environmentfilter', False): - args.insert(0, self.environment) - kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs) - if self.dyn_args is not None: - try: - args.extend(self.dyn_args.as_const(eval_ctx)) - except: - raise Impossible() - if self.dyn_kwargs is not None: - try: - kwargs.update(self.dyn_kwargs.as_const(eval_ctx)) - except: - raise Impossible() - try: - return filter_(obj, *args, **kwargs) - except: - raise Impossible() - - -class Test(Expr): - """Applies a test on an expression. `name` is the name of the test, the - rest of the fields are the same as for :class:`Call`. - """ - fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - -class Call(Expr): - """Calls an expression. `args` is a list of arguments, `kwargs` a list - of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args` - and `dyn_kwargs` has to be either `None` or a node that is used as - node for dynamic positional (``*args``) or keyword (``**kwargs``) - arguments. - """ - fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() - obj = self.node.as_const(eval_ctx) - - # don't evaluate context functions - args = [x.as_const(eval_ctx) for x in self.args] - if isinstance(obj, _context_function_types): - if getattr(obj, 'contextfunction', False): - raise Impossible() - elif getattr(obj, 'evalcontextfunction', False): - args.insert(0, eval_ctx) - elif getattr(obj, 'environmentfunction', False): - args.insert(0, self.environment) - - kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs) - if self.dyn_args is not None: - try: - args.extend(self.dyn_args.as_const(eval_ctx)) - except: - raise Impossible() - if self.dyn_kwargs is not None: - try: - kwargs.update(self.dyn_kwargs.as_const(eval_ctx)) - except: - raise Impossible() - try: - return obj(*args, **kwargs) - except: - raise Impossible() - - -class Getitem(Expr): - """Get an attribute or item from an expression and prefer the item.""" - fields = ('node', 'arg', 'ctx') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if self.ctx != 'load': - raise Impossible() - try: - return self.environment.getitem(self.node.as_const(eval_ctx), - self.arg.as_const(eval_ctx)) - except: - raise Impossible() - - def can_assign(self): - return False - - -class Getattr(Expr): - """Get an attribute or item from an expression that is a ascii-only - bytestring and prefer the attribute. - """ - fields = ('node', 'attr', 'ctx') - - def as_const(self, eval_ctx=None): - if self.ctx != 'load': - raise Impossible() - try: - eval_ctx = get_eval_context(self, eval_ctx) - return self.environment.getattr(self.node.as_const(eval_ctx), - self.attr) - except: - raise Impossible() - - def can_assign(self): - return False - - -class Slice(Expr): - """Represents a slice object. This must only be used as argument for - :class:`Subscript`. - """ - fields = ('start', 'stop', 'step') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - def const(obj): - if obj is None: - return None - return obj.as_const(eval_ctx) - return slice(const(self.start), const(self.stop), const(self.step)) - - -class Concat(Expr): - """Concatenates the list of expressions provided after converting them to - unicode. - """ - fields = ('nodes',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes) - - -class Compare(Expr): - """Compares an expression with some other expressions. `ops` must be a - list of :class:`Operand`\s. - """ - fields = ('expr', 'ops') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - result = value = self.expr.as_const(eval_ctx) - try: - for op in self.ops: - new_value = op.expr.as_const(eval_ctx) - result = _cmpop_to_func[op.op](value, new_value) - value = new_value - except: - raise Impossible() - return result - - -class Operand(Helper): - """Holds an operator and an expression.""" - fields = ('op', 'expr') - -if __debug__: - Operand.__doc__ += '\nThe following operators are available: ' + \ - ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) | - set(_uaop_to_func) | set(_cmpop_to_func))) - - -class Mul(BinExpr): - """Multiplies the left with the right node.""" - operator = '*' - - -class Div(BinExpr): - """Divides the left by the right node.""" - operator = '/' - - -class FloorDiv(BinExpr): - """Divides the left by the right node and truncates conver the - result into an integer by truncating. - """ - operator = '//' - - -class Add(BinExpr): - """Add the left to the right node.""" - operator = '+' - - -class Sub(BinExpr): - """Substract the right from the left node.""" - operator = '-' - - -class Mod(BinExpr): - """Left modulo right.""" - operator = '%' - - -class Pow(BinExpr): - """Left to the power of right.""" - operator = '**' - - -class And(BinExpr): - """Short circuited AND.""" - operator = 'and' - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx) - - -class Or(BinExpr): - """Short circuited OR.""" - operator = 'or' - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx) - - -class Not(UnaryExpr): - """Negate the expression.""" - operator = 'not' - - -class Neg(UnaryExpr): - """Make the expression negative.""" - operator = '-' - - -class Pos(UnaryExpr): - """Make the expression positive (noop for most expressions)""" - operator = '+' - - -# Helpers for extensions - - -class EnvironmentAttribute(Expr): - """Loads an attribute from the environment object. This is useful for - extensions that want to call a callback stored on the environment. - """ - fields = ('name',) - - -class ExtensionAttribute(Expr): - """Returns the attribute of an extension bound to the environment. - The identifier is the identifier of the :class:`Extension`. - - This node is usually constructed by calling the - :meth:`~jinja2.ext.Extension.attr` method on an extension. - """ - fields = ('identifier', 'name') - - -class ImportedName(Expr): - """If created with an import name the import name is returned on node - access. For example ``ImportedName('cgi.escape')`` returns the `escape` - function from the cgi module on evaluation. Imports are optimized by the - compiler so there is no need to assign them to local variables. - """ - fields = ('importname',) - - -class InternalName(Expr): - """An internal name in the compiler. You cannot create these nodes - yourself but the parser provides a - :meth:`~jinja2.parser.Parser.free_identifier` method that creates - a new identifier for you. This identifier is not available from the - template and is not threated specially by the compiler. - """ - fields = ('name',) - - def __init__(self): - raise TypeError('Can\'t create internal names. Use the ' - '`free_identifier` method on a parser.') - - -class MarkSafe(Expr): - """Mark the wrapped expression as safe (wrap it as `Markup`).""" - fields = ('expr',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return Markup(self.expr.as_const(eval_ctx)) - - -class MarkSafeIfAutoescape(Expr): - """Mark the wrapped expression as safe (wrap it as `Markup`) but - only if autoescaping is active. - - .. versionadded:: 2.5 - """ - fields = ('expr',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() - expr = self.expr.as_const(eval_ctx) - if eval_ctx.autoescape: - return Markup(expr) - return expr - - -class ContextReference(Expr): - """Returns the current template context. It can be used like a - :class:`Name` node, with a ``'load'`` ctx and will return the - current :class:`~jinja2.runtime.Context` object. - - Here an example that assigns the current template name to a - variable named `foo`:: - - Assign(Name('foo', ctx='store'), - Getattr(ContextReference(), 'name')) - """ - - -class Continue(Stmt): - """Continue a loop.""" - - -class Break(Stmt): - """Break a loop.""" - - -class Scope(Stmt): - """An artificial scope.""" - fields = ('body',) - - -class EvalContextModifier(Stmt): - """Modifies the eval context. For each option that should be modified, - a :class:`Keyword` has to be added to the :attr:`options` list. - - Example to change the `autoescape` setting:: - - EvalContextModifier(options=[Keyword('autoescape', Const(True))]) - """ - fields = ('options',) - - -class ScopedEvalContextModifier(EvalContextModifier): - """Modifies the eval context and reverts it later. Works exactly like - :class:`EvalContextModifier` but will only modify the - :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`. - """ - fields = ('body',) - - -# make sure nobody creates custom nodes -def _failing_new(*args, **kwargs): - raise TypeError('can\'t create custom node types') -NodeType.__new__ = staticmethod(_failing_new); del _failing_new diff --git a/module/lib/jinja2/optimizer.py b/module/lib/jinja2/optimizer.py deleted file mode 100644 index 00eab115e..000000000 --- a/module/lib/jinja2/optimizer.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.optimizer - ~~~~~~~~~~~~~~~~ - - The jinja optimizer is currently trying to constant fold a few expressions - and modify the AST in place so that it should be easier to evaluate it. - - Because the AST does not contain all the scoping information and the - compiler has to find that out, we cannot do all the optimizations we - want. For example loop unrolling doesn't work because unrolled loops would - have a different scoping. - - The solution would be a second syntax tree that has the scoping rules stored. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -from jinja2 import nodes -from jinja2.visitor import NodeTransformer - - -def optimize(node, environment): - """The context hint can be used to perform an static optimization - based on the context given.""" - optimizer = Optimizer(environment) - return optimizer.visit(node) - - -class Optimizer(NodeTransformer): - - def __init__(self, environment): - self.environment = environment - - def visit_If(self, node): - """Eliminate dead code.""" - # do not optimize ifs that have a block inside so that it doesn't - # break super(). - if node.find(nodes.Block) is not None: - return self.generic_visit(node) - try: - val = self.visit(node.test).as_const() - except nodes.Impossible: - return self.generic_visit(node) - if val: - body = node.body - else: - body = node.else_ - result = [] - for node in body: - result.extend(self.visit_list(node)) - return result - - def fold(self, node): - """Do constant folding.""" - node = self.generic_visit(node) - try: - return nodes.Const.from_untrusted(node.as_const(), - lineno=node.lineno, - environment=self.environment) - except nodes.Impossible: - return node - - visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ - visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ - visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \ - visit_Filter = visit_Test = visit_CondExpr = fold - del fold diff --git a/module/lib/jinja2/parser.py b/module/lib/jinja2/parser.py deleted file mode 100644 index d44229ad0..000000000 --- a/module/lib/jinja2/parser.py +++ /dev/null @@ -1,896 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.parser - ~~~~~~~~~~~~~ - - Implements the template parser. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from jinja2 import nodes -from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError -from jinja2.utils import next -from jinja2.lexer import describe_token, describe_token_expr - - -#: statements that callinto -_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', - 'macro', 'include', 'from', 'import', - 'set']) -_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) - - -class Parser(object): - """This is the central parsing class Jinja2 uses. It's passed to - extensions and can be used to parse expressions or statements. - """ - - def __init__(self, environment, source, name=None, filename=None, - state=None): - self.environment = environment - self.stream = environment._tokenize(source, name, filename, state) - self.name = name - self.filename = filename - self.closed = False - self.extensions = {} - for extension in environment.iter_extensions(): - for tag in extension.tags: - self.extensions[tag] = extension.parse - self._last_identifier = 0 - self._tag_stack = [] - self._end_token_stack = [] - - def fail(self, msg, lineno=None, exc=TemplateSyntaxError): - """Convenience method that raises `exc` with the message, passed - line number or last line number as well as the current name and - filename. - """ - if lineno is None: - lineno = self.stream.current.lineno - raise exc(msg, lineno, self.name, self.filename) - - def _fail_ut_eof(self, name, end_token_stack, lineno): - expected = [] - for exprs in end_token_stack: - expected.extend(map(describe_token_expr, exprs)) - if end_token_stack: - currently_looking = ' or '.join( - "'%s'" % describe_token_expr(expr) - for expr in end_token_stack[-1]) - else: - currently_looking = None - - if name is None: - message = ['Unexpected end of template.'] - else: - message = ['Encountered unknown tag \'%s\'.' % name] - - if currently_looking: - if name is not None and name in expected: - message.append('You probably made a nesting mistake. Jinja ' - 'is expecting this tag, but currently looking ' - 'for %s.' % currently_looking) - else: - message.append('Jinja was looking for the following tags: ' - '%s.' % currently_looking) - - if self._tag_stack: - message.append('The innermost block that needs to be ' - 'closed is \'%s\'.' % self._tag_stack[-1]) - - self.fail(' '.join(message), lineno) - - def fail_unknown_tag(self, name, lineno=None): - """Called if the parser encounters an unknown tag. Tries to fail - with a human readable error message that could help to identify - the problem. - """ - return self._fail_ut_eof(name, self._end_token_stack, lineno) - - def fail_eof(self, end_tokens=None, lineno=None): - """Like fail_unknown_tag but for end of template situations.""" - stack = list(self._end_token_stack) - if end_tokens is not None: - stack.append(end_tokens) - return self._fail_ut_eof(None, stack, lineno) - - def is_tuple_end(self, extra_end_rules=None): - """Are we at the end of a tuple?""" - if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): - return True - elif extra_end_rules is not None: - return self.stream.current.test_any(extra_end_rules) - return False - - def free_identifier(self, lineno=None): - """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" - self._last_identifier += 1 - rv = object.__new__(nodes.InternalName) - nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) - return rv - - def parse_statement(self): - """Parse a single statement.""" - token = self.stream.current - if token.type != 'name': - self.fail('tag name expected', token.lineno) - self._tag_stack.append(token.value) - pop_tag = True - try: - if token.value in _statement_keywords: - return getattr(self, 'parse_' + self.stream.current.value)() - if token.value == 'call': - return self.parse_call_block() - if token.value == 'filter': - return self.parse_filter_block() - ext = self.extensions.get(token.value) - if ext is not None: - return ext(self) - - # did not work out, remove the token we pushed by accident - # from the stack so that the unknown tag fail function can - # produce a proper error message. - self._tag_stack.pop() - pop_tag = False - self.fail_unknown_tag(token.value, token.lineno) - finally: - if pop_tag: - self._tag_stack.pop() - - def parse_statements(self, end_tokens, drop_needle=False): - """Parse multiple statements into a list until one of the end tokens - is reached. This is used to parse the body of statements as it also - parses template data if appropriate. The parser checks first if the - current token is a colon and skips it if there is one. Then it checks - for the block end and parses until if one of the `end_tokens` is - reached. Per default the active token in the stream at the end of - the call is the matched end token. If this is not wanted `drop_needle` - can be set to `True` and the end token is removed. - """ - # the first token may be a colon for python compatibility - self.stream.skip_if('colon') - - # in the future it would be possible to add whole code sections - # by adding some sort of end of statement token and parsing those here. - self.stream.expect('block_end') - result = self.subparse(end_tokens) - - # we reached the end of the template too early, the subparser - # does not check for this, so we do that now - if self.stream.current.type == 'eof': - self.fail_eof(end_tokens) - - if drop_needle: - next(self.stream) - return result - - def parse_set(self): - """Parse an assign statement.""" - lineno = next(self.stream).lineno - target = self.parse_assign_target() - self.stream.expect('assign') - expr = self.parse_tuple() - return nodes.Assign(target, expr, lineno=lineno) - - def parse_for(self): - """Parse a for loop.""" - lineno = self.stream.expect('name:for').lineno - target = self.parse_assign_target(extra_end_rules=('name:in',)) - self.stream.expect('name:in') - iter = self.parse_tuple(with_condexpr=False, - extra_end_rules=('name:recursive',)) - test = None - if self.stream.skip_if('name:if'): - test = self.parse_expression() - recursive = self.stream.skip_if('name:recursive') - body = self.parse_statements(('name:endfor', 'name:else')) - if next(self.stream).value == 'endfor': - else_ = [] - else: - else_ = self.parse_statements(('name:endfor',), drop_needle=True) - return nodes.For(target, iter, body, else_, test, - recursive, lineno=lineno) - - def parse_if(self): - """Parse an if construct.""" - node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) - while 1: - node.test = self.parse_tuple(with_condexpr=False) - node.body = self.parse_statements(('name:elif', 'name:else', - 'name:endif')) - token = next(self.stream) - if token.test('name:elif'): - new_node = nodes.If(lineno=self.stream.current.lineno) - node.else_ = [new_node] - node = new_node - continue - elif token.test('name:else'): - node.else_ = self.parse_statements(('name:endif',), - drop_needle=True) - else: - node.else_ = [] - break - return result - - def parse_block(self): - node = nodes.Block(lineno=next(self.stream).lineno) - node.name = self.stream.expect('name').value - node.scoped = self.stream.skip_if('name:scoped') - - # common problem people encounter when switching from django - # to jinja. we do not support hyphens in block names, so let's - # raise a nicer error message in that case. - if self.stream.current.type == 'sub': - self.fail('Block names in Jinja have to be valid Python ' - 'identifiers and may not contain hypens, use an ' - 'underscore instead.') - - node.body = self.parse_statements(('name:endblock',), drop_needle=True) - self.stream.skip_if('name:' + node.name) - return node - - def parse_extends(self): - node = nodes.Extends(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - return node - - def parse_import_context(self, node, default): - if self.stream.current.test_any('name:with', 'name:without') and \ - self.stream.look().test('name:context'): - node.with_context = next(self.stream).value == 'with' - self.stream.skip() - else: - node.with_context = default - return node - - def parse_include(self): - node = nodes.Include(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - if self.stream.current.test('name:ignore') and \ - self.stream.look().test('name:missing'): - node.ignore_missing = True - self.stream.skip(2) - else: - node.ignore_missing = False - return self.parse_import_context(node, True) - - def parse_import(self): - node = nodes.Import(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - self.stream.expect('name:as') - node.target = self.parse_assign_target(name_only=True).name - return self.parse_import_context(node, False) - - def parse_from(self): - node = nodes.FromImport(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - self.stream.expect('name:import') - node.names = [] - - def parse_context(): - if self.stream.current.value in ('with', 'without') and \ - self.stream.look().test('name:context'): - node.with_context = next(self.stream).value == 'with' - self.stream.skip() - return True - return False - - while 1: - if node.names: - self.stream.expect('comma') - if self.stream.current.type == 'name': - if parse_context(): - break - target = self.parse_assign_target(name_only=True) - if target.name.startswith('_'): - self.fail('names starting with an underline can not ' - 'be imported', target.lineno, - exc=TemplateAssertionError) - if self.stream.skip_if('name:as'): - alias = self.parse_assign_target(name_only=True) - node.names.append((target.name, alias.name)) - else: - node.names.append(target.name) - if parse_context() or self.stream.current.type != 'comma': - break - else: - break - if not hasattr(node, 'with_context'): - node.with_context = False - self.stream.skip_if('comma') - return node - - def parse_signature(self, node): - node.args = args = [] - node.defaults = defaults = [] - self.stream.expect('lparen') - while self.stream.current.type != 'rparen': - if args: - self.stream.expect('comma') - arg = self.parse_assign_target(name_only=True) - arg.set_ctx('param') - if self.stream.skip_if('assign'): - defaults.append(self.parse_expression()) - args.append(arg) - self.stream.expect('rparen') - - def parse_call_block(self): - node = nodes.CallBlock(lineno=next(self.stream).lineno) - if self.stream.current.type == 'lparen': - self.parse_signature(node) - else: - node.args = [] - node.defaults = [] - - node.call = self.parse_expression() - if not isinstance(node.call, nodes.Call): - self.fail('expected call', node.lineno) - node.body = self.parse_statements(('name:endcall',), drop_needle=True) - return node - - def parse_filter_block(self): - node = nodes.FilterBlock(lineno=next(self.stream).lineno) - node.filter = self.parse_filter(None, start_inline=True) - node.body = self.parse_statements(('name:endfilter',), - drop_needle=True) - return node - - def parse_macro(self): - node = nodes.Macro(lineno=next(self.stream).lineno) - node.name = self.parse_assign_target(name_only=True).name - self.parse_signature(node) - node.body = self.parse_statements(('name:endmacro',), - drop_needle=True) - return node - - def parse_print(self): - node = nodes.Output(lineno=next(self.stream).lineno) - node.nodes = [] - while self.stream.current.type != 'block_end': - if node.nodes: - self.stream.expect('comma') - node.nodes.append(self.parse_expression()) - return node - - def parse_assign_target(self, with_tuple=True, name_only=False, - extra_end_rules=None): - """Parse an assignment target. As Jinja2 allows assignments to - tuples, this function can parse all allowed assignment targets. Per - default assignments to tuples are parsed, that can be disable however - by setting `with_tuple` to `False`. If only assignments to names are - wanted `name_only` can be set to `True`. The `extra_end_rules` - parameter is forwarded to the tuple parsing function. - """ - if name_only: - token = self.stream.expect('name') - target = nodes.Name(token.value, 'store', lineno=token.lineno) - else: - if with_tuple: - target = self.parse_tuple(simplified=True, - extra_end_rules=extra_end_rules) - else: - target = self.parse_primary() - target.set_ctx('store') - if not target.can_assign(): - self.fail('can\'t assign to %r' % target.__class__. - __name__.lower(), target.lineno) - return target - - def parse_expression(self, with_condexpr=True): - """Parse an expression. Per default all expressions are parsed, if - the optional `with_condexpr` parameter is set to `False` conditional - expressions are not parsed. - """ - if with_condexpr: - return self.parse_condexpr() - return self.parse_or() - - def parse_condexpr(self): - lineno = self.stream.current.lineno - expr1 = self.parse_or() - while self.stream.skip_if('name:if'): - expr2 = self.parse_or() - if self.stream.skip_if('name:else'): - expr3 = self.parse_condexpr() - else: - expr3 = None - expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) - lineno = self.stream.current.lineno - return expr1 - - def parse_or(self): - lineno = self.stream.current.lineno - left = self.parse_and() - while self.stream.skip_if('name:or'): - right = self.parse_and() - left = nodes.Or(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_and(self): - lineno = self.stream.current.lineno - left = self.parse_not() - while self.stream.skip_if('name:and'): - right = self.parse_not() - left = nodes.And(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_not(self): - if self.stream.current.test('name:not'): - lineno = next(self.stream).lineno - return nodes.Not(self.parse_not(), lineno=lineno) - return self.parse_compare() - - def parse_compare(self): - lineno = self.stream.current.lineno - expr = self.parse_add() - ops = [] - while 1: - token_type = self.stream.current.type - if token_type in _compare_operators: - next(self.stream) - ops.append(nodes.Operand(token_type, self.parse_add())) - elif self.stream.skip_if('name:in'): - ops.append(nodes.Operand('in', self.parse_add())) - elif self.stream.current.test('name:not') and \ - self.stream.look().test('name:in'): - self.stream.skip(2) - ops.append(nodes.Operand('notin', self.parse_add())) - else: - break - lineno = self.stream.current.lineno - if not ops: - return expr - return nodes.Compare(expr, ops, lineno=lineno) - - def parse_add(self): - lineno = self.stream.current.lineno - left = self.parse_sub() - while self.stream.current.type == 'add': - next(self.stream) - right = self.parse_sub() - left = nodes.Add(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_sub(self): - lineno = self.stream.current.lineno - left = self.parse_concat() - while self.stream.current.type == 'sub': - next(self.stream) - right = self.parse_concat() - left = nodes.Sub(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_concat(self): - lineno = self.stream.current.lineno - args = [self.parse_mul()] - while self.stream.current.type == 'tilde': - next(self.stream) - args.append(self.parse_mul()) - if len(args) == 1: - return args[0] - return nodes.Concat(args, lineno=lineno) - - def parse_mul(self): - lineno = self.stream.current.lineno - left = self.parse_div() - while self.stream.current.type == 'mul': - next(self.stream) - right = self.parse_div() - left = nodes.Mul(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_div(self): - lineno = self.stream.current.lineno - left = self.parse_floordiv() - while self.stream.current.type == 'div': - next(self.stream) - right = self.parse_floordiv() - left = nodes.Div(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_floordiv(self): - lineno = self.stream.current.lineno - left = self.parse_mod() - while self.stream.current.type == 'floordiv': - next(self.stream) - right = self.parse_mod() - left = nodes.FloorDiv(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_mod(self): - lineno = self.stream.current.lineno - left = self.parse_pow() - while self.stream.current.type == 'mod': - next(self.stream) - right = self.parse_pow() - left = nodes.Mod(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_pow(self): - lineno = self.stream.current.lineno - left = self.parse_unary() - while self.stream.current.type == 'pow': - next(self.stream) - right = self.parse_unary() - left = nodes.Pow(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_unary(self, with_filter=True): - token_type = self.stream.current.type - lineno = self.stream.current.lineno - if token_type == 'sub': - next(self.stream) - node = nodes.Neg(self.parse_unary(False), lineno=lineno) - elif token_type == 'add': - next(self.stream) - node = nodes.Pos(self.parse_unary(False), lineno=lineno) - else: - node = self.parse_primary() - node = self.parse_postfix(node) - if with_filter: - node = self.parse_filter_expr(node) - return node - - def parse_primary(self): - token = self.stream.current - if token.type == 'name': - if token.value in ('true', 'false', 'True', 'False'): - node = nodes.Const(token.value in ('true', 'True'), - lineno=token.lineno) - elif token.value in ('none', 'None'): - node = nodes.Const(None, lineno=token.lineno) - else: - node = nodes.Name(token.value, 'load', lineno=token.lineno) - next(self.stream) - elif token.type == 'string': - next(self.stream) - buf = [token.value] - lineno = token.lineno - while self.stream.current.type == 'string': - buf.append(self.stream.current.value) - next(self.stream) - node = nodes.Const(''.join(buf), lineno=lineno) - elif token.type in ('integer', 'float'): - next(self.stream) - node = nodes.Const(token.value, lineno=token.lineno) - elif token.type == 'lparen': - next(self.stream) - node = self.parse_tuple(explicit_parentheses=True) - self.stream.expect('rparen') - elif token.type == 'lbracket': - node = self.parse_list() - elif token.type == 'lbrace': - node = self.parse_dict() - else: - self.fail("unexpected '%s'" % describe_token(token), token.lineno) - return node - - def parse_tuple(self, simplified=False, with_condexpr=True, - extra_end_rules=None, explicit_parentheses=False): - """Works like `parse_expression` but if multiple expressions are - delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. - This method could also return a regular expression instead of a tuple - if no commas where found. - - The default parsing mode is a full tuple. If `simplified` is `True` - only names and literals are parsed. The `no_condexpr` parameter is - forwarded to :meth:`parse_expression`. - - Because tuples do not require delimiters and may end in a bogus comma - an extra hint is needed that marks the end of a tuple. For example - for loops support tuples between `for` and `in`. In that case the - `extra_end_rules` is set to ``['name:in']``. - - `explicit_parentheses` is true if the parsing was triggered by an - expression in parentheses. This is used to figure out if an empty - tuple is a valid expression or not. - """ - lineno = self.stream.current.lineno - if simplified: - parse = self.parse_primary - elif with_condexpr: - parse = self.parse_expression - else: - parse = lambda: self.parse_expression(with_condexpr=False) - args = [] - is_tuple = False - while 1: - if args: - self.stream.expect('comma') - if self.is_tuple_end(extra_end_rules): - break - args.append(parse()) - if self.stream.current.type == 'comma': - is_tuple = True - else: - break - lineno = self.stream.current.lineno - - if not is_tuple: - if args: - return args[0] - - # if we don't have explicit parentheses, an empty tuple is - # not a valid expression. This would mean nothing (literally - # nothing) in the spot of an expression would be an empty - # tuple. - if not explicit_parentheses: - self.fail('Expected an expression, got \'%s\'' % - describe_token(self.stream.current)) - - return nodes.Tuple(args, 'load', lineno=lineno) - - def parse_list(self): - token = self.stream.expect('lbracket') - items = [] - while self.stream.current.type != 'rbracket': - if items: - self.stream.expect('comma') - if self.stream.current.type == 'rbracket': - break - items.append(self.parse_expression()) - self.stream.expect('rbracket') - return nodes.List(items, lineno=token.lineno) - - def parse_dict(self): - token = self.stream.expect('lbrace') - items = [] - while self.stream.current.type != 'rbrace': - if items: - self.stream.expect('comma') - if self.stream.current.type == 'rbrace': - break - key = self.parse_expression() - self.stream.expect('colon') - value = self.parse_expression() - items.append(nodes.Pair(key, value, lineno=key.lineno)) - self.stream.expect('rbrace') - return nodes.Dict(items, lineno=token.lineno) - - def parse_postfix(self, node): - while 1: - token_type = self.stream.current.type - if token_type == 'dot' or token_type == 'lbracket': - node = self.parse_subscript(node) - # calls are valid both after postfix expressions (getattr - # and getitem) as well as filters and tests - elif token_type == 'lparen': - node = self.parse_call(node) - else: - break - return node - - def parse_filter_expr(self, node): - while 1: - token_type = self.stream.current.type - if token_type == 'pipe': - node = self.parse_filter(node) - elif token_type == 'name' and self.stream.current.value == 'is': - node = self.parse_test(node) - # calls are valid both after postfix expressions (getattr - # and getitem) as well as filters and tests - elif token_type == 'lparen': - node = self.parse_call(node) - else: - break - return node - - def parse_subscript(self, node): - token = next(self.stream) - if token.type == 'dot': - attr_token = self.stream.current - next(self.stream) - if attr_token.type == 'name': - return nodes.Getattr(node, attr_token.value, 'load', - lineno=token.lineno) - elif attr_token.type != 'integer': - self.fail('expected name or number', attr_token.lineno) - arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) - return nodes.Getitem(node, arg, 'load', lineno=token.lineno) - if token.type == 'lbracket': - priority_on_attribute = False - args = [] - while self.stream.current.type != 'rbracket': - if args: - self.stream.expect('comma') - args.append(self.parse_subscribed()) - self.stream.expect('rbracket') - if len(args) == 1: - arg = args[0] - else: - arg = nodes.Tuple(args, 'load', lineno=token.lineno) - return nodes.Getitem(node, arg, 'load', lineno=token.lineno) - self.fail('expected subscript expression', self.lineno) - - def parse_subscribed(self): - lineno = self.stream.current.lineno - - if self.stream.current.type == 'colon': - next(self.stream) - args = [None] - else: - node = self.parse_expression() - if self.stream.current.type != 'colon': - return node - next(self.stream) - args = [node] - - if self.stream.current.type == 'colon': - args.append(None) - elif self.stream.current.type not in ('rbracket', 'comma'): - args.append(self.parse_expression()) - else: - args.append(None) - - if self.stream.current.type == 'colon': - next(self.stream) - if self.stream.current.type not in ('rbracket', 'comma'): - args.append(self.parse_expression()) - else: - args.append(None) - else: - args.append(None) - - return nodes.Slice(lineno=lineno, *args) - - def parse_call(self, node): - token = self.stream.expect('lparen') - args = [] - kwargs = [] - dyn_args = dyn_kwargs = None - require_comma = False - - def ensure(expr): - if not expr: - self.fail('invalid syntax for function call expression', - token.lineno) - - while self.stream.current.type != 'rparen': - if require_comma: - self.stream.expect('comma') - # support for trailing comma - if self.stream.current.type == 'rparen': - break - if self.stream.current.type == 'mul': - ensure(dyn_args is None and dyn_kwargs is None) - next(self.stream) - dyn_args = self.parse_expression() - elif self.stream.current.type == 'pow': - ensure(dyn_kwargs is None) - next(self.stream) - dyn_kwargs = self.parse_expression() - else: - ensure(dyn_args is None and dyn_kwargs is None) - if self.stream.current.type == 'name' and \ - self.stream.look().type == 'assign': - key = self.stream.current.value - self.stream.skip(2) - value = self.parse_expression() - kwargs.append(nodes.Keyword(key, value, - lineno=value.lineno)) - else: - ensure(not kwargs) - args.append(self.parse_expression()) - - require_comma = True - self.stream.expect('rparen') - - if node is None: - return args, kwargs, dyn_args, dyn_kwargs - return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, - lineno=token.lineno) - - def parse_filter(self, node, start_inline=False): - while self.stream.current.type == 'pipe' or start_inline: - if not start_inline: - next(self.stream) - token = self.stream.expect('name') - name = token.value - while self.stream.current.type == 'dot': - next(self.stream) - name += '.' + self.stream.expect('name').value - if self.stream.current.type == 'lparen': - args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) - else: - args = [] - kwargs = [] - dyn_args = dyn_kwargs = None - node = nodes.Filter(node, name, args, kwargs, dyn_args, - dyn_kwargs, lineno=token.lineno) - start_inline = False - return node - - def parse_test(self, node): - token = next(self.stream) - if self.stream.current.test('name:not'): - next(self.stream) - negated = True - else: - negated = False - name = self.stream.expect('name').value - while self.stream.current.type == 'dot': - next(self.stream) - name += '.' + self.stream.expect('name').value - dyn_args = dyn_kwargs = None - kwargs = [] - if self.stream.current.type == 'lparen': - args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) - elif self.stream.current.type in ('name', 'string', 'integer', - 'float', 'lparen', 'lbracket', - 'lbrace') and not \ - self.stream.current.test_any('name:else', 'name:or', - 'name:and'): - if self.stream.current.test('name:is'): - self.fail('You cannot chain multiple tests with is') - args = [self.parse_expression()] - else: - args = [] - node = nodes.Test(node, name, args, kwargs, dyn_args, - dyn_kwargs, lineno=token.lineno) - if negated: - node = nodes.Not(node, lineno=token.lineno) - return node - - def subparse(self, end_tokens=None): - body = [] - data_buffer = [] - add_data = data_buffer.append - - if end_tokens is not None: - self._end_token_stack.append(end_tokens) - - def flush_data(): - if data_buffer: - lineno = data_buffer[0].lineno - body.append(nodes.Output(data_buffer[:], lineno=lineno)) - del data_buffer[:] - - try: - while self.stream: - token = self.stream.current - if token.type == 'data': - if token.value: - add_data(nodes.TemplateData(token.value, - lineno=token.lineno)) - next(self.stream) - elif token.type == 'variable_begin': - next(self.stream) - add_data(self.parse_tuple(with_condexpr=True)) - self.stream.expect('variable_end') - elif token.type == 'block_begin': - flush_data() - next(self.stream) - if end_tokens is not None and \ - self.stream.current.test_any(*end_tokens): - return body - rv = self.parse_statement() - if isinstance(rv, list): - body.extend(rv) - else: - body.append(rv) - self.stream.expect('block_end') - else: - raise AssertionError('internal parsing error') - - flush_data() - finally: - if end_tokens is not None: - self._end_token_stack.pop() - - return body - - def parse(self): - """Parse the whole template into a `Template` node.""" - result = nodes.Template(self.subparse(), lineno=1) - result.set_environment(self.environment) - return result diff --git a/module/lib/jinja2/runtime.py b/module/lib/jinja2/runtime.py deleted file mode 100644 index 6fea3aa4f..000000000 --- a/module/lib/jinja2/runtime.py +++ /dev/null @@ -1,544 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.runtime - ~~~~~~~~~~~~~~ - - Runtime helpers. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -import sys -from itertools import chain, imap -from jinja2.nodes import EvalContext, _context_function_types -from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \ - concat, internalcode, next, object_type_repr -from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \ - TemplateNotFound - - -# these variables are exported to the template runtime -__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', - 'TemplateRuntimeError', 'missing', 'concat', 'escape', - 'markup_join', 'unicode_join', 'to_string', 'identity', - 'TemplateNotFound'] - -#: the name of the function that is used to convert something into -#: a string. 2to3 will adopt that automatically and the generated -#: code can take advantage of it. -to_string = unicode - -#: the identity function. Useful for certain things in the environment -identity = lambda x: x - - -def markup_join(seq): - """Concatenation that escapes if necessary and converts to unicode.""" - buf = [] - iterator = imap(soft_unicode, seq) - for arg in iterator: - buf.append(arg) - if hasattr(arg, '__html__'): - return Markup(u'').join(chain(buf, iterator)) - return concat(buf) - - -def unicode_join(seq): - """Simple args to unicode conversion and concatenation.""" - return concat(imap(unicode, seq)) - - -def new_context(environment, template_name, blocks, vars=None, - shared=None, globals=None, locals=None): - """Internal helper to for context creation.""" - if vars is None: - vars = {} - if shared: - parent = vars - else: - parent = dict(globals or (), **vars) - if locals: - # if the parent is shared a copy should be created because - # we don't want to modify the dict passed - if shared: - parent = dict(parent) - for key, value in locals.iteritems(): - if key[:2] == 'l_' and value is not missing: - parent[key[2:]] = value - return Context(environment, parent, template_name, blocks) - - -class TemplateReference(object): - """The `self` in templates.""" - - def __init__(self, context): - self.__context = context - - def __getitem__(self, name): - blocks = self.__context.blocks[name] - wrap = self.__context.eval_ctx.autoescape and \ - Markup or (lambda x: x) - return BlockReference(name, self.__context, blocks, 0) - - def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self.__context.name - ) - - -class Context(object): - """The template context holds the variables of a template. It stores the - values passed to the template and also the names the template exports. - Creating instances is neither supported nor useful as it's created - automatically at various stages of the template evaluation and should not - be created by hand. - - The context is immutable. Modifications on :attr:`parent` **must not** - happen and modifications on :attr:`vars` are allowed from generated - template code only. Template filters and global functions marked as - :func:`contextfunction`\s get the active context passed as first argument - and are allowed to access the context read-only. - - The template context supports read only dict operations (`get`, - `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`, - `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve` - method that doesn't fail with a `KeyError` but returns an - :class:`Undefined` object for missing variables. - """ - __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars', - 'name', 'blocks', '__weakref__') - - def __init__(self, environment, parent, name, blocks): - self.parent = parent - self.vars = {} - self.environment = environment - self.eval_ctx = EvalContext(self.environment, name) - self.exported_vars = set() - self.name = name - - # create the initial mapping of blocks. Whenever template inheritance - # takes place the runtime will update this mapping with the new blocks - # from the template. - self.blocks = dict((k, [v]) for k, v in blocks.iteritems()) - - def super(self, name, current): - """Render a parent block.""" - try: - blocks = self.blocks[name] - index = blocks.index(current) + 1 - blocks[index] - except LookupError: - return self.environment.undefined('there is no parent block ' - 'called %r.' % name, - name='super') - return BlockReference(name, self, blocks, index) - - def get(self, key, default=None): - """Returns an item from the template context, if it doesn't exist - `default` is returned. - """ - try: - return self[key] - except KeyError: - return default - - def resolve(self, key): - """Looks up a variable like `__getitem__` or `get` but returns an - :class:`Undefined` object with the name of the name looked up. - """ - if key in self.vars: - return self.vars[key] - if key in self.parent: - return self.parent[key] - return self.environment.undefined(name=key) - - def get_exported(self): - """Get a new dict with the exported variables.""" - return dict((k, self.vars[k]) for k in self.exported_vars) - - def get_all(self): - """Return a copy of the complete context as dict including the - exported variables. - """ - return dict(self.parent, **self.vars) - - @internalcode - def call(__self, __obj, *args, **kwargs): - """Call the callable with the arguments and keyword arguments - provided but inject the active context or environment as first - argument if the callable is a :func:`contextfunction` or - :func:`environmentfunction`. - """ - if __debug__: - __traceback_hide__ = True - if isinstance(__obj, _context_function_types): - if getattr(__obj, 'contextfunction', 0): - args = (__self,) + args - elif getattr(__obj, 'evalcontextfunction', 0): - args = (__self.eval_ctx,) + args - elif getattr(__obj, 'environmentfunction', 0): - args = (__self.environment,) + args - try: - return __obj(*args, **kwargs) - except StopIteration: - return __self.environment.undefined('value was undefined because ' - 'a callable raised a ' - 'StopIteration exception') - - def derived(self, locals=None): - """Internal helper function to create a derived context.""" - context = new_context(self.environment, self.name, {}, - self.parent, True, None, locals) - context.eval_ctx = self.eval_ctx - context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems()) - return context - - def _all(meth): - proxy = lambda self: getattr(self.get_all(), meth)() - proxy.__doc__ = getattr(dict, meth).__doc__ - proxy.__name__ = meth - return proxy - - keys = _all('keys') - values = _all('values') - items = _all('items') - - # not available on python 3 - if hasattr(dict, 'iterkeys'): - iterkeys = _all('iterkeys') - itervalues = _all('itervalues') - iteritems = _all('iteritems') - del _all - - def __contains__(self, name): - return name in self.vars or name in self.parent - - def __getitem__(self, key): - """Lookup a variable or raise `KeyError` if the variable is - undefined. - """ - item = self.resolve(key) - if isinstance(item, Undefined): - raise KeyError(key) - return item - - def __repr__(self): - return '<%s %s of %r>' % ( - self.__class__.__name__, - repr(self.get_all()), - self.name - ) - - -# register the context as mapping if possible -try: - from collections import Mapping - Mapping.register(Context) -except ImportError: - pass - - -class BlockReference(object): - """One block on a template reference.""" - - def __init__(self, name, context, stack, depth): - self.name = name - self._context = context - self._stack = stack - self._depth = depth - - @property - def super(self): - """Super the block.""" - if self._depth + 1 >= len(self._stack): - return self._context.environment. \ - undefined('there is no parent block called %r.' % - self.name, name='super') - return BlockReference(self.name, self._context, self._stack, - self._depth + 1) - - @internalcode - def __call__(self): - rv = concat(self._stack[self._depth](self._context)) - if self._context.eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -class LoopContext(object): - """A loop context for dynamic iteration.""" - - def __init__(self, iterable, recurse=None): - self._iterator = iter(iterable) - self._recurse = recurse - self.index0 = -1 - - # try to get the length of the iterable early. This must be done - # here because there are some broken iterators around where there - # __len__ is the number of iterations left (i'm looking at your - # listreverseiterator!). - try: - self._length = len(iterable) - except (TypeError, AttributeError): - self._length = None - - def cycle(self, *args): - """Cycles among the arguments with the current loop index.""" - if not args: - raise TypeError('no items for cycling given') - return args[self.index0 % len(args)] - - first = property(lambda x: x.index0 == 0) - last = property(lambda x: x.index0 + 1 == x.length) - index = property(lambda x: x.index0 + 1) - revindex = property(lambda x: x.length - x.index0) - revindex0 = property(lambda x: x.length - x.index) - - def __len__(self): - return self.length - - def __iter__(self): - return LoopContextIterator(self) - - @internalcode - def loop(self, iterable): - if self._recurse is None: - raise TypeError('Tried to call non recursive loop. Maybe you ' - "forgot the 'recursive' modifier.") - return self._recurse(iterable, self._recurse) - - # a nifty trick to enhance the error message if someone tried to call - # the the loop without or with too many arguments. - __call__ = loop - del loop - - @property - def length(self): - if self._length is None: - # if was not possible to get the length of the iterator when - # the loop context was created (ie: iterating over a generator) - # we have to convert the iterable into a sequence and use the - # length of that. - iterable = tuple(self._iterator) - self._iterator = iter(iterable) - self._length = len(iterable) + self.index0 + 1 - return self._length - - def __repr__(self): - return '<%s %r/%r>' % ( - self.__class__.__name__, - self.index, - self.length - ) - - -class LoopContextIterator(object): - """The iterator for a loop context.""" - __slots__ = ('context',) - - def __init__(self, context): - self.context = context - - def __iter__(self): - return self - - def next(self): - ctx = self.context - ctx.index0 += 1 - return next(ctx._iterator), ctx - - -class Macro(object): - """Wraps a macro function.""" - - def __init__(self, environment, func, name, arguments, defaults, - catch_kwargs, catch_varargs, caller): - self._environment = environment - self._func = func - self._argument_count = len(arguments) - self.name = name - self.arguments = arguments - self.defaults = defaults - self.catch_kwargs = catch_kwargs - self.catch_varargs = catch_varargs - self.caller = caller - - @internalcode - def __call__(self, *args, **kwargs): - # try to consume the positional arguments - arguments = list(args[:self._argument_count]) - off = len(arguments) - - # if the number of arguments consumed is not the number of - # arguments expected we start filling in keyword arguments - # and defaults. - if off != self._argument_count: - for idx, name in enumerate(self.arguments[len(arguments):]): - try: - value = kwargs.pop(name) - except KeyError: - try: - value = self.defaults[idx - self._argument_count + off] - except IndexError: - value = self._environment.undefined( - 'parameter %r was not provided' % name, name=name) - arguments.append(value) - - # it's important that the order of these arguments does not change - # if not also changed in the compiler's `function_scoping` method. - # the order is caller, keyword arguments, positional arguments! - if self.caller: - caller = kwargs.pop('caller', None) - if caller is None: - caller = self._environment.undefined('No caller defined', - name='caller') - arguments.append(caller) - if self.catch_kwargs: - arguments.append(kwargs) - elif kwargs: - raise TypeError('macro %r takes no keyword argument %r' % - (self.name, next(iter(kwargs)))) - if self.catch_varargs: - arguments.append(args[self._argument_count:]) - elif len(args) > self._argument_count: - raise TypeError('macro %r takes not more than %d argument(s)' % - (self.name, len(self.arguments))) - return self._func(*arguments) - - def __repr__(self): - return '<%s %s>' % ( - self.__class__.__name__, - self.name is None and 'anonymous' or repr(self.name) - ) - - -class Undefined(object): - """The default undefined type. This undefined type can be printed and - iterated over, but every other access will raise an :exc:`UndefinedError`: - - >>> foo = Undefined(name='foo') - >>> str(foo) - '' - >>> not foo - True - >>> foo + 42 - Traceback (most recent call last): - ... - UndefinedError: 'foo' is undefined - """ - __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name', - '_undefined_exception') - - def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError): - self._undefined_hint = hint - self._undefined_obj = obj - self._undefined_name = name - self._undefined_exception = exc - - @internalcode - def _fail_with_undefined_error(self, *args, **kwargs): - """Regular callback function for undefined objects that raises an - `UndefinedError` on call. - """ - if self._undefined_hint is None: - if self._undefined_obj is missing: - hint = '%r is undefined' % self._undefined_name - elif not isinstance(self._undefined_name, basestring): - hint = '%s has no element %r' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - else: - hint = '%r has no attribute %r' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - else: - hint = self._undefined_hint - raise self._undefined_exception(hint) - - __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ - __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \ - __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ - __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \ - __int__ = __float__ = __complex__ = __pow__ = __rpow__ = \ - _fail_with_undefined_error - - def __str__(self): - return unicode(self).encode('utf-8') - - # unicode goes after __str__ because we configured 2to3 to rename - # __unicode__ to __str__. because the 2to3 tree is not designed to - # remove nodes from it, we leave the above __str__ around and let - # it override at runtime. - def __unicode__(self): - return u'' - - def __len__(self): - return 0 - - def __iter__(self): - if 0: - yield None - - def __nonzero__(self): - return False - - def __repr__(self): - return 'Undefined' - - -class DebugUndefined(Undefined): - """An undefined that returns the debug info when printed. - - >>> foo = DebugUndefined(name='foo') - >>> str(foo) - '{{ foo }}' - >>> not foo - True - >>> foo + 42 - Traceback (most recent call last): - ... - UndefinedError: 'foo' is undefined - """ - __slots__ = () - - def __unicode__(self): - if self._undefined_hint is None: - if self._undefined_obj is missing: - return u'{{ %s }}' % self._undefined_name - return '{{ no such element: %s[%r] }}' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - return u'{{ undefined value printed: %s }}' % self._undefined_hint - - -class StrictUndefined(Undefined): - """An undefined that barks on print and iteration as well as boolean - tests and all kinds of comparisons. In other words: you can do nothing - with it except checking if it's defined using the `defined` test. - - >>> foo = StrictUndefined(name='foo') - >>> str(foo) - Traceback (most recent call last): - ... - UndefinedError: 'foo' is undefined - >>> not foo - Traceback (most recent call last): - ... - UndefinedError: 'foo' is undefined - >>> foo + 42 - Traceback (most recent call last): - ... - UndefinedError: 'foo' is undefined - """ - __slots__ = () - __iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \ - __ne__ = __bool__ = Undefined._fail_with_undefined_error - - -# remove remaining slots attributes, after the metaclass did the magic they -# are unneeded and irritating as they contain wrong data for the subclasses. -del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ diff --git a/module/lib/jinja2/sandbox.py b/module/lib/jinja2/sandbox.py deleted file mode 100644 index 749719548..000000000 --- a/module/lib/jinja2/sandbox.py +++ /dev/null @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.sandbox - ~~~~~~~~~~~~~~ - - Adds a sandbox layer to Jinja as it was the default behavior in the old - Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the - default behavior is easier to use. - - The behavior can be changed by subclassing the environment. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -import operator -from jinja2.runtime import Undefined -from jinja2.environment import Environment -from jinja2.exceptions import SecurityError -from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \ - FrameType, GeneratorType - - -#: maximum number of items a range may produce -MAX_RANGE = 100000 - -#: attributes of function objects that are considered unsafe. -UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict', - 'func_defaults', 'func_globals']) - -#: unsafe method attributes. function attributes are unsafe for methods too -UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self']) - - -import warnings - -# make sure we don't warn in python 2.6 about stuff we don't care about -warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning, - module='jinja2.sandbox') - -from collections import deque - -_mutable_set_types = (set,) -_mutable_mapping_types = (dict,) -_mutable_sequence_types = (list,) - - -# on python 2.x we can register the user collection types -try: - from UserDict import UserDict, DictMixin - from UserList import UserList - _mutable_mapping_types += (UserDict, DictMixin) - _mutable_set_types += (UserList,) -except ImportError: - pass - -# if sets is still available, register the mutable set from there as well -try: - from sets import Set - _mutable_set_types += (Set,) -except ImportError: - pass - -#: register Python 2.6 abstract base classes -try: - from collections import MutableSet, MutableMapping, MutableSequence - _mutable_set_types += (MutableSet,) - _mutable_mapping_types += (MutableMapping,) - _mutable_sequence_types += (MutableSequence,) -except ImportError: - pass - -_mutable_spec = ( - (_mutable_set_types, frozenset([ - 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove', - 'symmetric_difference_update', 'update' - ])), - (_mutable_mapping_types, frozenset([ - 'clear', 'pop', 'popitem', 'setdefault', 'update' - ])), - (_mutable_sequence_types, frozenset([ - 'append', 'reverse', 'insert', 'sort', 'extend', 'remove' - ])), - (deque, frozenset([ - 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop', - 'popleft', 'remove', 'rotate' - ])) -) - - -def safe_range(*args): - """A range that can't generate ranges with a length of more than - MAX_RANGE items. - """ - rng = xrange(*args) - if len(rng) > MAX_RANGE: - raise OverflowError('range too big, maximum size for range is %d' % - MAX_RANGE) - return rng - - -def unsafe(f): - """ - Mark a function or method as unsafe:: - - @unsafe - def delete(self): - pass - """ - f.unsafe_callable = True - return f - - -def is_internal_attribute(obj, attr): - """Test if the attribute given is an internal python attribute. For - example this function returns `True` for the `func_code` attribute of - python objects. This is useful if the environment method - :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden. - - >>> from jinja2.sandbox import is_internal_attribute - >>> is_internal_attribute(lambda: None, "func_code") - True - >>> is_internal_attribute((lambda x:x).func_code, 'co_code') - True - >>> is_internal_attribute(str, "upper") - False - """ - if isinstance(obj, FunctionType): - if attr in UNSAFE_FUNCTION_ATTRIBUTES: - return True - elif isinstance(obj, MethodType): - if attr in UNSAFE_FUNCTION_ATTRIBUTES or \ - attr in UNSAFE_METHOD_ATTRIBUTES: - return True - elif isinstance(obj, type): - if attr == 'mro': - return True - elif isinstance(obj, (CodeType, TracebackType, FrameType)): - return True - elif isinstance(obj, GeneratorType): - if attr == 'gi_frame': - return True - return attr.startswith('__') - - -def modifies_known_mutable(obj, attr): - """This function checks if an attribute on a builtin mutable object - (list, dict, set or deque) would modify it if called. It also supports - the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and - with Python 2.6 onwards the abstract base classes `MutableSet`, - `MutableMapping`, and `MutableSequence`. - - >>> modifies_known_mutable({}, "clear") - True - >>> modifies_known_mutable({}, "keys") - False - >>> modifies_known_mutable([], "append") - True - >>> modifies_known_mutable([], "index") - False - - If called with an unsupported object (such as unicode) `False` is - returned. - - >>> modifies_known_mutable("foo", "upper") - False - """ - for typespec, unsafe in _mutable_spec: - if isinstance(obj, typespec): - return attr in unsafe - return False - - -class SandboxedEnvironment(Environment): - """The sandboxed environment. It works like the regular environment but - tells the compiler to generate sandboxed code. Additionally subclasses of - this environment may override the methods that tell the runtime what - attributes or functions are safe to access. - - If the template tries to access insecure code a :exc:`SecurityError` is - raised. However also other exceptions may occour during the rendering so - the caller has to ensure that all exceptions are catched. - """ - sandboxed = True - - def __init__(self, *args, **kwargs): - Environment.__init__(self, *args, **kwargs) - self.globals['range'] = safe_range - - def is_safe_attribute(self, obj, attr, value): - """The sandboxed environment will call this method to check if the - attribute of an object is safe to access. Per default all attributes - starting with an underscore are considered private as well as the - special attributes of internal python objects as returned by the - :func:`is_internal_attribute` function. - """ - return not (attr.startswith('_') or is_internal_attribute(obj, attr)) - - def is_safe_callable(self, obj): - """Check if an object is safely callable. Per default a function is - considered safe unless the `unsafe_callable` attribute exists and is - True. Override this method to alter the behavior, but this won't - affect the `unsafe` decorator from this module. - """ - return not (getattr(obj, 'unsafe_callable', False) or \ - getattr(obj, 'alters_data', False)) - - def getitem(self, obj, argument): - """Subscribe an object from sandboxed code.""" - try: - return obj[argument] - except (TypeError, LookupError): - if isinstance(argument, basestring): - try: - attr = str(argument) - except: - pass - else: - try: - value = getattr(obj, attr) - except AttributeError: - pass - else: - if self.is_safe_attribute(obj, argument, value): - return value - return self.unsafe_undefined(obj, argument) - return self.undefined(obj=obj, name=argument) - - def getattr(self, obj, attribute): - """Subscribe an object from sandboxed code and prefer the - attribute. The attribute passed *must* be a bytestring. - """ - try: - value = getattr(obj, attribute) - except AttributeError: - try: - return obj[attribute] - except (TypeError, LookupError): - pass - else: - if self.is_safe_attribute(obj, attribute, value): - return value - return self.unsafe_undefined(obj, attribute) - return self.undefined(obj=obj, name=attribute) - - def unsafe_undefined(self, obj, attribute): - """Return an undefined object for unsafe attributes.""" - return self.undefined('access to attribute %r of %r ' - 'object is unsafe.' % ( - attribute, - obj.__class__.__name__ - ), name=attribute, obj=obj, exc=SecurityError) - - def call(__self, __context, __obj, *args, **kwargs): - """Call an object from sandboxed code.""" - # the double prefixes are to avoid double keyword argument - # errors when proxying the call. - if not __self.is_safe_callable(__obj): - raise SecurityError('%r is not safely callable' % (__obj,)) - return __context.call(__obj, *args, **kwargs) - - -class ImmutableSandboxedEnvironment(SandboxedEnvironment): - """Works exactly like the regular `SandboxedEnvironment` but does not - permit modifications on the builtin mutable objects `list`, `set`, and - `dict` by using the :func:`modifies_known_mutable` function. - """ - - def is_safe_attribute(self, obj, attr, value): - if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value): - return False - return not modifies_known_mutable(obj, attr) diff --git a/module/lib/jinja2/tests.py b/module/lib/jinja2/tests.py deleted file mode 100644 index d257eca0a..000000000 --- a/module/lib/jinja2/tests.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.tests - ~~~~~~~~~~~~ - - Jinja test functions. Used with the "is" operator. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -from jinja2.runtime import Undefined - -# nose, nothing here to test -__test__ = False - - -number_re = re.compile(r'^-?\d+(\.\d+)?$') -regex_type = type(number_re) - - -try: - test_callable = callable -except NameError: - def test_callable(x): - return hasattr(x, '__call__') - - -def test_odd(value): - """Return true if the variable is odd.""" - return value % 2 == 1 - - -def test_even(value): - """Return true if the variable is even.""" - return value % 2 == 0 - - -def test_divisibleby(value, num): - """Check if a variable is divisible by a number.""" - return value % num == 0 - - -def test_defined(value): - """Return true if the variable is defined: - - .. sourcecode:: jinja - - {% if variable is defined %} - value of variable: {{ variable }} - {% else %} - variable is not defined - {% endif %} - - See the :func:`default` filter for a simple way to set undefined - variables. - """ - return not isinstance(value, Undefined) - - -def test_undefined(value): - """Like :func:`defined` but the other way round.""" - return isinstance(value, Undefined) - - -def test_none(value): - """Return true if the variable is none.""" - return value is None - - -def test_lower(value): - """Return true if the variable is lowercased.""" - return unicode(value).islower() - - -def test_upper(value): - """Return true if the variable is uppercased.""" - return unicode(value).isupper() - - -def test_string(value): - """Return true if the object is a string.""" - return isinstance(value, basestring) - - -def test_number(value): - """Return true if the variable is a number.""" - return isinstance(value, (int, long, float, complex)) - - -def test_sequence(value): - """Return true if the variable is a sequence. Sequences are variables - that are iterable. - """ - try: - len(value) - value.__getitem__ - except: - return False - return True - - -def test_sameas(value, other): - """Check if an object points to the same memory address than another - object: - - .. sourcecode:: jinja - - {% if foo.attribute is sameas false %} - the foo attribute really is the `False` singleton - {% endif %} - """ - return value is other - - -def test_iterable(value): - """Check if it's possible to iterate over an object.""" - try: - iter(value) - except TypeError: - return False - return True - - -def test_escaped(value): - """Check if the value is escaped.""" - return hasattr(value, '__html__') - - -TESTS = { - 'odd': test_odd, - 'even': test_even, - 'divisibleby': test_divisibleby, - 'defined': test_defined, - 'undefined': test_undefined, - 'none': test_none, - 'lower': test_lower, - 'upper': test_upper, - 'string': test_string, - 'number': test_number, - 'sequence': test_sequence, - 'iterable': test_iterable, - 'callable': test_callable, - 'sameas': test_sameas, - 'escaped': test_escaped -} diff --git a/module/lib/jinja2/utils.py b/module/lib/jinja2/utils.py deleted file mode 100644 index 7b77b8eb7..000000000 --- a/module/lib/jinja2/utils.py +++ /dev/null @@ -1,601 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.utils - ~~~~~~~~~~~~ - - Utility functions. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -import sys -import errno -try: - from thread import allocate_lock -except ImportError: - from dummy_thread import allocate_lock -from collections import deque -from itertools import imap - - -_word_split_re = re.compile(r'(\s+)') -_punctuation_re = re.compile( - '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( - '|'.join(imap(re.escape, ('(', '<', '<'))), - '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>'))) - ) -) -_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') -_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') -_entity_re = re.compile(r'&([^;]+);') -_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' -_digits = '0123456789' - -# special singleton representing missing values for the runtime -missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() - -# internal code -internal_code = set() - - -# concatenate a list of strings and convert them to unicode. -# unfortunately there is a bug in python 2.4 and lower that causes -# unicode.join trash the traceback. -_concat = u''.join -try: - def _test_gen_bug(): - raise TypeError(_test_gen_bug) - yield None - _concat(_test_gen_bug()) -except TypeError, _error: - if not _error.args or _error.args[0] is not _test_gen_bug: - def concat(gen): - try: - return _concat(list(gen)) - except: - # this hack is needed so that the current frame - # does not show up in the traceback. - exc_type, exc_value, tb = sys.exc_info() - raise exc_type, exc_value, tb.tb_next - else: - concat = _concat - del _test_gen_bug, _error - - -# for python 2.x we create outselves a next() function that does the -# basics without exception catching. -try: - next = next -except NameError: - def next(x): - return x.next() - - -# if this python version is unable to deal with unicode filenames -# when passed to encode we let this function encode it properly. -# This is used in a couple of places. As far as Jinja is concerned -# filenames are unicode *or* bytestrings in 2.x and unicode only in -# 3.x because compile cannot handle bytes -if sys.version_info < (3, 0): - def _encode_filename(filename): - if isinstance(filename, unicode): - return filename.encode('utf-8') - return filename -else: - def _encode_filename(filename): - assert filename is None or isinstance(filename, str), \ - 'filenames must be strings' - return filename - -from keyword import iskeyword as is_python_keyword - - -# common types. These do exist in the special types module too which however -# does not exist in IronPython out of the box. Also that way we don't have -# to deal with implementation specific stuff here -class _C(object): - def method(self): pass -def _func(): - yield None -FunctionType = type(_func) -GeneratorType = type(_func()) -MethodType = type(_C.method) -CodeType = type(_C.method.func_code) -try: - raise TypeError() -except TypeError: - _tb = sys.exc_info()[2] - TracebackType = type(_tb) - FrameType = type(_tb.tb_frame) -del _C, _tb, _func - - -def contextfunction(f): - """This decorator can be used to mark a function or method context callable. - A context callable is passed the active :class:`Context` as first argument when - called from the template. This is useful if a function wants to get access - to the context or functions provided on the context object. For example - a function that returns a sorted list of template variables the current - template exports could look like this:: - - @contextfunction - def get_exported_names(context): - return sorted(context.exported_vars) - """ - f.contextfunction = True - return f - - -def evalcontextfunction(f): - """This decoraotr can be used to mark a function or method as an eval - context callable. This is similar to the :func:`contextfunction` - but instead of passing the context, an evaluation context object is - passed. For more information about the eval context, see - :ref:`eval-context`. - - .. versionadded:: 2.4 - """ - f.evalcontextfunction = True - return f - - -def environmentfunction(f): - """This decorator can be used to mark a function or method as environment - callable. This decorator works exactly like the :func:`contextfunction` - decorator just that the first argument is the active :class:`Environment` - and not context. - """ - f.environmentfunction = True - return f - - -def internalcode(f): - """Marks the function as internally used""" - internal_code.add(f.func_code) - return f - - -def is_undefined(obj): - """Check if the object passed is undefined. This does nothing more than - performing an instance check against :class:`Undefined` but looks nicer. - This can be used for custom filters or tests that want to react to - undefined variables. For example a custom default filter can look like - this:: - - def default(var, default=''): - if is_undefined(var): - return default - return var - """ - from jinja2.runtime import Undefined - return isinstance(obj, Undefined) - - -def consume(iterable): - """Consumes an iterable without doing anything with it.""" - for event in iterable: - pass - - -def clear_caches(): - """Jinja2 keeps internal caches for environments and lexers. These are - used so that Jinja2 doesn't have to recreate environments and lexers all - the time. Normally you don't have to care about that but if you are - messuring memory consumption you may want to clean the caches. - """ - from jinja2.environment import _spontaneous_environments - from jinja2.lexer import _lexer_cache - _spontaneous_environments.clear() - _lexer_cache.clear() - - -def import_string(import_name, silent=False): - """Imports an object based on a string. This use useful if you want to - use import paths as endpoints or something similar. An import path can - be specified either in dotted notation (``xml.sax.saxutils.escape``) - or with a colon as object delimiter (``xml.sax.saxutils:escape``). - - If the `silent` is True the return value will be `None` if the import - fails. - - :return: imported object - """ - try: - if ':' in import_name: - module, obj = import_name.split(':', 1) - elif '.' in import_name: - items = import_name.split('.') - module = '.'.join(items[:-1]) - obj = items[-1] - else: - return __import__(import_name) - return getattr(__import__(module, None, None, [obj]), obj) - except (ImportError, AttributeError): - if not silent: - raise - - -def open_if_exists(filename, mode='rb'): - """Returns a file descriptor for the filename if that file exists, - otherwise `None`. - """ - try: - return open(filename, mode) - except IOError, e: - if e.errno not in (errno.ENOENT, errno.EISDIR): - raise - - -def object_type_repr(obj): - """Returns the name of the object's type. For some recognized - singletons the name of the object is returned instead. (For - example for `None` and `Ellipsis`). - """ - if obj is None: - return 'None' - elif obj is Ellipsis: - return 'Ellipsis' - # __builtin__ in 2.x, builtins in 3.x - if obj.__class__.__module__ in ('__builtin__', 'builtins'): - name = obj.__class__.__name__ - else: - name = obj.__class__.__module__ + '.' + obj.__class__.__name__ - return '%s object' % name - - -def pformat(obj, verbose=False): - """Prettyprint an object. Either use the `pretty` library or the - builtin `pprint`. - """ - try: - from pretty import pretty - return pretty(obj, verbose=verbose) - except ImportError: - from pprint import pformat - return pformat(obj) - - -def urlize(text, trim_url_limit=None, nofollow=False): - """Converts any URLs in text into clickable links. Works on http://, - https:// and www. links. Links can have trailing punctuation (periods, - commas, close-parens) and leading punctuation (opening parens) and - it'll still do the right thing. - - If trim_url_limit is not None, the URLs in link text will be limited - to trim_url_limit characters. - - If nofollow is True, the URLs in link text will get a rel="nofollow" - attribute. - """ - trim_url = lambda x, limit=trim_url_limit: limit is not None \ - and (x[:limit] + (len(x) >=limit and '...' - or '')) or x - words = _word_split_re.split(unicode(escape(text))) - nofollow_attr = nofollow and ' rel="nofollow"' or '' - for i, word in enumerate(words): - match = _punctuation_re.match(word) - if match: - lead, middle, trail = match.groups() - if middle.startswith('www.') or ( - '@' not in middle and - not middle.startswith('http://') and - len(middle) > 0 and - middle[0] in _letters + _digits and ( - middle.endswith('.org') or - middle.endswith('.net') or - middle.endswith('.com') - )): - middle = '<a href="http://%s"%s>%s</a>' % (middle, - nofollow_attr, trim_url(middle)) - if middle.startswith('http://') or \ - middle.startswith('https://'): - middle = '<a href="%s"%s>%s</a>' % (middle, - nofollow_attr, trim_url(middle)) - if '@' in middle and not middle.startswith('www.') and \ - not ':' in middle and _simple_email_re.match(middle): - middle = '<a href="mailto:%s">%s</a>' % (middle, middle) - if lead + middle + trail != word: - words[i] = lead + middle + trail - return u''.join(words) - - -def generate_lorem_ipsum(n=5, html=True, min=20, max=100): - """Generate some lorem impsum for the template.""" - from jinja2.constants import LOREM_IPSUM_WORDS - from random import choice, randrange - words = LOREM_IPSUM_WORDS.split() - result = [] - - for _ in xrange(n): - next_capitalized = True - last_comma = last_fullstop = 0 - word = None - last = None - p = [] - - # each paragraph contains out of 20 to 100 words. - for idx, _ in enumerate(xrange(randrange(min, max))): - while True: - word = choice(words) - if word != last: - last = word - break - if next_capitalized: - word = word.capitalize() - next_capitalized = False - # add commas - if idx - randrange(3, 8) > last_comma: - last_comma = idx - last_fullstop += 2 - word += ',' - # add end of sentences - if idx - randrange(10, 20) > last_fullstop: - last_comma = last_fullstop = idx - word += '.' - next_capitalized = True - p.append(word) - - # ensure that the paragraph ends with a dot. - p = u' '.join(p) - if p.endswith(','): - p = p[:-1] + '.' - elif not p.endswith('.'): - p += '.' - result.append(p) - - if not html: - return u'\n\n'.join(result) - return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) - - -class LRUCache(object): - """A simple LRU Cache implementation.""" - - # this is fast for small capacities (something below 1000) but doesn't - # scale. But as long as it's only used as storage for templates this - # won't do any harm. - - def __init__(self, capacity): - self.capacity = capacity - self._mapping = {} - self._queue = deque() - self._postinit() - - def _postinit(self): - # alias all queue methods for faster lookup - self._popleft = self._queue.popleft - self._pop = self._queue.pop - if hasattr(self._queue, 'remove'): - self._remove = self._queue.remove - self._wlock = allocate_lock() - self._append = self._queue.append - - def _remove(self, obj): - """Python 2.4 compatibility.""" - for idx, item in enumerate(self._queue): - if item == obj: - del self._queue[idx] - break - - def __getstate__(self): - return { - 'capacity': self.capacity, - '_mapping': self._mapping, - '_queue': self._queue - } - - def __setstate__(self, d): - self.__dict__.update(d) - self._postinit() - - def __getnewargs__(self): - return (self.capacity,) - - def copy(self): - """Return an shallow copy of the instance.""" - rv = self.__class__(self.capacity) - rv._mapping.update(self._mapping) - rv._queue = deque(self._queue) - return rv - - def get(self, key, default=None): - """Return an item from the cache dict or `default`""" - try: - return self[key] - except KeyError: - return default - - def setdefault(self, key, default=None): - """Set `default` if the key is not in the cache otherwise - leave unchanged. Return the value of this key. - """ - try: - return self[key] - except KeyError: - self[key] = default - return default - - def clear(self): - """Clear the cache.""" - self._wlock.acquire() - try: - self._mapping.clear() - self._queue.clear() - finally: - self._wlock.release() - - def __contains__(self, key): - """Check if a key exists in this cache.""" - return key in self._mapping - - def __len__(self): - """Return the current size of the cache.""" - return len(self._mapping) - - def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self._mapping - ) - - def __getitem__(self, key): - """Get an item from the cache. Moves the item up so that it has the - highest priority then. - - Raise an `KeyError` if it does not exist. - """ - rv = self._mapping[key] - if self._queue[-1] != key: - try: - self._remove(key) - except ValueError: - # if something removed the key from the container - # when we read, ignore the ValueError that we would - # get otherwise. - pass - self._append(key) - return rv - - def __setitem__(self, key, value): - """Sets the value for an item. Moves the item up so that it - has the highest priority then. - """ - self._wlock.acquire() - try: - if key in self._mapping: - try: - self._remove(key) - except ValueError: - # __getitem__ is not locked, it might happen - pass - elif len(self._mapping) == self.capacity: - del self._mapping[self._popleft()] - self._append(key) - self._mapping[key] = value - finally: - self._wlock.release() - - def __delitem__(self, key): - """Remove an item from the cache dict. - Raise an `KeyError` if it does not exist. - """ - self._wlock.acquire() - try: - del self._mapping[key] - try: - self._remove(key) - except ValueError: - # __getitem__ is not locked, it might happen - pass - finally: - self._wlock.release() - - def items(self): - """Return a list of items.""" - result = [(key, self._mapping[key]) for key in list(self._queue)] - result.reverse() - return result - - def iteritems(self): - """Iterate over all items.""" - return iter(self.items()) - - def values(self): - """Return a list of all values.""" - return [x[1] for x in self.items()] - - def itervalue(self): - """Iterate over all values.""" - return iter(self.values()) - - def keys(self): - """Return a list of all keys ordered by most recent usage.""" - return list(self) - - def iterkeys(self): - """Iterate over all keys in the cache dict, ordered by - the most recent usage. - """ - return reversed(tuple(self._queue)) - - __iter__ = iterkeys - - def __reversed__(self): - """Iterate over the values in the cache dict, oldest items - coming first. - """ - return iter(tuple(self._queue)) - - __copy__ = copy - - -# register the LRU cache as mutable mapping if possible -try: - from collections import MutableMapping - MutableMapping.register(LRUCache) -except ImportError: - pass - - -class Cycler(object): - """A cycle helper for templates.""" - - def __init__(self, *items): - if not items: - raise RuntimeError('at least one item has to be provided') - self.items = items - self.reset() - - def reset(self): - """Resets the cycle.""" - self.pos = 0 - - @property - def current(self): - """Returns the current item.""" - return self.items[self.pos] - - def next(self): - """Goes one item ahead and returns it.""" - rv = self.current - self.pos = (self.pos + 1) % len(self.items) - return rv - - -class Joiner(object): - """A joining helper for templates.""" - - def __init__(self, sep=u', '): - self.sep = sep - self.used = False - - def __call__(self): - if not self.used: - self.used = True - return u'' - return self.sep - - -# try markupsafe first, if that fails go with Jinja2's bundled version -# of markupsafe. Markupsafe was previously Jinja2's implementation of -# the Markup object but was moved into a separate package in a patchleve -# release -try: - from markupsafe import Markup, escape, soft_unicode -except ImportError: - from jinja2._markupsafe import Markup, escape, soft_unicode - - -# partials -try: - from functools import partial -except ImportError: - class partial(object): - def __init__(self, _func, *args, **kwargs): - self._func = _func - self._args = args - self._kwargs = kwargs - def __call__(self, *args, **kwargs): - kwargs.update(self._kwargs) - return self._func(*(self._args + args), **kwargs) diff --git a/module/lib/jinja2/visitor.py b/module/lib/jinja2/visitor.py deleted file mode 100644 index 413e7c309..000000000 --- a/module/lib/jinja2/visitor.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.visitor - ~~~~~~~~~~~~~~ - - This module implements a visitor for the nodes. - - :copyright: (c) 2010 by the Jinja Team. - :license: BSD. -""" -from jinja2.nodes import Node - - -class NodeVisitor(object): - """Walks the abstract syntax tree and call visitor functions for every - node found. The visitor functions may return values which will be - forwarded by the `visit` method. - - Per default the visitor functions for the nodes are ``'visit_'`` + - class name of the node. So a `TryFinally` node visit function would - be `visit_TryFinally`. This behavior can be changed by overriding - the `get_visitor` function. If no visitor function exists for a node - (return value `None`) the `generic_visit` visitor is used instead. - """ - - def get_visitor(self, node): - """Return the visitor function for this node or `None` if no visitor - exists for this node. In that case the generic visit function is - used instead. - """ - method = 'visit_' + node.__class__.__name__ - return getattr(self, method, None) - - def visit(self, node, *args, **kwargs): - """Visit a node.""" - f = self.get_visitor(node) - if f is not None: - return f(node, *args, **kwargs) - return self.generic_visit(node, *args, **kwargs) - - def generic_visit(self, node, *args, **kwargs): - """Called if no explicit visitor function exists for a node.""" - for node in node.iter_child_nodes(): - self.visit(node, *args, **kwargs) - - -class NodeTransformer(NodeVisitor): - """Walks the abstract syntax tree and allows modifications of nodes. - - The `NodeTransformer` will walk the AST and use the return value of the - visitor functions to replace or remove the old node. If the return - value of the visitor function is `None` the node will be removed - from the previous location otherwise it's replaced with the return - value. The return value may be the original node in which case no - replacement takes place. - """ - - def generic_visit(self, node, *args, **kwargs): - for field, old_value in node.iter_fields(): - if isinstance(old_value, list): - new_values = [] - for value in old_value: - if isinstance(value, Node): - value = self.visit(value, *args, **kwargs) - if value is None: - continue - elif not isinstance(value, Node): - new_values.extend(value) - continue - new_values.append(value) - old_value[:] = new_values - elif isinstance(old_value, Node): - new_node = self.visit(old_value, *args, **kwargs) - if new_node is None: - delattr(node, field) - else: - setattr(node, field, new_node) - return node - - def visit_list(self, node, *args, **kwargs): - """As transformers may return lists in some places this method - can be used to enforce a list as return value. - """ - rv = self.visit(node, *args, **kwargs) - if not isinstance(rv, list): - rv = [rv] - return rv diff --git a/module/lib/thrift/TSCons.py b/module/lib/thrift/TSCons.py deleted file mode 100644 index 24046256c..000000000 --- a/module/lib/thrift/TSCons.py +++ /dev/null @@ -1,33 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from os import path -from SCons.Builder import Builder - -def scons_env(env, add=''): - opath = path.dirname(path.abspath('$TARGET')) - lstr = 'thrift --gen cpp -o ' + opath + ' ' + add + ' $SOURCE' - cppbuild = Builder(action = lstr) - env.Append(BUILDERS = {'ThriftCpp' : cppbuild}) - -def gen_cpp(env, dir, file): - scons_env(env) - suffixes = ['_types.h', '_types.cpp'] - targets = map(lambda s: 'gen-cpp/' + file + s, suffixes) - return env.ThriftCpp(targets, dir+file+'.thrift') diff --git a/module/lib/thrift/TSerialization.py b/module/lib/thrift/TSerialization.py deleted file mode 100644 index b19f98aa8..000000000 --- a/module/lib/thrift/TSerialization.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from protocol import TBinaryProtocol -from transport import TTransport - -def serialize(thrift_object, protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()): - transport = TTransport.TMemoryBuffer() - protocol = protocol_factory.getProtocol(transport) - thrift_object.write(protocol) - return transport.getvalue() - -def deserialize(base, buf, protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()): - transport = TTransport.TMemoryBuffer(buf) - protocol = protocol_factory.getProtocol(transport) - base.read(protocol) - return base - diff --git a/module/lib/thrift/Thrift.py b/module/lib/thrift/Thrift.py deleted file mode 100644 index 1d271fcff..000000000 --- a/module/lib/thrift/Thrift.py +++ /dev/null @@ -1,154 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import sys - -class TType: - STOP = 0 - VOID = 1 - BOOL = 2 - BYTE = 3 - I08 = 3 - DOUBLE = 4 - I16 = 6 - I32 = 8 - I64 = 10 - STRING = 11 - UTF7 = 11 - STRUCT = 12 - MAP = 13 - SET = 14 - LIST = 15 - UTF8 = 16 - UTF16 = 17 - - _VALUES_TO_NAMES = ( 'STOP', - 'VOID', - 'BOOL', - 'BYTE', - 'DOUBLE', - None, - 'I16', - None, - 'I32', - None, - 'I64', - 'STRING', - 'STRUCT', - 'MAP', - 'SET', - 'LIST', - 'UTF8', - 'UTF16' ) - -class TMessageType: - CALL = 1 - REPLY = 2 - EXCEPTION = 3 - ONEWAY = 4 - -class TProcessor: - - """Base class for procsessor, which works on two streams.""" - - def process(iprot, oprot): - pass - -class TException(Exception): - - """Base class for all thrift exceptions.""" - - # BaseException.message is deprecated in Python v[2.6,3.0) - if (2,6,0) <= sys.version_info < (3,0): - def _get_message(self): - return self._message - def _set_message(self, message): - self._message = message - message = property(_get_message, _set_message) - - def __init__(self, message=None): - Exception.__init__(self, message) - self.message = message - -class TApplicationException(TException): - - """Application level thrift exceptions.""" - - UNKNOWN = 0 - UNKNOWN_METHOD = 1 - INVALID_MESSAGE_TYPE = 2 - WRONG_METHOD_NAME = 3 - BAD_SEQUENCE_ID = 4 - MISSING_RESULT = 5 - INTERNAL_ERROR = 6 - PROTOCOL_ERROR = 7 - - def __init__(self, type=UNKNOWN, message=None): - TException.__init__(self, message) - self.type = type - - def __str__(self): - if self.message: - return self.message - elif self.type == self.UNKNOWN_METHOD: - return 'Unknown method' - elif self.type == self.INVALID_MESSAGE_TYPE: - return 'Invalid message type' - elif self.type == self.WRONG_METHOD_NAME: - return 'Wrong method name' - elif self.type == self.BAD_SEQUENCE_ID: - return 'Bad sequence ID' - elif self.type == self.MISSING_RESULT: - return 'Missing result' - else: - return 'Default (unknown) TApplicationException' - - def read(self, iprot): - iprot.readStructBegin() - while True: - (fname, ftype, fid) = iprot.readFieldBegin() - if ftype == TType.STOP: - break - if fid == 1: - if ftype == TType.STRING: - self.message = iprot.readString(); - else: - iprot.skip(ftype) - elif fid == 2: - if ftype == TType.I32: - self.type = iprot.readI32(); - else: - iprot.skip(ftype) - else: - iprot.skip(ftype) - iprot.readFieldEnd() - iprot.readStructEnd() - - def write(self, oprot): - oprot.writeStructBegin('TApplicationException') - if self.message != None: - oprot.writeFieldBegin('message', TType.STRING, 1) - oprot.writeString(self.message) - oprot.writeFieldEnd() - if self.type != None: - oprot.writeFieldBegin('type', TType.I32, 2) - oprot.writeI32(self.type) - oprot.writeFieldEnd() - oprot.writeFieldStop() - oprot.writeStructEnd() diff --git a/module/lib/thrift/__init__.py b/module/lib/thrift/__init__.py deleted file mode 100644 index 48d659c40..000000000 --- a/module/lib/thrift/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -__all__ = ['Thrift', 'TSCons'] diff --git a/module/lib/thrift/protocol/TBase.py b/module/lib/thrift/protocol/TBase.py deleted file mode 100644 index e675c7dc0..000000000 --- a/module/lib/thrift/protocol/TBase.py +++ /dev/null @@ -1,72 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from thrift.Thrift import * -from thrift.protocol import TBinaryProtocol -from thrift.transport import TTransport - -try: - from thrift.protocol import fastbinary -except: - fastbinary = None - -class TBase(object): - __slots__ = [] - - def __repr__(self): - L = ['%s=%r' % (key, getattr(self, key)) - for key in self.__slots__ ] - return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return False - for attr in self.__slots__: - my_val = getattr(self, attr) - other_val = getattr(other, attr) - if my_val != other_val: - return False - return True - - def __ne__(self, other): - return not (self == other) - - def read(self, iprot): - if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None: - fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec)) - return - iprot.readStruct(self, self.thrift_spec) - - def write(self, oprot): - if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None: - oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec))) - return - oprot.writeStruct(self, self.thrift_spec) - -class TExceptionBase(Exception): - # old style class so python2.4 can raise exceptions derived from this - # This can't inherit from TBase because of that limitation. - __slots__ = [] - - __repr__ = TBase.__repr__.im_func - __eq__ = TBase.__eq__.im_func - __ne__ = TBase.__ne__.im_func - read = TBase.read.im_func - write = TBase.write.im_func - diff --git a/module/lib/thrift/protocol/TBinaryProtocol.py b/module/lib/thrift/protocol/TBinaryProtocol.py deleted file mode 100644 index 50c6aa896..000000000 --- a/module/lib/thrift/protocol/TBinaryProtocol.py +++ /dev/null @@ -1,259 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from TProtocol import * -from struct import pack, unpack - -class TBinaryProtocol(TProtocolBase): - - """Binary implementation of the Thrift protocol driver.""" - - # NastyHaxx. Python 2.4+ on 32-bit machines forces hex constants to be - # positive, converting this into a long. If we hardcode the int value - # instead it'll stay in 32 bit-land. - - # VERSION_MASK = 0xffff0000 - VERSION_MASK = -65536 - - # VERSION_1 = 0x80010000 - VERSION_1 = -2147418112 - - TYPE_MASK = 0x000000ff - - def __init__(self, trans, strictRead=False, strictWrite=True): - TProtocolBase.__init__(self, trans) - self.strictRead = strictRead - self.strictWrite = strictWrite - - def writeMessageBegin(self, name, type, seqid): - if self.strictWrite: - self.writeI32(TBinaryProtocol.VERSION_1 | type) - self.writeString(name) - self.writeI32(seqid) - else: - self.writeString(name) - self.writeByte(type) - self.writeI32(seqid) - - def writeMessageEnd(self): - pass - - def writeStructBegin(self, name): - pass - - def writeStructEnd(self): - pass - - def writeFieldBegin(self, name, type, id): - self.writeByte(type) - self.writeI16(id) - - def writeFieldEnd(self): - pass - - def writeFieldStop(self): - self.writeByte(TType.STOP); - - def writeMapBegin(self, ktype, vtype, size): - self.writeByte(ktype) - self.writeByte(vtype) - self.writeI32(size) - - def writeMapEnd(self): - pass - - def writeListBegin(self, etype, size): - self.writeByte(etype) - self.writeI32(size) - - def writeListEnd(self): - pass - - def writeSetBegin(self, etype, size): - self.writeByte(etype) - self.writeI32(size) - - def writeSetEnd(self): - pass - - def writeBool(self, bool): - if bool: - self.writeByte(1) - else: - self.writeByte(0) - - def writeByte(self, byte): - buff = pack("!b", byte) - self.trans.write(buff) - - def writeI16(self, i16): - buff = pack("!h", i16) - self.trans.write(buff) - - def writeI32(self, i32): - buff = pack("!i", i32) - self.trans.write(buff) - - def writeI64(self, i64): - buff = pack("!q", i64) - self.trans.write(buff) - - def writeDouble(self, dub): - buff = pack("!d", dub) - self.trans.write(buff) - - def writeString(self, str): - self.writeI32(len(str)) - self.trans.write(str) - - def readMessageBegin(self): - sz = self.readI32() - if sz < 0: - version = sz & TBinaryProtocol.VERSION_MASK - if version != TBinaryProtocol.VERSION_1: - raise TProtocolException(type=TProtocolException.BAD_VERSION, message='Bad version in readMessageBegin: %d' % (sz)) - type = sz & TBinaryProtocol.TYPE_MASK - name = self.readString() - seqid = self.readI32() - else: - if self.strictRead: - raise TProtocolException(type=TProtocolException.BAD_VERSION, message='No protocol version header') - name = self.trans.readAll(sz) - type = self.readByte() - seqid = self.readI32() - return (name, type, seqid) - - def readMessageEnd(self): - pass - - def readStructBegin(self): - pass - - def readStructEnd(self): - pass - - def readFieldBegin(self): - type = self.readByte() - if type == TType.STOP: - return (None, type, 0) - id = self.readI16() - return (None, type, id) - - def readFieldEnd(self): - pass - - def readMapBegin(self): - ktype = self.readByte() - vtype = self.readByte() - size = self.readI32() - return (ktype, vtype, size) - - def readMapEnd(self): - pass - - def readListBegin(self): - etype = self.readByte() - size = self.readI32() - return (etype, size) - - def readListEnd(self): - pass - - def readSetBegin(self): - etype = self.readByte() - size = self.readI32() - return (etype, size) - - def readSetEnd(self): - pass - - def readBool(self): - byte = self.readByte() - if byte == 0: - return False - return True - - def readByte(self): - buff = self.trans.readAll(1) - val, = unpack('!b', buff) - return val - - def readI16(self): - buff = self.trans.readAll(2) - val, = unpack('!h', buff) - return val - - def readI32(self): - buff = self.trans.readAll(4) - val, = unpack('!i', buff) - return val - - def readI64(self): - buff = self.trans.readAll(8) - val, = unpack('!q', buff) - return val - - def readDouble(self): - buff = self.trans.readAll(8) - val, = unpack('!d', buff) - return val - - def readString(self): - len = self.readI32() - str = self.trans.readAll(len) - return str - - -class TBinaryProtocolFactory: - def __init__(self, strictRead=False, strictWrite=True): - self.strictRead = strictRead - self.strictWrite = strictWrite - - def getProtocol(self, trans): - prot = TBinaryProtocol(trans, self.strictRead, self.strictWrite) - return prot - - -class TBinaryProtocolAccelerated(TBinaryProtocol): - - """C-Accelerated version of TBinaryProtocol. - - This class does not override any of TBinaryProtocol's methods, - but the generated code recognizes it directly and will call into - our C module to do the encoding, bypassing this object entirely. - We inherit from TBinaryProtocol so that the normal TBinaryProtocol - encoding can happen if the fastbinary module doesn't work for some - reason. (TODO(dreiss): Make this happen sanely in more cases.) - - In order to take advantage of the C module, just use - TBinaryProtocolAccelerated instead of TBinaryProtocol. - - NOTE: This code was contributed by an external developer. - The internal Thrift team has reviewed and tested it, - but we cannot guarantee that it is production-ready. - Please feel free to report bugs and/or success stories - to the public mailing list. - """ - - pass - - -class TBinaryProtocolAcceleratedFactory: - def getProtocol(self, trans): - return TBinaryProtocolAccelerated(trans) diff --git a/module/lib/thrift/protocol/TCompactProtocol.py b/module/lib/thrift/protocol/TCompactProtocol.py deleted file mode 100644 index 016a33171..000000000 --- a/module/lib/thrift/protocol/TCompactProtocol.py +++ /dev/null @@ -1,395 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from TProtocol import * -from struct import pack, unpack - -__all__ = ['TCompactProtocol', 'TCompactProtocolFactory'] - -CLEAR = 0 -FIELD_WRITE = 1 -VALUE_WRITE = 2 -CONTAINER_WRITE = 3 -BOOL_WRITE = 4 -FIELD_READ = 5 -CONTAINER_READ = 6 -VALUE_READ = 7 -BOOL_READ = 8 - -def make_helper(v_from, container): - def helper(func): - def nested(self, *args, **kwargs): - assert self.state in (v_from, container), (self.state, v_from, container) - return func(self, *args, **kwargs) - return nested - return helper -writer = make_helper(VALUE_WRITE, CONTAINER_WRITE) -reader = make_helper(VALUE_READ, CONTAINER_READ) - -def makeZigZag(n, bits): - return (n << 1) ^ (n >> (bits - 1)) - -def fromZigZag(n): - return (n >> 1) ^ -(n & 1) - -def writeVarint(trans, n): - out = [] - while True: - if n & ~0x7f == 0: - out.append(n) - break - else: - out.append((n & 0xff) | 0x80) - n = n >> 7 - trans.write(''.join(map(chr, out))) - -def readVarint(trans): - result = 0 - shift = 0 - while True: - x = trans.readAll(1) - byte = ord(x) - result |= (byte & 0x7f) << shift - if byte >> 7 == 0: - return result - shift += 7 - -class CompactType: - STOP = 0x00 - TRUE = 0x01 - FALSE = 0x02 - BYTE = 0x03 - I16 = 0x04 - I32 = 0x05 - I64 = 0x06 - DOUBLE = 0x07 - BINARY = 0x08 - LIST = 0x09 - SET = 0x0A - MAP = 0x0B - STRUCT = 0x0C - -CTYPES = {TType.STOP: CompactType.STOP, - TType.BOOL: CompactType.TRUE, # used for collection - TType.BYTE: CompactType.BYTE, - TType.I16: CompactType.I16, - TType.I32: CompactType.I32, - TType.I64: CompactType.I64, - TType.DOUBLE: CompactType.DOUBLE, - TType.STRING: CompactType.BINARY, - TType.STRUCT: CompactType.STRUCT, - TType.LIST: CompactType.LIST, - TType.SET: CompactType.SET, - TType.MAP: CompactType.MAP - } - -TTYPES = {} -for k, v in CTYPES.items(): - TTYPES[v] = k -TTYPES[CompactType.FALSE] = TType.BOOL -del k -del v - -class TCompactProtocol(TProtocolBase): - "Compact implementation of the Thrift protocol driver." - - PROTOCOL_ID = 0x82 - VERSION = 1 - VERSION_MASK = 0x1f - TYPE_MASK = 0xe0 - TYPE_SHIFT_AMOUNT = 5 - - def __init__(self, trans): - TProtocolBase.__init__(self, trans) - self.state = CLEAR - self.__last_fid = 0 - self.__bool_fid = None - self.__bool_value = None - self.__structs = [] - self.__containers = [] - - def __writeVarint(self, n): - writeVarint(self.trans, n) - - def writeMessageBegin(self, name, type, seqid): - assert self.state == CLEAR - self.__writeUByte(self.PROTOCOL_ID) - self.__writeUByte(self.VERSION | (type << self.TYPE_SHIFT_AMOUNT)) - self.__writeVarint(seqid) - self.__writeString(name) - self.state = VALUE_WRITE - - def writeMessageEnd(self): - assert self.state == VALUE_WRITE - self.state = CLEAR - - def writeStructBegin(self, name): - assert self.state in (CLEAR, CONTAINER_WRITE, VALUE_WRITE), self.state - self.__structs.append((self.state, self.__last_fid)) - self.state = FIELD_WRITE - self.__last_fid = 0 - - def writeStructEnd(self): - assert self.state == FIELD_WRITE - self.state, self.__last_fid = self.__structs.pop() - - def writeFieldStop(self): - self.__writeByte(0) - - def __writeFieldHeader(self, type, fid): - delta = fid - self.__last_fid - if 0 < delta <= 15: - self.__writeUByte(delta << 4 | type) - else: - self.__writeByte(type) - self.__writeI16(fid) - self.__last_fid = fid - - def writeFieldBegin(self, name, type, fid): - assert self.state == FIELD_WRITE, self.state - if type == TType.BOOL: - self.state = BOOL_WRITE - self.__bool_fid = fid - else: - self.state = VALUE_WRITE - self.__writeFieldHeader(CTYPES[type], fid) - - def writeFieldEnd(self): - assert self.state in (VALUE_WRITE, BOOL_WRITE), self.state - self.state = FIELD_WRITE - - def __writeUByte(self, byte): - self.trans.write(pack('!B', byte)) - - def __writeByte(self, byte): - self.trans.write(pack('!b', byte)) - - def __writeI16(self, i16): - self.__writeVarint(makeZigZag(i16, 16)) - - def __writeSize(self, i32): - self.__writeVarint(i32) - - def writeCollectionBegin(self, etype, size): - assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state - if size <= 14: - self.__writeUByte(size << 4 | CTYPES[etype]) - else: - self.__writeUByte(0xf0 | CTYPES[etype]) - self.__writeSize(size) - self.__containers.append(self.state) - self.state = CONTAINER_WRITE - writeSetBegin = writeCollectionBegin - writeListBegin = writeCollectionBegin - - def writeMapBegin(self, ktype, vtype, size): - assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state - if size == 0: - self.__writeByte(0) - else: - self.__writeSize(size) - self.__writeUByte(CTYPES[ktype] << 4 | CTYPES[vtype]) - self.__containers.append(self.state) - self.state = CONTAINER_WRITE - - def writeCollectionEnd(self): - assert self.state == CONTAINER_WRITE, self.state - self.state = self.__containers.pop() - writeMapEnd = writeCollectionEnd - writeSetEnd = writeCollectionEnd - writeListEnd = writeCollectionEnd - - def writeBool(self, bool): - if self.state == BOOL_WRITE: - if bool: - ctype = CompactType.TRUE - else: - ctype = CompactType.FALSE - self.__writeFieldHeader(ctype, self.__bool_fid) - elif self.state == CONTAINER_WRITE: - if bool: - self.__writeByte(CompactType.TRUE) - else: - self.__writeByte(CompactType.FALSE) - else: - raise AssertionError, "Invalid state in compact protocol" - - writeByte = writer(__writeByte) - writeI16 = writer(__writeI16) - - @writer - def writeI32(self, i32): - self.__writeVarint(makeZigZag(i32, 32)) - - @writer - def writeI64(self, i64): - self.__writeVarint(makeZigZag(i64, 64)) - - @writer - def writeDouble(self, dub): - self.trans.write(pack('!d', dub)) - - def __writeString(self, s): - self.__writeSize(len(s)) - self.trans.write(s) - writeString = writer(__writeString) - - def readFieldBegin(self): - assert self.state == FIELD_READ, self.state - type = self.__readUByte() - if type & 0x0f == TType.STOP: - return (None, 0, 0) - delta = type >> 4 - if delta == 0: - fid = self.__readI16() - else: - fid = self.__last_fid + delta - self.__last_fid = fid - type = type & 0x0f - if type == CompactType.TRUE: - self.state = BOOL_READ - self.__bool_value = True - elif type == CompactType.FALSE: - self.state = BOOL_READ - self.__bool_value = False - else: - self.state = VALUE_READ - return (None, self.__getTType(type), fid) - - def readFieldEnd(self): - assert self.state in (VALUE_READ, BOOL_READ), self.state - self.state = FIELD_READ - - def __readUByte(self): - result, = unpack('!B', self.trans.readAll(1)) - return result - - def __readByte(self): - result, = unpack('!b', self.trans.readAll(1)) - return result - - def __readVarint(self): - return readVarint(self.trans) - - def __readZigZag(self): - return fromZigZag(self.__readVarint()) - - def __readSize(self): - result = self.__readVarint() - if result < 0: - raise TException("Length < 0") - return result - - def readMessageBegin(self): - assert self.state == CLEAR - proto_id = self.__readUByte() - if proto_id != self.PROTOCOL_ID: - raise TProtocolException(TProtocolException.BAD_VERSION, - 'Bad protocol id in the message: %d' % proto_id) - ver_type = self.__readUByte() - type = (ver_type & self.TYPE_MASK) >> self.TYPE_SHIFT_AMOUNT - version = ver_type & self.VERSION_MASK - if version != self.VERSION: - raise TProtocolException(TProtocolException.BAD_VERSION, - 'Bad version: %d (expect %d)' % (version, self.VERSION)) - seqid = self.__readVarint() - name = self.__readString() - return (name, type, seqid) - - def readMessageEnd(self): - assert self.state == CLEAR - assert len(self.__structs) == 0 - - def readStructBegin(self): - assert self.state in (CLEAR, CONTAINER_READ, VALUE_READ), self.state - self.__structs.append((self.state, self.__last_fid)) - self.state = FIELD_READ - self.__last_fid = 0 - - def readStructEnd(self): - assert self.state == FIELD_READ - self.state, self.__last_fid = self.__structs.pop() - - def readCollectionBegin(self): - assert self.state in (VALUE_READ, CONTAINER_READ), self.state - size_type = self.__readUByte() - size = size_type >> 4 - type = self.__getTType(size_type) - if size == 15: - size = self.__readSize() - self.__containers.append(self.state) - self.state = CONTAINER_READ - return type, size - readSetBegin = readCollectionBegin - readListBegin = readCollectionBegin - - def readMapBegin(self): - assert self.state in (VALUE_READ, CONTAINER_READ), self.state - size = self.__readSize() - types = 0 - if size > 0: - types = self.__readUByte() - vtype = self.__getTType(types) - ktype = self.__getTType(types >> 4) - self.__containers.append(self.state) - self.state = CONTAINER_READ - return (ktype, vtype, size) - - def readCollectionEnd(self): - assert self.state == CONTAINER_READ, self.state - self.state = self.__containers.pop() - readSetEnd = readCollectionEnd - readListEnd = readCollectionEnd - readMapEnd = readCollectionEnd - - def readBool(self): - if self.state == BOOL_READ: - return self.__bool_value == CompactType.TRUE - elif self.state == CONTAINER_READ: - return self.__readByte() == CompactType.TRUE - else: - raise AssertionError, "Invalid state in compact protocol: %d" % self.state - - readByte = reader(__readByte) - __readI16 = __readZigZag - readI16 = reader(__readZigZag) - readI32 = reader(__readZigZag) - readI64 = reader(__readZigZag) - - @reader - def readDouble(self): - buff = self.trans.readAll(8) - val, = unpack('!d', buff) - return val - - def __readString(self): - len = self.__readSize() - return self.trans.readAll(len) - readString = reader(__readString) - - def __getTType(self, byte): - return TTYPES[byte & 0x0f] - - -class TCompactProtocolFactory: - def __init__(self): - pass - - def getProtocol(self, trans): - return TCompactProtocol(trans) diff --git a/module/lib/thrift/protocol/TProtocol.py b/module/lib/thrift/protocol/TProtocol.py deleted file mode 100644 index 7338ff68a..000000000 --- a/module/lib/thrift/protocol/TProtocol.py +++ /dev/null @@ -1,404 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from thrift.Thrift import * - -class TProtocolException(TException): - - """Custom Protocol Exception class""" - - UNKNOWN = 0 - INVALID_DATA = 1 - NEGATIVE_SIZE = 2 - SIZE_LIMIT = 3 - BAD_VERSION = 4 - - def __init__(self, type=UNKNOWN, message=None): - TException.__init__(self, message) - self.type = type - -class TProtocolBase: - - """Base class for Thrift protocol driver.""" - - def __init__(self, trans): - self.trans = trans - - def writeMessageBegin(self, name, type, seqid): - pass - - def writeMessageEnd(self): - pass - - def writeStructBegin(self, name): - pass - - def writeStructEnd(self): - pass - - def writeFieldBegin(self, name, type, id): - pass - - def writeFieldEnd(self): - pass - - def writeFieldStop(self): - pass - - def writeMapBegin(self, ktype, vtype, size): - pass - - def writeMapEnd(self): - pass - - def writeListBegin(self, etype, size): - pass - - def writeListEnd(self): - pass - - def writeSetBegin(self, etype, size): - pass - - def writeSetEnd(self): - pass - - def writeBool(self, bool): - pass - - def writeByte(self, byte): - pass - - def writeI16(self, i16): - pass - - def writeI32(self, i32): - pass - - def writeI64(self, i64): - pass - - def writeDouble(self, dub): - pass - - def writeString(self, str): - pass - - def readMessageBegin(self): - pass - - def readMessageEnd(self): - pass - - def readStructBegin(self): - pass - - def readStructEnd(self): - pass - - def readFieldBegin(self): - pass - - def readFieldEnd(self): - pass - - def readMapBegin(self): - pass - - def readMapEnd(self): - pass - - def readListBegin(self): - pass - - def readListEnd(self): - pass - - def readSetBegin(self): - pass - - def readSetEnd(self): - pass - - def readBool(self): - pass - - def readByte(self): - pass - - def readI16(self): - pass - - def readI32(self): - pass - - def readI64(self): - pass - - def readDouble(self): - pass - - def readString(self): - pass - - def skip(self, type): - if type == TType.STOP: - return - elif type == TType.BOOL: - self.readBool() - elif type == TType.BYTE: - self.readByte() - elif type == TType.I16: - self.readI16() - elif type == TType.I32: - self.readI32() - elif type == TType.I64: - self.readI64() - elif type == TType.DOUBLE: - self.readDouble() - elif type == TType.STRING: - self.readString() - elif type == TType.STRUCT: - name = self.readStructBegin() - while True: - (name, type, id) = self.readFieldBegin() - if type == TType.STOP: - break - self.skip(type) - self.readFieldEnd() - self.readStructEnd() - elif type == TType.MAP: - (ktype, vtype, size) = self.readMapBegin() - for i in range(size): - self.skip(ktype) - self.skip(vtype) - self.readMapEnd() - elif type == TType.SET: - (etype, size) = self.readSetBegin() - for i in range(size): - self.skip(etype) - self.readSetEnd() - elif type == TType.LIST: - (etype, size) = self.readListBegin() - for i in range(size): - self.skip(etype) - self.readListEnd() - - # tuple of: ( 'reader method' name, is_container boolean, 'writer_method' name ) - _TTYPE_HANDLERS = ( - (None, None, False), # 0 == TType,STOP - (None, None, False), # 1 == TType.VOID # TODO: handle void? - ('readBool', 'writeBool', False), # 2 == TType.BOOL - ('readByte', 'writeByte', False), # 3 == TType.BYTE and I08 - ('readDouble', 'writeDouble', False), # 4 == TType.DOUBLE - (None, None, False), # 5, undefined - ('readI16', 'writeI16', False), # 6 == TType.I16 - (None, None, False), # 7, undefined - ('readI32', 'writeI32', False), # 8 == TType.I32 - (None, None, False), # 9, undefined - ('readI64', 'writeI64', False), # 10 == TType.I64 - ('readString', 'writeString', False), # 11 == TType.STRING and UTF7 - ('readContainerStruct', 'writeContainerStruct', True), # 12 == TType.STRUCT - ('readContainerMap', 'writeContainerMap', True), # 13 == TType.MAP - ('readContainerSet', 'writeContainerSet', True), # 14 == TType.SET - ('readContainerList', 'writeContainerList', True), # 15 == TType.LIST - (None, None, False), # 16 == TType.UTF8 # TODO: handle utf8 types? - (None, None, False)# 17 == TType.UTF16 # TODO: handle utf16 types? - ) - - def readFieldByTType(self, ttype, spec): - try: - (r_handler, w_handler, is_container) = self._TTYPE_HANDLERS[ttype] - except IndexError: - raise TProtocolException(type=TProtocolException.INVALID_DATA, - message='Invalid field type %d' % (ttype)) - if r_handler is None: - raise TProtocolException(type=TProtocolException.INVALID_DATA, - message='Invalid field type %d' % (ttype)) - reader = getattr(self, r_handler) - if not is_container: - return reader() - return reader(spec) - - def readContainerList(self, spec): - results = [] - ttype, tspec = spec[0], spec[1] - r_handler = self._TTYPE_HANDLERS[ttype][0] - reader = getattr(self, r_handler) - (list_type, list_len) = self.readListBegin() - if tspec is None: - # list values are simple types - for idx in xrange(list_len): - results.append(reader()) - else: - # this is like an inlined readFieldByTType - container_reader = self._TTYPE_HANDLERS[list_type][0] - val_reader = getattr(self, container_reader) - for idx in xrange(list_len): - val = val_reader(tspec) - results.append(val) - self.readListEnd() - return results - - def readContainerSet(self, spec): - results = set() - ttype, tspec = spec[0], spec[1] - r_handler = self._TTYPE_HANDLERS[ttype][0] - reader = getattr(self, r_handler) - (set_type, set_len) = self.readSetBegin() - if tspec is None: - # set members are simple types - for idx in xrange(set_len): - results.add(reader()) - else: - container_reader = self._TTYPE_HANDLERS[set_type][0] - val_reader = getattr(self, container_reader) - for idx in xrange(set_len): - results.add(val_reader(tspec)) - self.readSetEnd() - return results - - def readContainerStruct(self, spec): - (obj_class, obj_spec) = spec - obj = obj_class() - obj.read(self) - return obj - - def readContainerMap(self, spec): - results = dict() - key_ttype, key_spec = spec[0], spec[1] - val_ttype, val_spec = spec[2], spec[3] - (map_ktype, map_vtype, map_len) = self.readMapBegin() - # TODO: compare types we just decoded with thrift_spec and abort/skip if types disagree - key_reader = getattr(self, self._TTYPE_HANDLERS[key_ttype][0]) - val_reader = getattr(self, self._TTYPE_HANDLERS[val_ttype][0]) - # list values are simple types - for idx in xrange(map_len): - if key_spec is None: - k_val = key_reader() - else: - k_val = self.readFieldByTType(key_ttype, key_spec) - if val_spec is None: - v_val = val_reader() - else: - v_val = self.readFieldByTType(val_ttype, val_spec) - # this raises a TypeError with unhashable keys types. i.e. d=dict(); d[[0,1]] = 2 fails - results[k_val] = v_val - self.readMapEnd() - return results - - def readStruct(self, obj, thrift_spec): - self.readStructBegin() - while True: - (fname, ftype, fid) = self.readFieldBegin() - if ftype == TType.STOP: - break - try: - field = thrift_spec[fid] - except IndexError: - self.skip(ftype) - else: - if field is not None and ftype == field[1]: - fname = field[2] - fspec = field[3] - val = self.readFieldByTType(ftype, fspec) - setattr(obj, fname, val) - else: - self.skip(ftype) - self.readFieldEnd() - self.readStructEnd() - - def writeContainerStruct(self, val, spec): - val.write(self) - - def writeContainerList(self, val, spec): - self.writeListBegin(spec[0], len(val)) - r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]] - e_writer = getattr(self, w_handler) - if not is_container: - for elem in val: - e_writer(elem) - else: - for elem in val: - e_writer(elem, spec[1]) - self.writeListEnd() - - def writeContainerSet(self, val, spec): - self.writeSetBegin(spec[0], len(val)) - r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]] - e_writer = getattr(self, w_handler) - if not is_container: - for elem in val: - e_writer(elem) - else: - for elem in val: - e_writer(elem, spec[1]) - self.writeSetEnd() - - def writeContainerMap(self, val, spec): - k_type = spec[0] - v_type = spec[2] - ignore, ktype_name, k_is_container = self._TTYPE_HANDLERS[k_type] - ignore, vtype_name, v_is_container = self._TTYPE_HANDLERS[v_type] - k_writer = getattr(self, ktype_name) - v_writer = getattr(self, vtype_name) - self.writeMapBegin(k_type, v_type, len(val)) - for m_key, m_val in val.iteritems(): - if not k_is_container: - k_writer(m_key) - else: - k_writer(m_key, spec[1]) - if not v_is_container: - v_writer(m_val) - else: - v_writer(m_val, spec[3]) - self.writeMapEnd() - - def writeStruct(self, obj, thrift_spec): - self.writeStructBegin(obj.__class__.__name__) - for field in thrift_spec: - if field is None: - continue - fname = field[2] - val = getattr(obj, fname) - if val is None: - # skip writing out unset fields - continue - fid = field[0] - ftype = field[1] - fspec = field[3] - # get the writer method for this value - self.writeFieldBegin(fname, ftype, fid) - self.writeFieldByTType(ftype, val, fspec) - self.writeFieldEnd() - self.writeFieldStop() - self.writeStructEnd() - - def writeFieldByTType(self, ttype, val, spec): - r_handler, w_handler, is_container = self._TTYPE_HANDLERS[ttype] - writer = getattr(self, w_handler) - if is_container: - writer(val, spec) - else: - writer(val) - -class TProtocolFactory: - def getProtocol(self, trans): - pass - diff --git a/module/lib/thrift/protocol/__init__.py b/module/lib/thrift/protocol/__init__.py deleted file mode 100644 index d53359b28..000000000 --- a/module/lib/thrift/protocol/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -__all__ = ['TProtocol', 'TBinaryProtocol', 'fastbinary', 'TBase'] diff --git a/module/lib/thrift/server/THttpServer.py b/module/lib/thrift/server/THttpServer.py deleted file mode 100644 index 3047d9c00..000000000 --- a/module/lib/thrift/server/THttpServer.py +++ /dev/null @@ -1,82 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import BaseHTTPServer - -from thrift.server import TServer -from thrift.transport import TTransport - -class ResponseException(Exception): - """Allows handlers to override the HTTP response - - Normally, THttpServer always sends a 200 response. If a handler wants - to override this behavior (e.g., to simulate a misconfigured or - overloaded web server during testing), it can raise a ResponseException. - The function passed to the constructor will be called with the - RequestHandler as its only argument. - """ - def __init__(self, handler): - self.handler = handler - - -class THttpServer(TServer.TServer): - """A simple HTTP-based Thrift server - - This class is not very performant, but it is useful (for example) for - acting as a mock version of an Apache-based PHP Thrift endpoint.""" - - def __init__(self, processor, server_address, - inputProtocolFactory, outputProtocolFactory = None, - server_class = BaseHTTPServer.HTTPServer): - """Set up protocol factories and HTTP server. - - See BaseHTTPServer for server_address. - See TServer for protocol factories.""" - - if outputProtocolFactory is None: - outputProtocolFactory = inputProtocolFactory - - TServer.TServer.__init__(self, processor, None, None, None, - inputProtocolFactory, outputProtocolFactory) - - thttpserver = self - - class RequestHander(BaseHTTPServer.BaseHTTPRequestHandler): - def do_POST(self): - # Don't care about the request path. - itrans = TTransport.TFileObjectTransport(self.rfile) - otrans = TTransport.TFileObjectTransport(self.wfile) - itrans = TTransport.TBufferedTransport(itrans, int(self.headers['Content-Length'])) - otrans = TTransport.TMemoryBuffer() - iprot = thttpserver.inputProtocolFactory.getProtocol(itrans) - oprot = thttpserver.outputProtocolFactory.getProtocol(otrans) - try: - thttpserver.processor.process(iprot, oprot) - except ResponseException, exn: - exn.handler(self) - else: - self.send_response(200) - self.send_header("content-type", "application/x-thrift") - self.end_headers() - self.wfile.write(otrans.getvalue()) - - self.httpd = server_class(server_address, RequestHander) - - def serve(self): - self.httpd.serve_forever() diff --git a/module/lib/thrift/server/TNonblockingServer.py b/module/lib/thrift/server/TNonblockingServer.py deleted file mode 100644 index ea348a0b6..000000000 --- a/module/lib/thrift/server/TNonblockingServer.py +++ /dev/null @@ -1,310 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -"""Implementation of non-blocking server. - -The main idea of the server is reciving and sending requests -only from main thread. - -It also makes thread pool server in tasks terms, not connections. -""" -import threading -import socket -import Queue -import select -import struct -import logging - -from thrift.transport import TTransport -from thrift.protocol.TBinaryProtocol import TBinaryProtocolFactory - -__all__ = ['TNonblockingServer'] - -class Worker(threading.Thread): - """Worker is a small helper to process incoming connection.""" - def __init__(self, queue): - threading.Thread.__init__(self) - self.queue = queue - - def run(self): - """Process queries from task queue, stop if processor is None.""" - while True: - try: - processor, iprot, oprot, otrans, callback = self.queue.get() - if processor is None: - break - processor.process(iprot, oprot) - callback(True, otrans.getvalue()) - except Exception: - logging.exception("Exception while processing request") - callback(False, '') - -WAIT_LEN = 0 -WAIT_MESSAGE = 1 -WAIT_PROCESS = 2 -SEND_ANSWER = 3 -CLOSED = 4 - -def locked(func): - "Decorator which locks self.lock." - def nested(self, *args, **kwargs): - self.lock.acquire() - try: - return func(self, *args, **kwargs) - finally: - self.lock.release() - return nested - -def socket_exception(func): - "Decorator close object on socket.error." - def read(self, *args, **kwargs): - try: - return func(self, *args, **kwargs) - except socket.error: - self.close() - return read - -class Connection: - """Basic class is represented connection. - - It can be in state: - WAIT_LEN --- connection is reading request len. - WAIT_MESSAGE --- connection is reading request. - WAIT_PROCESS --- connection has just read whole request and - waits for call ready routine. - SEND_ANSWER --- connection is sending answer string (including length - of answer). - CLOSED --- socket was closed and connection should be deleted. - """ - def __init__(self, new_socket, wake_up): - self.socket = new_socket - self.socket.setblocking(False) - self.status = WAIT_LEN - self.len = 0 - self.message = '' - self.lock = threading.Lock() - self.wake_up = wake_up - - def _read_len(self): - """Reads length of request. - - It's really paranoic routine and it may be replaced by - self.socket.recv(4).""" - read = self.socket.recv(4 - len(self.message)) - if len(read) == 0: - # if we read 0 bytes and self.message is empty, it means client close - # connection - if len(self.message) != 0: - logging.error("can't read frame size from socket") - self.close() - return - self.message += read - if len(self.message) == 4: - self.len, = struct.unpack('!i', self.message) - if self.len < 0: - logging.error("negative frame size, it seems client"\ - " doesn't use FramedTransport") - self.close() - elif self.len == 0: - logging.error("empty frame, it's really strange") - self.close() - else: - self.message = '' - self.status = WAIT_MESSAGE - - @socket_exception - def read(self): - """Reads data from stream and switch state.""" - assert self.status in (WAIT_LEN, WAIT_MESSAGE) - if self.status == WAIT_LEN: - self._read_len() - # go back to the main loop here for simplicity instead of - # falling through, even though there is a good chance that - # the message is already available - elif self.status == WAIT_MESSAGE: - read = self.socket.recv(self.len - len(self.message)) - if len(read) == 0: - logging.error("can't read frame from socket (get %d of %d bytes)" % - (len(self.message), self.len)) - self.close() - return - self.message += read - if len(self.message) == self.len: - self.status = WAIT_PROCESS - - @socket_exception - def write(self): - """Writes data from socket and switch state.""" - assert self.status == SEND_ANSWER - sent = self.socket.send(self.message) - if sent == len(self.message): - self.status = WAIT_LEN - self.message = '' - self.len = 0 - else: - self.message = self.message[sent:] - - @locked - def ready(self, all_ok, message): - """Callback function for switching state and waking up main thread. - - This function is the only function witch can be called asynchronous. - - The ready can switch Connection to three states: - WAIT_LEN if request was oneway. - SEND_ANSWER if request was processed in normal way. - CLOSED if request throws unexpected exception. - - The one wakes up main thread. - """ - assert self.status == WAIT_PROCESS - if not all_ok: - self.close() - self.wake_up() - return - self.len = '' - if len(message) == 0: - # it was a oneway request, do not write answer - self.message = '' - self.status = WAIT_LEN - else: - self.message = struct.pack('!i', len(message)) + message - self.status = SEND_ANSWER - self.wake_up() - - @locked - def is_writeable(self): - "Returns True if connection should be added to write list of select." - return self.status == SEND_ANSWER - - # it's not necessary, but... - @locked - def is_readable(self): - "Returns True if connection should be added to read list of select." - return self.status in (WAIT_LEN, WAIT_MESSAGE) - - @locked - def is_closed(self): - "Returns True if connection is closed." - return self.status == CLOSED - - def fileno(self): - "Returns the file descriptor of the associated socket." - return self.socket.fileno() - - def close(self): - "Closes connection" - self.status = CLOSED - self.socket.close() - -class TNonblockingServer: - """Non-blocking server.""" - def __init__(self, processor, lsocket, inputProtocolFactory=None, - outputProtocolFactory=None, threads=10): - self.processor = processor - self.socket = lsocket - self.in_protocol = inputProtocolFactory or TBinaryProtocolFactory() - self.out_protocol = outputProtocolFactory or self.in_protocol - self.threads = int(threads) - self.clients = {} - self.tasks = Queue.Queue() - self._read, self._write = socket.socketpair() - self.prepared = False - - def setNumThreads(self, num): - """Set the number of worker threads that should be created.""" - # implement ThreadPool interface - assert not self.prepared, "You can't change number of threads for working server" - self.threads = num - - def prepare(self): - """Prepares server for serve requests.""" - self.socket.listen() - for _ in xrange(self.threads): - thread = Worker(self.tasks) - thread.setDaemon(True) - thread.start() - self.prepared = True - - def wake_up(self): - """Wake up main thread. - - The server usualy waits in select call in we should terminate one. - The simplest way is using socketpair. - - Select always wait to read from the first socket of socketpair. - - In this case, we can just write anything to the second socket from - socketpair.""" - self._write.send('1') - - def _select(self): - """Does select on open connections.""" - readable = [self.socket.handle.fileno(), self._read.fileno()] - writable = [] - for i, connection in self.clients.items(): - if connection.is_readable(): - readable.append(connection.fileno()) - if connection.is_writeable(): - writable.append(connection.fileno()) - if connection.is_closed(): - del self.clients[i] - return select.select(readable, writable, readable) - - def handle(self): - """Handle requests. - - WARNING! You must call prepare BEFORE calling handle. - """ - assert self.prepared, "You have to call prepare before handle" - rset, wset, xset = self._select() - for readable in rset: - if readable == self._read.fileno(): - # don't care i just need to clean readable flag - self._read.recv(1024) - elif readable == self.socket.handle.fileno(): - client = self.socket.accept().handle - self.clients[client.fileno()] = Connection(client, self.wake_up) - else: - connection = self.clients[readable] - connection.read() - if connection.status == WAIT_PROCESS: - itransport = TTransport.TMemoryBuffer(connection.message) - otransport = TTransport.TMemoryBuffer() - iprot = self.in_protocol.getProtocol(itransport) - oprot = self.out_protocol.getProtocol(otransport) - self.tasks.put([self.processor, iprot, oprot, - otransport, connection.ready]) - for writeable in wset: - self.clients[writeable].write() - for oob in xset: - self.clients[oob].close() - del self.clients[oob] - - def close(self): - """Closes the server.""" - for _ in xrange(self.threads): - self.tasks.put([None, None, None, None, None]) - self.socket.close() - self.prepared = False - - def serve(self): - """Serve forever.""" - self.prepare() - while True: - self.handle() diff --git a/module/lib/thrift/server/TProcessPoolServer.py b/module/lib/thrift/server/TProcessPoolServer.py deleted file mode 100644 index 7ed814a88..000000000 --- a/module/lib/thrift/server/TProcessPoolServer.py +++ /dev/null @@ -1,125 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -import logging -from multiprocessing import Process, Value, Condition, reduction - -from TServer import TServer -from thrift.transport.TTransport import TTransportException - -class TProcessPoolServer(TServer): - - """ - Server with a fixed size pool of worker subprocesses which service requests. - Note that if you need shared state between the handlers - it's up to you! - Written by Dvir Volk, doat.com - """ - - def __init__(self, * args): - TServer.__init__(self, *args) - self.numWorkers = 10 - self.workers = [] - self.isRunning = Value('b', False) - self.stopCondition = Condition() - self.postForkCallback = None - - def setPostForkCallback(self, callback): - if not callable(callback): - raise TypeError("This is not a callback!") - self.postForkCallback = callback - - def setNumWorkers(self, num): - """Set the number of worker threads that should be created""" - self.numWorkers = num - - def workerProcess(self): - """Loop around getting clients from the shared queue and process them.""" - - if self.postForkCallback: - self.postForkCallback() - - while self.isRunning.value == True: - try: - client = self.serverTransport.accept() - self.serveClient(client) - except (KeyboardInterrupt, SystemExit): - return 0 - except Exception, x: - logging.exception(x) - - def serveClient(self, client): - """Process input/output from a client for as long as possible""" - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - iprot = self.inputProtocolFactory.getProtocol(itrans) - oprot = self.outputProtocolFactory.getProtocol(otrans) - - try: - while True: - self.processor.process(iprot, oprot) - except TTransportException, tx: - pass - except Exception, x: - logging.exception(x) - - itrans.close() - otrans.close() - - - def serve(self): - """Start a fixed number of worker threads and put client into a queue""" - - #this is a shared state that can tell the workers to exit when set as false - self.isRunning.value = True - - #first bind and listen to the port - self.serverTransport.listen() - - #fork the children - for i in range(self.numWorkers): - try: - w = Process(target=self.workerProcess) - w.daemon = True - w.start() - self.workers.append(w) - except Exception, x: - logging.exception(x) - - #wait until the condition is set by stop() - - while True: - - self.stopCondition.acquire() - try: - self.stopCondition.wait() - break - except (SystemExit, KeyboardInterrupt): - break - except Exception, x: - logging.exception(x) - - self.isRunning.value = False - - def stop(self): - self.isRunning.value = False - self.stopCondition.acquire() - self.stopCondition.notify() - self.stopCondition.release() - diff --git a/module/lib/thrift/server/TServer.py b/module/lib/thrift/server/TServer.py deleted file mode 100644 index 8456e2d40..000000000 --- a/module/lib/thrift/server/TServer.py +++ /dev/null @@ -1,274 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import logging -import sys -import os -import traceback -import threading -import Queue - -from thrift.Thrift import TProcessor -from thrift.transport import TTransport -from thrift.protocol import TBinaryProtocol - -class TServer: - - """Base interface for a server, which must have a serve method.""" - - """ 3 constructors for all servers: - 1) (processor, serverTransport) - 2) (processor, serverTransport, transportFactory, protocolFactory) - 3) (processor, serverTransport, - inputTransportFactory, outputTransportFactory, - inputProtocolFactory, outputProtocolFactory)""" - def __init__(self, *args): - if (len(args) == 2): - self.__initArgs__(args[0], args[1], - TTransport.TTransportFactoryBase(), - TTransport.TTransportFactoryBase(), - TBinaryProtocol.TBinaryProtocolFactory(), - TBinaryProtocol.TBinaryProtocolFactory()) - elif (len(args) == 4): - self.__initArgs__(args[0], args[1], args[2], args[2], args[3], args[3]) - elif (len(args) == 6): - self.__initArgs__(args[0], args[1], args[2], args[3], args[4], args[5]) - - def __initArgs__(self, processor, serverTransport, - inputTransportFactory, outputTransportFactory, - inputProtocolFactory, outputProtocolFactory): - self.processor = processor - self.serverTransport = serverTransport - self.inputTransportFactory = inputTransportFactory - self.outputTransportFactory = outputTransportFactory - self.inputProtocolFactory = inputProtocolFactory - self.outputProtocolFactory = outputProtocolFactory - - def serve(self): - pass - -class TSimpleServer(TServer): - - """Simple single-threaded server that just pumps around one transport.""" - - def __init__(self, *args): - TServer.__init__(self, *args) - - def serve(self): - self.serverTransport.listen() - while True: - client = self.serverTransport.accept() - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - iprot = self.inputProtocolFactory.getProtocol(itrans) - oprot = self.outputProtocolFactory.getProtocol(otrans) - try: - while True: - self.processor.process(iprot, oprot) - except TTransport.TTransportException, tx: - pass - except Exception, x: - logging.exception(x) - - itrans.close() - otrans.close() - -class TThreadedServer(TServer): - - """Threaded server that spawns a new thread per each connection.""" - - def __init__(self, *args, **kwargs): - TServer.__init__(self, *args) - self.daemon = kwargs.get("daemon", False) - - def serve(self): - self.serverTransport.listen() - while True: - try: - client = self.serverTransport.accept() - t = threading.Thread(target = self.handle, args=(client,)) - t.setDaemon(self.daemon) - t.start() - except KeyboardInterrupt: - raise - except Exception, x: - logging.exception(x) - - def handle(self, client): - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - iprot = self.inputProtocolFactory.getProtocol(itrans) - oprot = self.outputProtocolFactory.getProtocol(otrans) - try: - while True: - self.processor.process(iprot, oprot) - except TTransport.TTransportException, tx: - pass - except Exception, x: - logging.exception(x) - - itrans.close() - otrans.close() - -class TThreadPoolServer(TServer): - - """Server with a fixed size pool of threads which service requests.""" - - def __init__(self, *args, **kwargs): - TServer.__init__(self, *args) - self.clients = Queue.Queue() - self.threads = 10 - self.daemon = kwargs.get("daemon", False) - - def setNumThreads(self, num): - """Set the number of worker threads that should be created""" - self.threads = num - - def serveThread(self): - """Loop around getting clients from the shared queue and process them.""" - while True: - try: - client = self.clients.get() - self.serveClient(client) - except Exception, x: - logging.exception(x) - - def serveClient(self, client): - """Process input/output from a client for as long as possible""" - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - iprot = self.inputProtocolFactory.getProtocol(itrans) - oprot = self.outputProtocolFactory.getProtocol(otrans) - try: - while True: - self.processor.process(iprot, oprot) - except TTransport.TTransportException, tx: - pass - except Exception, x: - logging.exception(x) - - itrans.close() - otrans.close() - - def serve(self): - """Start a fixed number of worker threads and put client into a queue""" - for i in range(self.threads): - try: - t = threading.Thread(target = self.serveThread) - t.setDaemon(self.daemon) - t.start() - except Exception, x: - logging.exception(x) - - # Pump the socket for clients - self.serverTransport.listen() - while True: - try: - client = self.serverTransport.accept() - self.clients.put(client) - except Exception, x: - logging.exception(x) - - -class TForkingServer(TServer): - - """A Thrift server that forks a new process for each request""" - """ - This is more scalable than the threaded server as it does not cause - GIL contention. - - Note that this has different semantics from the threading server. - Specifically, updates to shared variables will no longer be shared. - It will also not work on windows. - - This code is heavily inspired by SocketServer.ForkingMixIn in the - Python stdlib. - """ - - def __init__(self, *args): - TServer.__init__(self, *args) - self.children = [] - - def serve(self): - def try_close(file): - try: - file.close() - except IOError, e: - logging.warning(e, exc_info=True) - - - self.serverTransport.listen() - while True: - client = self.serverTransport.accept() - try: - pid = os.fork() - - if pid: # parent - # add before collect, otherwise you race w/ waitpid - self.children.append(pid) - self.collect_children() - - # Parent must close socket or the connection may not get - # closed promptly - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - try_close(itrans) - try_close(otrans) - else: - itrans = self.inputTransportFactory.getTransport(client) - otrans = self.outputTransportFactory.getTransport(client) - - iprot = self.inputProtocolFactory.getProtocol(itrans) - oprot = self.outputProtocolFactory.getProtocol(otrans) - - ecode = 0 - try: - try: - while True: - self.processor.process(iprot, oprot) - except TTransport.TTransportException, tx: - pass - except Exception, e: - logging.exception(e) - ecode = 1 - finally: - try_close(itrans) - try_close(otrans) - - os._exit(ecode) - - except TTransport.TTransportException, tx: - pass - except Exception, x: - logging.exception(x) - - - def collect_children(self): - while self.children: - try: - pid, status = os.waitpid(0, os.WNOHANG) - except os.error: - pid = None - - if pid: - self.children.remove(pid) - else: - break - - diff --git a/module/lib/thrift/server/__init__.py b/module/lib/thrift/server/__init__.py deleted file mode 100644 index 1bf6e254e..000000000 --- a/module/lib/thrift/server/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -__all__ = ['TServer', 'TNonblockingServer'] diff --git a/module/lib/thrift/transport/THttpClient.py b/module/lib/thrift/transport/THttpClient.py deleted file mode 100644 index 50269785c..000000000 --- a/module/lib/thrift/transport/THttpClient.py +++ /dev/null @@ -1,126 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from TTransport import * -from cStringIO import StringIO - -import urlparse -import httplib -import warnings -import socket - -class THttpClient(TTransportBase): - - """Http implementation of TTransport base.""" - - def __init__(self, uri_or_host, port=None, path=None): - """THttpClient supports two different types constructor parameters. - - THttpClient(host, port, path) - deprecated - THttpClient(uri) - - Only the second supports https.""" - - if port is not None: - warnings.warn("Please use the THttpClient('http://host:port/path') syntax", DeprecationWarning, stacklevel=2) - self.host = uri_or_host - self.port = port - assert path - self.path = path - self.scheme = 'http' - else: - parsed = urlparse.urlparse(uri_or_host) - self.scheme = parsed.scheme - assert self.scheme in ('http', 'https') - if self.scheme == 'http': - self.port = parsed.port or httplib.HTTP_PORT - elif self.scheme == 'https': - self.port = parsed.port or httplib.HTTPS_PORT - self.host = parsed.hostname - self.path = parsed.path - if parsed.query: - self.path += '?%s' % parsed.query - self.__wbuf = StringIO() - self.__http = None - self.__timeout = None - - def open(self): - if self.scheme == 'http': - self.__http = httplib.HTTP(self.host, self.port) - else: - self.__http = httplib.HTTPS(self.host, self.port) - - def close(self): - self.__http.close() - self.__http = None - - def isOpen(self): - return self.__http != None - - def setTimeout(self, ms): - if not hasattr(socket, 'getdefaulttimeout'): - raise NotImplementedError - - if ms is None: - self.__timeout = None - else: - self.__timeout = ms/1000.0 - - def read(self, sz): - return self.__http.file.read(sz) - - def write(self, buf): - self.__wbuf.write(buf) - - def __withTimeout(f): - def _f(*args, **kwargs): - orig_timeout = socket.getdefaulttimeout() - socket.setdefaulttimeout(args[0].__timeout) - result = f(*args, **kwargs) - socket.setdefaulttimeout(orig_timeout) - return result - return _f - - def flush(self): - if self.isOpen(): - self.close() - self.open(); - - # Pull data out of buffer - data = self.__wbuf.getvalue() - self.__wbuf = StringIO() - - # HTTP request - self.__http.putrequest('POST', self.path) - - # Write headers - self.__http.putheader('Host', self.host) - self.__http.putheader('Content-Type', 'application/x-thrift') - self.__http.putheader('Content-Length', str(len(data))) - self.__http.endheaders() - - # Write payload - self.__http.send(data) - - # Get reply to flush the request - self.code, self.message, self.headers = self.__http.getreply() - - # Decorate if we know how to timeout - if hasattr(socket, 'getdefaulttimeout'): - flush = __withTimeout(flush) diff --git a/module/lib/thrift/transport/TSocket.py b/module/lib/thrift/transport/TSocket.py deleted file mode 100644 index 4e0e1874f..000000000 --- a/module/lib/thrift/transport/TSocket.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from TTransport import * -import os -import errno -import socket -import sys - -class TSocketBase(TTransportBase): - def _resolveAddr(self): - if self._unix_socket is not None: - return [(socket.AF_UNIX, socket.SOCK_STREAM, None, None, self._unix_socket)] - else: - return socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE | socket.AI_ADDRCONFIG) - - def close(self): - if self.handle: - self.handle.close() - self.handle = None - -class TSocket(TSocketBase): - """Socket implementation of TTransport base.""" - - def __init__(self, host='localhost', port=9090, unix_socket=None): - """Initialize a TSocket - - @param host(str) The host to connect to. - @param port(int) The (TCP) port to connect to. - @param unix_socket(str) The filename of a unix socket to connect to. - (host and port will be ignored.) - """ - - self.host = host - self.port = port - self.handle = None - self._unix_socket = unix_socket - self._timeout = None - - def setHandle(self, h): - self.handle = h - - def isOpen(self): - return self.handle is not None - - def setTimeout(self, ms): - if ms is None: - self._timeout = None - else: - self._timeout = ms/1000.0 - - if self.handle is not None: - self.handle.settimeout(self._timeout) - - def open(self): - try: - res0 = self._resolveAddr() - for res in res0: - self.handle = socket.socket(res[0], res[1]) - self.handle.settimeout(self._timeout) - try: - self.handle.connect(res[4]) - except socket.error, e: - if res is not res0[-1]: - continue - else: - raise e - break - except socket.error, e: - if self._unix_socket: - message = 'Could not connect to socket %s' % self._unix_socket - else: - message = 'Could not connect to %s:%d' % (self.host, self.port) - raise TTransportException(type=TTransportException.NOT_OPEN, message=message) - - def read(self, sz): - try: - buff = self.handle.recv(sz) - except socket.error, e: - if (e.args[0] == errno.ECONNRESET and - (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))): - # freebsd and Mach don't follow POSIX semantic of recv - # and fail with ECONNRESET if peer performed shutdown. - # See corresponding comment and code in TSocket::read() - # in lib/cpp/src/transport/TSocket.cpp. - self.close() - # Trigger the check to raise the END_OF_FILE exception below. - buff = '' - else: - raise - if len(buff) == 0: - raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes') - return buff - - def write(self, buff): - if not self.handle: - raise TTransportException(type=TTransportException.NOT_OPEN, message='Transport not open') - sent = 0 - have = len(buff) - while sent < have: - plus = self.handle.send(buff) - if plus == 0: - raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket sent 0 bytes') - sent += plus - buff = buff[plus:] - - def flush(self): - pass - -class TServerSocket(TSocketBase, TServerTransportBase): - """Socket implementation of TServerTransport base.""" - - def __init__(self, host=None, port=9090, unix_socket=None): - self.host = host - self.port = port - self._unix_socket = unix_socket - self.handle = None - - def listen(self): - res0 = self._resolveAddr() - for res in res0: - if res[0] is socket.AF_INET6 or res is res0[-1]: - break - - # We need remove the old unix socket if the file exists and - # nobody is listening on it. - if self._unix_socket: - tmp = socket.socket(res[0], res[1]) - try: - tmp.connect(res[4]) - except socket.error, err: - eno, message = err.args - if eno == errno.ECONNREFUSED: - os.unlink(res[4]) - - self.handle = socket.socket(res[0], res[1]) - self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if hasattr(self.handle, 'settimeout'): - self.handle.settimeout(None) - self.handle.bind(res[4]) - self.handle.listen(128) - - def accept(self): - client, addr = self.handle.accept() - result = TSocket() - result.setHandle(client) - return result diff --git a/module/lib/thrift/transport/TTransport.py b/module/lib/thrift/transport/TTransport.py deleted file mode 100644 index 12e51a9bf..000000000 --- a/module/lib/thrift/transport/TTransport.py +++ /dev/null @@ -1,331 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from cStringIO import StringIO -from struct import pack,unpack -from thrift.Thrift import TException - -class TTransportException(TException): - - """Custom Transport Exception class""" - - UNKNOWN = 0 - NOT_OPEN = 1 - ALREADY_OPEN = 2 - TIMED_OUT = 3 - END_OF_FILE = 4 - - def __init__(self, type=UNKNOWN, message=None): - TException.__init__(self, message) - self.type = type - -class TTransportBase: - - """Base class for Thrift transport layer.""" - - def isOpen(self): - pass - - def open(self): - pass - - def close(self): - pass - - def read(self, sz): - pass - - def readAll(self, sz): - buff = '' - have = 0 - while (have < sz): - chunk = self.read(sz-have) - have += len(chunk) - buff += chunk - - if len(chunk) == 0: - raise EOFError() - - return buff - - def write(self, buf): - pass - - def flush(self): - pass - -# This class should be thought of as an interface. -class CReadableTransport: - """base class for transports that are readable from C""" - - # TODO(dreiss): Think about changing this interface to allow us to use - # a (Python, not c) StringIO instead, because it allows - # you to write after reading. - - # NOTE: This is a classic class, so properties will NOT work - # correctly for setting. - @property - def cstringio_buf(self): - """A cStringIO buffer that contains the current chunk we are reading.""" - pass - - def cstringio_refill(self, partialread, reqlen): - """Refills cstringio_buf. - - Returns the currently used buffer (which can but need not be the same as - the old cstringio_buf). partialread is what the C code has read from the - buffer, and should be inserted into the buffer before any more reads. The - return value must be a new, not borrowed reference. Something along the - lines of self._buf should be fine. - - If reqlen bytes can't be read, throw EOFError. - """ - pass - -class TServerTransportBase: - - """Base class for Thrift server transports.""" - - def listen(self): - pass - - def accept(self): - pass - - def close(self): - pass - -class TTransportFactoryBase: - - """Base class for a Transport Factory""" - - def getTransport(self, trans): - return trans - -class TBufferedTransportFactory: - - """Factory transport that builds buffered transports""" - - def getTransport(self, trans): - buffered = TBufferedTransport(trans) - return buffered - - -class TBufferedTransport(TTransportBase,CReadableTransport): - - """Class that wraps another transport and buffers its I/O. - - The implementation uses a (configurable) fixed-size read buffer - but buffers all writes until a flush is performed. - """ - - DEFAULT_BUFFER = 4096 - - def __init__(self, trans, rbuf_size = DEFAULT_BUFFER): - self.__trans = trans - self.__wbuf = StringIO() - self.__rbuf = StringIO("") - self.__rbuf_size = rbuf_size - - def isOpen(self): - return self.__trans.isOpen() - - def open(self): - return self.__trans.open() - - def close(self): - return self.__trans.close() - - def read(self, sz): - ret = self.__rbuf.read(sz) - if len(ret) != 0: - return ret - - self.__rbuf = StringIO(self.__trans.read(max(sz, self.__rbuf_size))) - return self.__rbuf.read(sz) - - def write(self, buf): - self.__wbuf.write(buf) - - def flush(self): - out = self.__wbuf.getvalue() - # reset wbuf before write/flush to preserve state on underlying failure - self.__wbuf = StringIO() - self.__trans.write(out) - self.__trans.flush() - - # Implement the CReadableTransport interface. - @property - def cstringio_buf(self): - return self.__rbuf - - def cstringio_refill(self, partialread, reqlen): - retstring = partialread - if reqlen < self.__rbuf_size: - # try to make a read of as much as we can. - retstring += self.__trans.read(self.__rbuf_size) - - # but make sure we do read reqlen bytes. - if len(retstring) < reqlen: - retstring += self.__trans.readAll(reqlen - len(retstring)) - - self.__rbuf = StringIO(retstring) - return self.__rbuf - -class TMemoryBuffer(TTransportBase, CReadableTransport): - """Wraps a cStringIO object as a TTransport. - - NOTE: Unlike the C++ version of this class, you cannot write to it - then immediately read from it. If you want to read from a - TMemoryBuffer, you must either pass a string to the constructor. - TODO(dreiss): Make this work like the C++ version. - """ - - def __init__(self, value=None): - """value -- a value to read from for stringio - - If value is set, this will be a transport for reading, - otherwise, it is for writing""" - if value is not None: - self._buffer = StringIO(value) - else: - self._buffer = StringIO() - - def isOpen(self): - return not self._buffer.closed - - def open(self): - pass - - def close(self): - self._buffer.close() - - def read(self, sz): - return self._buffer.read(sz) - - def write(self, buf): - self._buffer.write(buf) - - def flush(self): - pass - - def getvalue(self): - return self._buffer.getvalue() - - # Implement the CReadableTransport interface. - @property - def cstringio_buf(self): - return self._buffer - - def cstringio_refill(self, partialread, reqlen): - # only one shot at reading... - raise EOFError() - -class TFramedTransportFactory: - - """Factory transport that builds framed transports""" - - def getTransport(self, trans): - framed = TFramedTransport(trans) - return framed - - -class TFramedTransport(TTransportBase, CReadableTransport): - - """Class that wraps another transport and frames its I/O when writing.""" - - def __init__(self, trans,): - self.__trans = trans - self.__rbuf = StringIO() - self.__wbuf = StringIO() - - def isOpen(self): - return self.__trans.isOpen() - - def open(self): - return self.__trans.open() - - def close(self): - return self.__trans.close() - - def read(self, sz): - ret = self.__rbuf.read(sz) - if len(ret) != 0: - return ret - - self.readFrame() - return self.__rbuf.read(sz) - - def readFrame(self): - buff = self.__trans.readAll(4) - sz, = unpack('!i', buff) - self.__rbuf = StringIO(self.__trans.readAll(sz)) - - def write(self, buf): - self.__wbuf.write(buf) - - def flush(self): - wout = self.__wbuf.getvalue() - wsz = len(wout) - # reset wbuf before write/flush to preserve state on underlying failure - self.__wbuf = StringIO() - # N.B.: Doing this string concatenation is WAY cheaper than making - # two separate calls to the underlying socket object. Socket writes in - # Python turn out to be REALLY expensive, but it seems to do a pretty - # good job of managing string buffer operations without excessive copies - buf = pack("!i", wsz) + wout - self.__trans.write(buf) - self.__trans.flush() - - # Implement the CReadableTransport interface. - @property - def cstringio_buf(self): - return self.__rbuf - - def cstringio_refill(self, prefix, reqlen): - # self.__rbuf will already be empty here because fastbinary doesn't - # ask for a refill until the previous buffer is empty. Therefore, - # we can start reading new frames immediately. - while len(prefix) < reqlen: - self.readFrame() - prefix += self.__rbuf.getvalue() - self.__rbuf = StringIO(prefix) - return self.__rbuf - - -class TFileObjectTransport(TTransportBase): - """Wraps a file-like object to make it work as a Thrift transport.""" - - def __init__(self, fileobj): - self.fileobj = fileobj - - def isOpen(self): - return True - - def close(self): - self.fileobj.close() - - def read(self, sz): - return self.fileobj.read(sz) - - def write(self, buf): - self.fileobj.write(buf) - - def flush(self): - self.fileobj.flush() diff --git a/module/lib/thrift/transport/TTwisted.py b/module/lib/thrift/transport/TTwisted.py deleted file mode 100644 index b6dcb4e0b..000000000 --- a/module/lib/thrift/transport/TTwisted.py +++ /dev/null @@ -1,219 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -from zope.interface import implements, Interface, Attribute -from twisted.internet.protocol import Protocol, ServerFactory, ClientFactory, \ - connectionDone -from twisted.internet import defer -from twisted.protocols import basic -from twisted.python import log -from twisted.web import server, resource, http - -from thrift.transport import TTransport -from cStringIO import StringIO - - -class TMessageSenderTransport(TTransport.TTransportBase): - - def __init__(self): - self.__wbuf = StringIO() - - def write(self, buf): - self.__wbuf.write(buf) - - def flush(self): - msg = self.__wbuf.getvalue() - self.__wbuf = StringIO() - self.sendMessage(msg) - - def sendMessage(self, message): - raise NotImplementedError - - -class TCallbackTransport(TMessageSenderTransport): - - def __init__(self, func): - TMessageSenderTransport.__init__(self) - self.func = func - - def sendMessage(self, message): - self.func(message) - - -class ThriftClientProtocol(basic.Int32StringReceiver): - - MAX_LENGTH = 2 ** 31 - 1 - - def __init__(self, client_class, iprot_factory, oprot_factory=None): - self._client_class = client_class - self._iprot_factory = iprot_factory - if oprot_factory is None: - self._oprot_factory = iprot_factory - else: - self._oprot_factory = oprot_factory - - self.recv_map = {} - self.started = defer.Deferred() - - def dispatch(self, msg): - self.sendString(msg) - - def connectionMade(self): - tmo = TCallbackTransport(self.dispatch) - self.client = self._client_class(tmo, self._oprot_factory) - self.started.callback(self.client) - - def connectionLost(self, reason=connectionDone): - for k,v in self.client._reqs.iteritems(): - tex = TTransport.TTransportException( - type=TTransport.TTransportException.END_OF_FILE, - message='Connection closed') - v.errback(tex) - - def stringReceived(self, frame): - tr = TTransport.TMemoryBuffer(frame) - iprot = self._iprot_factory.getProtocol(tr) - (fname, mtype, rseqid) = iprot.readMessageBegin() - - try: - method = self.recv_map[fname] - except KeyError: - method = getattr(self.client, 'recv_' + fname) - self.recv_map[fname] = method - - method(iprot, mtype, rseqid) - - -class ThriftServerProtocol(basic.Int32StringReceiver): - - MAX_LENGTH = 2 ** 31 - 1 - - def dispatch(self, msg): - self.sendString(msg) - - def processError(self, error): - self.transport.loseConnection() - - def processOk(self, _, tmo): - msg = tmo.getvalue() - - if len(msg) > 0: - self.dispatch(msg) - - def stringReceived(self, frame): - tmi = TTransport.TMemoryBuffer(frame) - tmo = TTransport.TMemoryBuffer() - - iprot = self.factory.iprot_factory.getProtocol(tmi) - oprot = self.factory.oprot_factory.getProtocol(tmo) - - d = self.factory.processor.process(iprot, oprot) - d.addCallbacks(self.processOk, self.processError, - callbackArgs=(tmo,)) - - -class IThriftServerFactory(Interface): - - processor = Attribute("Thrift processor") - - iprot_factory = Attribute("Input protocol factory") - - oprot_factory = Attribute("Output protocol factory") - - -class IThriftClientFactory(Interface): - - client_class = Attribute("Thrift client class") - - iprot_factory = Attribute("Input protocol factory") - - oprot_factory = Attribute("Output protocol factory") - - -class ThriftServerFactory(ServerFactory): - - implements(IThriftServerFactory) - - protocol = ThriftServerProtocol - - def __init__(self, processor, iprot_factory, oprot_factory=None): - self.processor = processor - self.iprot_factory = iprot_factory - if oprot_factory is None: - self.oprot_factory = iprot_factory - else: - self.oprot_factory = oprot_factory - - -class ThriftClientFactory(ClientFactory): - - implements(IThriftClientFactory) - - protocol = ThriftClientProtocol - - def __init__(self, client_class, iprot_factory, oprot_factory=None): - self.client_class = client_class - self.iprot_factory = iprot_factory - if oprot_factory is None: - self.oprot_factory = iprot_factory - else: - self.oprot_factory = oprot_factory - - def buildProtocol(self, addr): - p = self.protocol(self.client_class, self.iprot_factory, - self.oprot_factory) - p.factory = self - return p - - -class ThriftResource(resource.Resource): - - allowedMethods = ('POST',) - - def __init__(self, processor, inputProtocolFactory, - outputProtocolFactory=None): - resource.Resource.__init__(self) - self.inputProtocolFactory = inputProtocolFactory - if outputProtocolFactory is None: - self.outputProtocolFactory = inputProtocolFactory - else: - self.outputProtocolFactory = outputProtocolFactory - self.processor = processor - - def getChild(self, path, request): - return self - - def _cbProcess(self, _, request, tmo): - msg = tmo.getvalue() - request.setResponseCode(http.OK) - request.setHeader("content-type", "application/x-thrift") - request.write(msg) - request.finish() - - def render_POST(self, request): - request.content.seek(0, 0) - data = request.content.read() - tmi = TTransport.TMemoryBuffer(data) - tmo = TTransport.TMemoryBuffer() - - iprot = self.inputProtocolFactory.getProtocol(tmi) - oprot = self.outputProtocolFactory.getProtocol(tmo) - - d = self.processor.process(iprot, oprot) - d.addCallback(self._cbProcess, request, tmo) - return server.NOT_DONE_YET diff --git a/module/lib/thrift/transport/TZlibTransport.py b/module/lib/thrift/transport/TZlibTransport.py deleted file mode 100644 index 784d4e1e0..000000000 --- a/module/lib/thrift/transport/TZlibTransport.py +++ /dev/null @@ -1,261 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -''' -TZlibTransport provides a compressed transport and transport factory -class, using the python standard library zlib module to implement -data compression. -''' - -from __future__ import division -import zlib -from cStringIO import StringIO -from TTransport import TTransportBase, CReadableTransport - -class TZlibTransportFactory(object): - ''' - Factory transport that builds zlib compressed transports. - - This factory caches the last single client/transport that it was passed - and returns the same TZlibTransport object that was created. - - This caching means the TServer class will get the _same_ transport - object for both input and output transports from this factory. - (For non-threaded scenarios only, since the cache only holds one object) - - The purpose of this caching is to allocate only one TZlibTransport where - only one is really needed (since it must have separate read/write buffers), - and makes the statistics from getCompSavings() and getCompRatio() - easier to understand. - ''' - - # class scoped cache of last transport given and zlibtransport returned - _last_trans = None - _last_z = None - - def getTransport(self, trans, compresslevel=9): - '''Wrap a transport , trans, with the TZlibTransport - compressed transport class, returning a new - transport to the caller. - - @param compresslevel: The zlib compression level, ranging - from 0 (no compression) to 9 (best compression). Defaults to 9. - @type compresslevel: int - - This method returns a TZlibTransport which wraps the - passed C{trans} TTransport derived instance. - ''' - if trans == self._last_trans: - return self._last_z - ztrans = TZlibTransport(trans, compresslevel) - self._last_trans = trans - self._last_z = ztrans - return ztrans - - -class TZlibTransport(TTransportBase, CReadableTransport): - ''' - Class that wraps a transport with zlib, compressing writes - and decompresses reads, using the python standard - library zlib module. - ''' - - # Read buffer size for the python fastbinary C extension, - # the TBinaryProtocolAccelerated class. - DEFAULT_BUFFSIZE = 4096 - - def __init__(self, trans, compresslevel=9): - ''' - Create a new TZlibTransport, wrapping C{trans}, another - TTransport derived object. - - @param trans: A thrift transport object, i.e. a TSocket() object. - @type trans: TTransport - @param compresslevel: The zlib compression level, ranging - from 0 (no compression) to 9 (best compression). Default is 9. - @type compresslevel: int - ''' - self.__trans = trans - self.compresslevel = compresslevel - self.__rbuf = StringIO() - self.__wbuf = StringIO() - self._init_zlib() - self._init_stats() - - def _reinit_buffers(self): - ''' - Internal method to initialize/reset the internal StringIO objects - for read and write buffers. - ''' - self.__rbuf = StringIO() - self.__wbuf = StringIO() - - def _init_stats(self): - ''' - Internal method to reset the internal statistics counters - for compression ratios and bandwidth savings. - ''' - self.bytes_in = 0 - self.bytes_out = 0 - self.bytes_in_comp = 0 - self.bytes_out_comp = 0 - - def _init_zlib(self): - ''' - Internal method for setting up the zlib compression and - decompression objects. - ''' - self._zcomp_read = zlib.decompressobj() - self._zcomp_write = zlib.compressobj(self.compresslevel) - - def getCompRatio(self): - ''' - Get the current measured compression ratios (in,out) from - this transport. - - Returns a tuple of: - (inbound_compression_ratio, outbound_compression_ratio) - - The compression ratios are computed as: - compressed / uncompressed - - E.g., data that compresses by 10x will have a ratio of: 0.10 - and data that compresses to half of ts original size will - have a ratio of 0.5 - - None is returned if no bytes have yet been processed in - a particular direction. - ''' - r_percent, w_percent = (None, None) - if self.bytes_in > 0: - r_percent = self.bytes_in_comp / self.bytes_in - if self.bytes_out > 0: - w_percent = self.bytes_out_comp / self.bytes_out - return (r_percent, w_percent) - - def getCompSavings(self): - ''' - Get the current count of saved bytes due to data - compression. - - Returns a tuple of: - (inbound_saved_bytes, outbound_saved_bytes) - - Note: if compression is actually expanding your - data (only likely with very tiny thrift objects), then - the values returned will be negative. - ''' - r_saved = self.bytes_in - self.bytes_in_comp - w_saved = self.bytes_out - self.bytes_out_comp - return (r_saved, w_saved) - - def isOpen(self): - '''Return the underlying transport's open status''' - return self.__trans.isOpen() - - def open(self): - """Open the underlying transport""" - self._init_stats() - return self.__trans.open() - - def listen(self): - '''Invoke the underlying transport's listen() method''' - self.__trans.listen() - - def accept(self): - '''Accept connections on the underlying transport''' - return self.__trans.accept() - - def close(self): - '''Close the underlying transport,''' - self._reinit_buffers() - self._init_zlib() - return self.__trans.close() - - def read(self, sz): - ''' - Read up to sz bytes from the decompressed bytes buffer, and - read from the underlying transport if the decompression - buffer is empty. - ''' - ret = self.__rbuf.read(sz) - if len(ret) > 0: - return ret - # keep reading from transport until something comes back - while True: - if self.readComp(sz): - break - ret = self.__rbuf.read(sz) - return ret - - def readComp(self, sz): - ''' - Read compressed data from the underlying transport, then - decompress it and append it to the internal StringIO read buffer - ''' - zbuf = self.__trans.read(sz) - zbuf = self._zcomp_read.unconsumed_tail + zbuf - buf = self._zcomp_read.decompress(zbuf) - self.bytes_in += len(zbuf) - self.bytes_in_comp += len(buf) - old = self.__rbuf.read() - self.__rbuf = StringIO(old + buf) - if len(old) + len(buf) == 0: - return False - return True - - def write(self, buf): - ''' - Write some bytes, putting them into the internal write - buffer for eventual compression. - ''' - self.__wbuf.write(buf) - - def flush(self): - ''' - Flush any queued up data in the write buffer and ensure the - compression buffer is flushed out to the underlying transport - ''' - wout = self.__wbuf.getvalue() - if len(wout) > 0: - zbuf = self._zcomp_write.compress(wout) - self.bytes_out += len(wout) - self.bytes_out_comp += len(zbuf) - else: - zbuf = '' - ztail = self._zcomp_write.flush(zlib.Z_SYNC_FLUSH) - self.bytes_out_comp += len(ztail) - if (len(zbuf) + len(ztail)) > 0: - self.__wbuf = StringIO() - self.__trans.write(zbuf + ztail) - self.__trans.flush() - - @property - def cstringio_buf(self): - '''Implement the CReadableTransport interface''' - return self.__rbuf - - def cstringio_refill(self, partialread, reqlen): - '''Implement the CReadableTransport interface for refill''' - retstring = partialread - if reqlen < self.DEFAULT_BUFFSIZE: - retstring += self.read(self.DEFAULT_BUFFSIZE) - while len(retstring) < reqlen: - retstring += self.read(reqlen - len(retstring)) - self.__rbuf = StringIO(retstring) - return self.__rbuf diff --git a/module/lib/thrift/transport/__init__.py b/module/lib/thrift/transport/__init__.py deleted file mode 100644 index 46e54fe6b..000000000 --- a/module/lib/thrift/transport/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -__all__ = ['TTransport', 'TSocket', 'THttpClient','TZlibTransport'] diff --git a/module/network/Browser.py b/module/network/Browser.py deleted file mode 100644 index d68a23687..000000000 --- a/module/network/Browser.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from logging import getLogger - -from HTTPRequest import HTTPRequest -from HTTPDownload import HTTPDownload - - -class Browser(object): - __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl") - - def __init__(self, bucket=None, options={}): - self.log = getLogger("log") - - self.options = options #holds pycurl options - self.bucket = bucket - - self.cj = None # needs to be setted later - self._size = 0 - - self.renewHTTPRequest() - self.dl = None - - - def renewHTTPRequest(self): - if hasattr(self, "http"): self.http.close() - self.http = HTTPRequest(self.cj, self.options) - - def setLastURL(self, val): - self.http.lastURL = val - - # tunnel some attributes from HTTP Request to Browser - lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL) - lastURL = property(lambda self: self.http.lastURL, setLastURL) - code = property(lambda self: self.http.code) - cookieJar = property(lambda self: self.cj) - - def setCookieJar(self, cj): - self.cj = cj - self.http.cj = cj - - @property - def speed(self): - if self.dl: - return self.dl.speed - return 0 - - @property - def size(self): - if self._size: - return self._size - if self.dl: - return self.dl.size - return 0 - - @property - def arrived(self): - if self.dl: - return self.dl.arrived - return 0 - - @property - def percent(self): - if not self.size: return 0 - return (self.arrived * 100) / self.size - - def clearCookies(self): - if self.cj: - self.cj.clear() - self.http.clearCookies() - - def clearReferer(self): - self.http.lastURL = None - - def abortDownloads(self): - self.http.abort = True - if self.dl: - self._size = self.dl.size - self.dl.abort = True - - def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False, - progressNotify=None, disposition=False): - """ this can also download ftp """ - self._size = 0 - self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None, - self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition) - name = self.dl.download(chunks, resume) - self._size = self.dl.size - - self.dl = None - - return name - - def load(self, *args, **kwargs): - """ retrieves page """ - return self.http.load(*args, **kwargs) - - def putHeader(self, name, value): - """ add a header to the request """ - self.http.putHeader(name, value) - - def addAuth(self, pwd): - """Adds user and pw for http auth - - :param pwd: string, user:password - """ - self.options["auth"] = pwd - self.renewHTTPRequest() #we need a new request - - def removeAuth(self): - if "auth" in self.options: del self.options["auth"] - self.renewHTTPRequest() - - def setOption(self, name, value): - """Adds an option to the request, see HTTPRequest for existing ones""" - self.options[name] = value - - def deleteOption(self, name): - if name in self.options: del self.options[name] - - def clearHeaders(self): - self.http.clearHeaders() - - def close(self): - """ cleanup """ - if hasattr(self, "http"): - self.http.close() - del self.http - if hasattr(self, "dl"): - del self.dl - if hasattr(self, "cj"): - del self.cj - -if __name__ == "__main__": - browser = Browser()#proxies={"socks5": "localhost:5000"}) - ip = "http://www.whatismyip.com/automation/n09230945.asp" - #browser.getPage("http://google.com/search?q=bar") - #browser.getPage("https://encrypted.google.com/") - #print browser.getPage(ip) - #print browser.getRedirectLocation("http://google.com/") - #browser.getPage("https://encrypted.google.com/") - #browser.getPage("http://google.com/search?q=bar") - - browser.httpDownload("http://speedtest.netcologne.de/test_10mb.bin", "test_10mb.bin") - diff --git a/module/network/Bucket.py b/module/network/Bucket.py deleted file mode 100644 index 69da277ae..000000000 --- a/module/network/Bucket.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from time import time -from threading import Lock - -class Bucket: - def __init__(self): - self.rate = 0 - self.tokens = 0 - self.timestamp = time() - self.lock = Lock() - - def __nonzero__(self): - return False if self.rate < 10240 else True - - def setRate(self, rate): - self.lock.acquire() - self.rate = int(rate) - self.lock.release() - - def consumed(self, amount): - """ return time the process have to sleep, after consumed specified amount """ - if self.rate < 10240: return 0 #min. 10kb, may become unresponsive otherwise - self.lock.acquire() - - self.calc_tokens() - self.tokens -= amount - - if self.tokens < 0: - time = -self.tokens/float(self.rate) - else: - time = 0 - - - self.lock.release() - return time - - def calc_tokens(self): - if self.tokens < self.rate: - now = time() - delta = self.rate * (now - self.timestamp) - self.tokens = min(self.rate, self.tokens + delta) - self.timestamp = now - diff --git a/module/network/CookieJar.py b/module/network/CookieJar.py deleted file mode 100644 index c05812334..000000000 --- a/module/network/CookieJar.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay, RaNaN -""" - -from time import time - -class CookieJar(): - def __init__(self, pluginname, account=None): - self.cookies = {} - self.plugin = pluginname - self.account = account - - def addCookies(self, clist): - for c in clist: - name = c.split("\t")[5] - self.cookies[name] = c - - def getCookies(self): - return self.cookies.values() - - def parseCookie(self, name): - if name in self.cookies: - return self.cookies[name].split("\t")[6] - else: - return None - - def getCookie(self, name): - return self.parseCookie(name) - - def setCookie(self, domain, name, value, path="/", exp=time()+3600*24*180): - s = ".%s TRUE %s FALSE %s %s %s" % (domain, path, exp, name, value) - self.cookies[name] = s - - def clear(self): - self.cookies = {} diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py deleted file mode 100644 index b637aef32..000000000 --- a/module/network/HTTPChunk.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" -from os import remove, stat, fsync -from os.path import exists -from time import sleep -from re import search -from module.utils import fs_encode -import codecs -import pycurl - -from HTTPRequest import HTTPRequest - -class WrongFormat(Exception): - pass - - -class ChunkInfo(): - def __init__(self, name): - self.name = unicode(name) - self.size = 0 - self.resume = False - self.chunks = [] - - def __repr__(self): - ret = "ChunkInfo: %s, %s\n" % (self.name, self.size) - for i, c in enumerate(self.chunks): - ret += "%s# %s\n" % (i, c[1]) - - return ret - - def setSize(self, size): - self.size = int(size) - - def addChunk(self, name, range): - self.chunks.append((name, range)) - - def clear(self): - self.chunks = [] - - def createChunks(self, chunks): - self.clear() - chunk_size = self.size / chunks - - current = 0 - for i in range(chunks): - end = self.size - 1 if (i == chunks - 1) else current + chunk_size - self.addChunk("%s.chunk%s" % (self.name, i), (current, end)) - current += chunk_size + 1 - - - def save(self): - fs_name = fs_encode("%s.chunks" % self.name) - fh = codecs.open(fs_name, "w", "utf_8") - fh.write("name:%s\n" % self.name) - fh.write("size:%s\n" % self.size) - for i, c in enumerate(self.chunks): - fh.write("#%d:\n" % i) - fh.write("\tname:%s\n" % c[0]) - fh.write("\trange:%i-%i\n" % c[1]) - fh.close() - - @staticmethod - def load(name): - fs_name = fs_encode("%s.chunks" % name) - if not exists(fs_name): - raise IOError() - fh = codecs.open(fs_name, "r", "utf_8") - name = fh.readline()[:-1] - size = fh.readline()[:-1] - if name.startswith("name:") and size.startswith("size:"): - name = name[5:] - size = size[5:] - else: - fh.close() - raise WrongFormat() - ci = ChunkInfo(name) - ci.loaded = True - ci.setSize(size) - while True: - if not fh.readline(): #skip line - break - name = fh.readline()[1:-1] - range = fh.readline()[1:-1] - if name.startswith("name:") and range.startswith("range:"): - name = name[5:] - range = range[6:].split("-") - else: - raise WrongFormat() - - ci.addChunk(name, (long(range[0]), long(range[1]))) - fh.close() - return ci - - def remove(self): - fs_name = fs_encode("%s.chunks" % self.name) - if exists(fs_name): remove(fs_name) - - def getCount(self): - return len(self.chunks) - - def getChunkName(self, index): - return self.chunks[index][0] - - def getChunkRange(self, index): - return self.chunks[index][1] - - -class HTTPChunk(HTTPRequest): - def __init__(self, id, parent, range=None, resume=False): - self.id = id - self.p = parent # HTTPDownload instance - self.range = range # tuple (start, end) - self.resume = resume - self.log = parent.log - - self.size = range[1] - range[0] if range else -1 - self.arrived = 0 - self.lastURL = self.p.referer - - self.c = pycurl.Curl() - - self.header = "" - self.headerParsed = False #indicates if the header has been processed - - self.fp = None #file handle - - self.initHandle() - self.setInterface(self.p.options) - - self.BOMChecked = False # check and remove byte order mark - - self.rep = None - - self.sleep = 0.000 - self.lastSize = 0 - - def __repr__(self): - return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived) - - @property - def cj(self): - return self.p.cj - - def getHandle(self): - """ returns a Curl handle ready to use for perform/multiperform """ - - self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj) - self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody) - self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) - - # request all bytes, since some servers in russia seems to have a defect arihmetic unit - - fs_name = fs_encode(self.p.info.getChunkName(self.id)) - if self.resume: - self.fp = open(fs_name, "ab") - self.arrived = self.fp.tell() - if not self.arrived: - self.arrived = stat(fs_name).st_size - - if self.range: - #do nothing if chunk already finished - if self.arrived + self.range[0] >= self.range[1]: return None - - if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything - range = "%i-" % (self.arrived + self.range[0]) - else: - range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1)) - - self.log.debug("Chunked resume with range %s" % range) - self.c.setopt(pycurl.RANGE, range) - else: - self.log.debug("Resume File from %i" % self.arrived) - self.c.setopt(pycurl.RESUME_FROM, self.arrived) - - else: - if self.range: - if self.id == len(self.p.info.chunks) - 1: # see above - range = "%i-" % self.range[0] - else: - range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1)) - - self.log.debug("Chunked with range %s" % range) - self.c.setopt(pycurl.RANGE, range) - - self.fp = open(fs_name, "wb") - - return self.c - - def writeHeader(self, buf): - self.header += buf - #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers - # as first chunk, we will parse the headers - if not self.range and self.header.endswith("\r\n\r\n"): - self.parseHeader() - elif not self.range and buf.startswith("150") and "data connection" in buf: #ftp file size parsing - size = search(r"(\d+) bytes", buf) - if size: - self.p.size = int(size.group(1)) - self.p.chunkSupport = True - - self.headerParsed = True - - def writeBody(self, buf): - #ignore BOM, it confuses unrar - if not self.BOMChecked: - if [ord(b) for b in buf[:3]] == [239, 187, 191]: - buf = buf[3:] - self.BOMChecked = True - - size = len(buf) - - self.arrived += size - - self.fp.write(buf) - - if self.p.bucket: - sleep(self.p.bucket.consumed(size)) - else: - # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller - # otherwise reduce sleep time percentual (values are based on tests) - # So in general cpu time is saved without reducing bandwith too much - - if size < self.lastSize: - self.sleep += 0.002 - else: - self.sleep *= 0.7 - - self.lastSize = size - - sleep(self.sleep) - - if self.range and self.arrived > self.size: - return 0 #close if we have enough data - - - def parseHeader(self): - """parse data from recieved header""" - for orgline in self.decodeResponse(self.header).splitlines(): - line = orgline.strip().lower() - if line.startswith("accept-ranges") and "bytes" in line: - self.p.chunkSupport = True - - if line.startswith("content-disposition") and "filename=" in line: - name = orgline.partition("filename=")[2] - name = name.replace('"', "").replace("'", "").replace(";", "").strip() - self.p.nameDisposition = name - self.log.debug("Content-Disposition: %s" % name) - - if not self.resume and line.startswith("content-length"): - self.p.size = int(line.split(":")[1]) - - self.headerParsed = True - - def stop(self): - """The download will not proceed after next call of writeBody""" - self.range = [0,0] - self.size = 0 - - def resetRange(self): - """ Reset the range, so the download will load all data available """ - self.range = None - - def setRange(self, range): - self.range = range - self.size = range[1] - range[0] - - def flushFile(self): - """ flush and close file """ - self.fp.flush() - fsync(self.fp.fileno()) #make sure everything was written to disk - self.fp.close() #needs to be closed, or merging chunks will fail - - def close(self): - """ closes everything, unusable after this """ - if self.fp: self.fp.close() - self.c.close() - if hasattr(self, "p"): del self.p
\ No newline at end of file diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py deleted file mode 100644 index fe8075539..000000000 --- a/module/network/HTTPDownload.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from os import remove, fsync -from os.path import dirname -from time import sleep, time -from shutil import move -from logging import getLogger - -import pycurl - -from HTTPChunk import ChunkInfo, HTTPChunk -from HTTPRequest import BadHeader - -from module.plugins.Plugin import Abort -from module.utils import save_join, fs_encode - -class HTTPDownload(): - """ loads a url http + ftp """ - - def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None, - options={}, progressNotify=None, disposition=False): - self.url = url - self.filename = filename #complete file destination, not only name - self.get = get - self.post = post - self.referer = referer - self.cj = cj #cookiejar if cookies are needed - self.bucket = bucket - self.options = options - self.disposition = disposition - # all arguments - - self.abort = False - self.size = 0 - self.nameDisposition = None #will be parsed from content disposition - - self.chunks = [] - - self.log = getLogger("log") - - try: - self.info = ChunkInfo.load(filename) - self.info.resume = True #resume is only possible with valid info file - self.size = self.info.size - self.infoSaved = True - except IOError: - self.info = ChunkInfo(filename) - - self.chunkSupport = None - self.m = pycurl.CurlMulti() - - #needed for speed calculation - self.lastArrived = [] - self.speeds = [] - self.lastSpeeds = [0, 0] - - self.progressNotify = progressNotify - - @property - def speed(self): - last = [sum(x) for x in self.lastSpeeds if x] - return (sum(self.speeds) + sum(last)) / (1 + len(last)) - - @property - def arrived(self): - return sum([c.arrived for c in self.chunks]) - - @property - def percent(self): - if not self.size: return 0 - return (self.arrived * 100) / self.size - - def _copyChunks(self): - init = fs_encode(self.info.getChunkName(0)) #initial chunk name - - if self.info.getCount() > 1: - fo = open(init, "rb+") #first chunkfile - for i in range(1, self.info.getCount()): - #input file - fo.seek( - self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks - fname = fs_encode("%s.chunk%d" % (self.filename, i)) - fi = open(fname, "rb") - buf = 32 * 1024 - while True: #copy in chunks, consumes less memory - data = fi.read(buf) - if not data: - break - fo.write(data) - fi.close() - if fo.tell() < self.info.getChunkRange(i)[1]: - fo.close() - remove(init) - self.info.remove() #there are probably invalid chunks - raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.") - remove(fname) #remove chunk - fo.close() - - if self.nameDisposition and self.disposition: - self.filename = save_join(dirname(self.filename), self.nameDisposition) - - move(init, fs_encode(self.filename)) - self.info.remove() #remove info file - - def download(self, chunks=1, resume=False): - """ returns new filename or None """ - - chunks = max(1, chunks) - resume = self.info.resume and resume - - try: - self._download(chunks, resume) - except pycurl.error, e: - #code 33 - no resume - code = e.args[0] - if code == 33: - # try again without resume - self.log.debug("Errno 33 -> Restart without resume") - - #remove old handles - for chunk in self.chunks: - self.closeChunk(chunk) - - return self._download(chunks, False) - else: - raise - finally: - self.close() - - if self.nameDisposition and self.disposition: return self.nameDisposition - return None - - def _download(self, chunks, resume): - if not resume: - self.info.clear() - self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #create an initial entry - - self.chunks = [] - - init = HTTPChunk(0, self, None, resume) #initial chunk that will load complete file (if needed) - - self.chunks.append(init) - self.m.add_handle(init.getHandle()) - - lastFinishCheck = 0 - lastTimeCheck = 0 - chunksDone = set() # list of curl handles that are finished - chunksCreated = False - done = False - if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can - self.chunkSupport = True - - while 1: - #need to create chunks - if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk - - if not resume: - self.info.setSize(self.size) - self.info.createChunks(chunks) - self.info.save() - - chunks = self.info.getCount() - - init.setRange(self.info.getChunkRange(0)) - - for i in range(1, chunks): - c = HTTPChunk(i, self, self.info.getChunkRange(i), resume) - - handle = c.getHandle() - if handle: - self.chunks.append(c) - self.m.add_handle(handle) - else: - #close immediatly - self.log.debug("Invalid curl handle -> closed") - c.close() - - chunksCreated = True - - while 1: - ret, num_handles = self.m.perform() - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - - t = time() - - # reduce these calls - while lastFinishCheck + 0.5 < t: - # list of failed curl handles - failed = [] - ex = None # save only last exception, we can only raise one anyway - - num_q, ok_list, err_list = self.m.info_read() - for c in ok_list: - chunk = self.findChunk(c) - try: # check if the header implies success, else add it to failed list - chunk.verifyHeader() - except BadHeader, e: - self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) - failed.append(chunk) - ex = e - else: - chunksDone.add(c) - - for c in err_list: - curl, errno, msg = c - chunk = self.findChunk(curl) - #test if chunk was finished - if errno != 23 or "0 !=" not in msg: - failed.append(chunk) - ex = pycurl.error(errno, msg) - self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex))) - continue - - try: # check if the header implies success, else add it to failed list - chunk.verifyHeader() - except BadHeader, e: - self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) - failed.append(chunk) - ex = e - else: - chunksDone.add(curl) - if not num_q: # no more infos to get - - # check if init is not finished so we reset download connections - # note that other chunks are closed and downloaded with init too - if failed and init not in failed and init.c not in chunksDone: - self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex)))) - - #list of chunks to clean and remove - to_clean = filter(lambda x: x is not init, self.chunks) - for chunk in to_clean: - self.closeChunk(chunk) - self.chunks.remove(chunk) - remove(fs_encode(self.info.getChunkName(chunk.id))) - - #let first chunk load the rest and update the info file - init.resetRange() - self.info.clear() - self.info.addChunk("%s.chunk0" % self.filename, (0, self.size)) - self.info.save() - elif failed: - raise ex - - lastFinishCheck = t - - if len(chunksDone) >= len(self.chunks): - if len(chunksDone) > len(self.chunks): - self.log.warning("Finished download chunks size incorrect, please report bug.") - done = True #all chunks loaded - - break - - if done: - break #all chunks loaded - - # calc speed once per second, averaging over 3 seconds - if lastTimeCheck + 1 < t: - diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in - enumerate(self.chunks)] - - self.lastSpeeds[1] = self.lastSpeeds[0] - self.lastSpeeds[0] = self.speeds - self.speeds = [float(a) / (t - lastTimeCheck) for a in diff] - self.lastArrived = [c.arrived for c in self.chunks] - lastTimeCheck = t - self.updateProgress() - - if self.abort: - raise Abort() - - #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize - self.m.select(1) - - for chunk in self.chunks: - chunk.flushFile() #make sure downloads are written to disk - - self._copyChunks() - - def updateProgress(self): - if self.progressNotify: - self.progressNotify(self.percent) - - def findChunk(self, handle): - """ linear search to find a chunk (should be ok since chunk size is usually low) """ - for chunk in self.chunks: - if chunk.c == handle: return chunk - - def closeChunk(self, chunk): - try: - self.m.remove_handle(chunk.c) - except pycurl.error, e: - self.log.debug("Error removing chunk: %s" % str(e)) - finally: - chunk.close() - - def close(self): - """ cleanup """ - for chunk in self.chunks: - self.closeChunk(chunk) - - self.chunks = [] - if hasattr(self, "m"): - self.m.close() - del self.m - if hasattr(self, "cj"): - del self.cj - if hasattr(self, "info"): - del self.info - -if __name__ == "__main__": - url = "http://speedtest.netcologne.de/test_100mb.bin" - - from Bucket import Bucket - - bucket = Bucket() - bucket.setRate(200 * 1024) - bucket = None - - print "starting" - - dwnld = HTTPDownload(url, "test_100mb.bin", bucket=bucket) - dwnld.download(chunks=3, resume=True) diff --git a/module/network/HTTPRequest.py b/module/network/HTTPRequest.py deleted file mode 100644 index 4747d937f..000000000 --- a/module/network/HTTPRequest.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -import pycurl - -from codecs import getincrementaldecoder, lookup, BOM_UTF8 -from urllib import quote, urlencode -from httplib import responses -from logging import getLogger -from cStringIO import StringIO - -from module.plugins.Plugin import Abort - -def myquote(url): - return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]") - -def myurlencode(data): - data = dict(data) - return urlencode(dict((x.encode('utf_8') if isinstance(x, unicode) else x, \ - y.encode('utf_8') if isinstance(y, unicode) else y ) for x, y in data.iteritems())) - -bad_headers = range(400, 404) + range(405, 418) + range(500, 506) - -class BadHeader(Exception): - def __init__(self, code, content=""): - Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)])) - self.code = code - self.content = content - - -class HTTPRequest(): - def __init__(self, cookies=None, options=None): - self.c = pycurl.Curl() - self.rep = StringIO() - - self.cj = cookies #cookiejar - - self.lastURL = None - self.lastEffectiveURL = None - self.abort = False - self.code = 0 # last http code - - self.header = "" - - self.headers = [] #temporary request header - - self.initHandle() - self.setInterface(options) - - self.c.setopt(pycurl.WRITEFUNCTION, self.write) - self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) - - self.log = getLogger("log") - - - def initHandle(self): - """ sets common options to curl handle """ - self.c.setopt(pycurl.FOLLOWLOCATION, 1) - self.c.setopt(pycurl.MAXREDIRS, 5) - self.c.setopt(pycurl.CONNECTTIMEOUT, 30) - self.c.setopt(pycurl.NOSIGNAL, 1) - self.c.setopt(pycurl.NOPROGRESS, 1) - if hasattr(pycurl, "AUTOREFERER"): - self.c.setopt(pycurl.AUTOREFERER, 1) - self.c.setopt(pycurl.SSL_VERIFYPEER, 0) - self.c.setopt(pycurl.LOW_SPEED_TIME, 30) - self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5) - - #self.c.setopt(pycurl.VERBOSE, 1) - - self.c.setopt(pycurl.USERAGENT, - "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0") - if pycurl.version_info()[7]: - self.c.setopt(pycurl.ENCODING, "gzip, deflate") - self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*", - "Accept-Language: en-US,en", - "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7", - "Connection: keep-alive", - "Keep-Alive: 300", - "Expect:"]) - - def setInterface(self, options): - - interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"] - - if interface and interface.lower() != "none": - self.c.setopt(pycurl.INTERFACE, str(interface)) - - if proxy: - if proxy["type"] == "socks4": - self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4) - elif proxy["type"] == "socks5": - self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5) - else: - self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP) - - self.c.setopt(pycurl.PROXY, str(proxy["address"])) - self.c.setopt(pycurl.PROXYPORT, proxy["port"]) - - if proxy["username"]: - self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"]))) - - if ipv6: - self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) - else: - self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) - - if "auth" in options: - self.c.setopt(pycurl.USERPWD, str(options["auth"])) - - if "timeout" in options: - self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"]) - - - def addCookies(self): - """ put cookies from curl handle to cj """ - if self.cj: - self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST)) - - def getCookies(self): - """ add cookies from cj to curl handle """ - if self.cj: - for c in self.cj.getCookies(): - self.c.setopt(pycurl.COOKIELIST, c) - return - - def clearCookies(self): - self.c.setopt(pycurl.COOKIELIST, "") - - def setRequestContext(self, url, get, post, referer, cookies, multipart=False): - """ sets everything needed for the request """ - - url = myquote(url) - - if get: - get = urlencode(get) - url = "%s?%s" % (url, get) - - self.c.setopt(pycurl.URL, url) - self.c.lastUrl = url - - if post: - self.c.setopt(pycurl.POST, 1) - if not multipart: - if type(post) == unicode: - post = str(post) #unicode not allowed - elif type(post) == str: - pass - else: - post = myurlencode(post) - - self.c.setopt(pycurl.POSTFIELDS, post) - else: - post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()] - self.c.setopt(pycurl.HTTPPOST, post) - else: - self.c.setopt(pycurl.POST, 0) - - if referer and self.lastURL: - self.c.setopt(pycurl.REFERER, str(self.lastURL)) - - if cookies: - self.c.setopt(pycurl.COOKIEFILE, "") - self.c.setopt(pycurl.COOKIEJAR, "") - self.getCookies() - - - def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False): - """ load and returns a given page """ - - self.setRequestContext(url, get, post, referer, cookies, multipart) - - self.header = "" - - self.c.setopt(pycurl.HTTPHEADER, self.headers) - - if just_header: - self.c.setopt(pycurl.FOLLOWLOCATION, 0) - self.c.setopt(pycurl.NOBODY, 1) - self.c.perform() - rep = self.header - - self.c.setopt(pycurl.FOLLOWLOCATION, 1) - self.c.setopt(pycurl.NOBODY, 0) - - else: - self.c.perform() - rep = self.getResponse() - - self.c.setopt(pycurl.POSTFIELDS, "") - self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL) - self.code = self.verifyHeader() - - self.addCookies() - - if decode: - rep = self.decodeResponse(rep) - - return rep - - def verifyHeader(self): - """ raise an exceptions on bad headers """ - code = int(self.c.getinfo(pycurl.RESPONSE_CODE)) - if code in bad_headers: - #404 will NOT raise an exception - raise BadHeader(code, self.getResponse()) - return code - - def checkHeader(self): - """ check if header indicates failure""" - return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers - - def getResponse(self): - """ retrieve response from string io """ - if self.rep is None: return "" - value = self.rep.getvalue() - self.rep.close() - self.rep = StringIO() - return value - - def decodeResponse(self, rep): - """ decode with correct encoding, relies on header """ - header = self.header.splitlines() - encoding = "utf8" # default encoding - - for line in header: - line = line.lower().replace(" ", "") - if not line.startswith("content-type:") or\ - ("text" not in line and "application" not in line): - continue - - none, delemiter, charset = line.rpartition("charset=") - if delemiter: - charset = charset.split(";") - if charset: - encoding = charset[0] - - try: - #self.log.debug("Decoded %s" % encoding ) - if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8): - encoding = 'utf-8-sig' - - decoder = getincrementaldecoder(encoding)("replace") - rep = decoder.decode(rep, True) - - #TODO: html_unescape as default - - except LookupError: - self.log.debug("No Decoder foung for %s" % encoding) - except Exception: - self.log.debug("Error when decoding string from %s." % encoding) - - return rep - - def write(self, buf): - """ writes response """ - if self.rep.tell() > 1000000 or self.abort: - rep = self.getResponse() - if self.abort: raise Abort() - f = open("response.dump", "wb") - f.write(rep) - f.close() - raise Exception("Loaded Url exceeded limit") - - self.rep.write(buf) - - def writeHeader(self, buf): - """ writes header """ - self.header += buf - - def putHeader(self, name, value): - self.headers.append("%s: %s" % (name, value)) - - def clearHeaders(self): - self.headers = [] - - def close(self): - """ cleanup, unusable after this """ - self.rep.close() - if hasattr(self, "cj"): - del self.cj - if hasattr(self, "c"): - self.c.close() - del self.c - -if __name__ == "__main__": - url = "http://pyload.org" - c = HTTPRequest() - print c.load(url) - diff --git a/module/network/RequestFactory.py b/module/network/RequestFactory.py deleted file mode 100644 index 5b1528281..000000000 --- a/module/network/RequestFactory.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay, RaNaN -""" - -from threading import Lock - -from Browser import Browser -from Bucket import Bucket -from HTTPRequest import HTTPRequest -from CookieJar import CookieJar - -from XDCCRequest import XDCCRequest - -class RequestFactory(): - def __init__(self, core): - self.lock = Lock() - self.core = core - self.bucket = Bucket() - self.updateBucket() - self.cookiejars = {} - - def iface(self): - return self.core.config["download"]["interface"] - - def getRequest(self, pluginName, account=None, type="HTTP"): - self.lock.acquire() - - if type == "XDCC": - return XDCCRequest(proxies=self.getProxies()) - - req = Browser(self.bucket, self.getOptions()) - - if account: - cj = self.getCookieJar(pluginName, account) - req.setCookieJar(cj) - else: - req.setCookieJar(CookieJar(pluginName)) - - self.lock.release() - return req - - def getHTTPRequest(self, **kwargs): - """ returns a http request, dont forget to close it ! """ - options = self.getOptions() - options.update(kwargs) # submit kwargs as additional options - return HTTPRequest(CookieJar(None), options) - - def getURL(self, *args, **kwargs): - """ see HTTPRequest for argument list """ - h = HTTPRequest(None, self.getOptions()) - try: - rep = h.load(*args, **kwargs) - finally: - h.close() - - return rep - - def getCookieJar(self, pluginName, account=None): - if (pluginName, account) in self.cookiejars: - return self.cookiejars[(pluginName, account)] - - cj = CookieJar(pluginName, account) - self.cookiejars[(pluginName, account)] = cj - return cj - - def getProxies(self): - """ returns a proxy list for the request classes """ - if not self.core.config["proxy"]["proxy"]: - return {} - else: - type = "http" - setting = self.core.config["proxy"]["type"].lower() - if setting == "socks4": type = "socks4" - elif setting == "socks5": type = "socks5" - - username = None - if self.core.config["proxy"]["username"] and self.core.config["proxy"]["username"].lower() != "none": - username = self.core.config["proxy"]["username"] - - pw = None - if self.core.config["proxy"]["password"] and self.core.config["proxy"]["password"].lower() != "none": - pw = self.core.config["proxy"]["password"] - - return { - "type": type, - "address": self.core.config["proxy"]["address"], - "port": self.core.config["proxy"]["port"], - "username": username, - "password": pw, - } - - def getOptions(self): - """returns options needed for pycurl""" - return {"interface": self.iface(), - "proxies": self.getProxies(), - "ipv6": self.core.config["download"]["ipv6"]} - - def updateBucket(self): - """ set values in the bucket according to settings""" - if not self.core.config["download"]["limit_speed"]: - self.bucket.setRate(-1) - else: - self.bucket.setRate(self.core.config["download"]["max_speed"] * 1024) - -# needs pyreq in global namespace -def getURL(*args, **kwargs): - return pyreq.getURL(*args, **kwargs) - - -def getRequest(*args, **kwargs): - return pyreq.getHTTPRequest() diff --git a/module/network/XDCCRequest.py b/module/network/XDCCRequest.py deleted file mode 100644 index f03798c17..000000000 --- a/module/network/XDCCRequest.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: jeix -""" - -import socket -import re - -from os import remove -from os.path import exists - -from time import time - -import struct -from select import select - -from module.plugins.Plugin import Abort - - -class XDCCRequest(): - def __init__(self, timeout=30, proxies={}): - - self.proxies = proxies - self.timeout = timeout - - self.filesize = 0 - self.recv = 0 - self.speed = 0 - - self.abort = False - - - def createSocket(self): - # proxytype = None - # proxy = None - # if self.proxies.has_key("socks5"): - # proxytype = socks.PROXY_TYPE_SOCKS5 - # proxy = self.proxies["socks5"] - # elif self.proxies.has_key("socks4"): - # proxytype = socks.PROXY_TYPE_SOCKS4 - # proxy = self.proxies["socks4"] - # if proxytype: - # sock = socks.socksocket() - # t = _parse_proxy(proxy) - # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2]) - # else: - # sock = socket.socket() - # return sock - - return socket.socket() - - def download(self, ip, port, filename, irc, progressNotify=None): - - ircbuffer = "" - lastUpdate = time() - cumRecvLen = 0 - - dccsock = self.createSocket() - - dccsock.settimeout(self.timeout) - dccsock.connect((ip, port)) - - if exists(filename): - i = 0 - nameParts = filename.rpartition(".") - while True: - newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2]) - i += 1 - - if not exists(newfilename): - filename = newfilename - break - - fh = open(filename, "wb") - - # recv loop for dcc socket - while True: - if self.abort: - dccsock.close() - fh.close() - remove(filename) - raise Abort() - - self._keepAlive(irc, ircbuffer) - - data = dccsock.recv(4096) - dataLen = len(data) - self.recv += dataLen - - cumRecvLen += dataLen - - now = time() - timespan = now - lastUpdate - if timespan > 1: - self.speed = cumRecvLen / timespan - cumRecvLen = 0 - lastUpdate = now - - if progressNotify: - progressNotify(self.percent) - - - if not data: - break - - fh.write(data) - - # acknowledge data by sending number of recceived bytes - dccsock.send(struct.pack('!I', self.recv)) - - dccsock.close() - fh.close() - - return filename - - def _keepAlive(self, sock, readbuffer): - fdset = select([sock], [], [], 0) - if sock not in fdset[0]: - return - - readbuffer += sock.recv(1024) - temp = readbuffer.split("\n") - readbuffer = temp.pop() - - for line in temp: - line = line.rstrip() - first = line.split() - if first[0] == "PING": - sock.send("PONG %s\r\n" % first[1]) - - def abortDownloads(self): - self.abort = True - - @property - def size(self): - return self.filesize - - @property - def arrived(self): - return self.recv - - @property - def percent(self): - if not self.filesize: return 0 - return (self.recv * 100) / self.filesize - - def close(self): - pass diff --git a/module/plugins/Account.py b/module/plugins/Account.py deleted file mode 100644 index c147404e0..000000000 --- a/module/plugins/Account.py +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from random import choice -from time import time -from traceback import print_exc -from threading import RLock - -from Plugin import Base -from module.utils import compare_time, parseFileSize, lock - -class WrongPassword(Exception): - pass - - -class Account(Base): - """ - Base class for every Account plugin. - Just overwrite `login` and cookies will be stored and account becomes accessible in\ - associated hoster plugin. Plugin should also provide `loadAccountInfo` - """ - __name__ = "Account" - __version__ = "0.2" - __type__ = "account" - __description__ = """Account Plugin""" - __author_name__ = ("mkaay") - __author_mail__ = ("mkaay@mkaay.de") - - #: after that time [in minutes] pyload will relogin the account - login_timeout = 600 - #: account data will be reloaded after this time - info_threshold = 600 - - - def __init__(self, manager, accounts): - Base.__init__(self, manager.core) - - self.manager = manager - self.accounts = {} - self.infos = {} # cache for account information - self.lock = RLock() - - self.timestamps = {} - self.setAccounts(accounts) - self.init() - - def init(self): - pass - - def login(self, user, data, req): - """login into account, the cookies will be saved so user can be recognized - - :param user: loginname - :param data: data dictionary - :param req: `Request` instance - """ - pass - - @lock - def _login(self, user, data): - # set timestamp for login - self.timestamps[user] = time() - - req = self.getAccountRequest(user) - try: - self.login(user, data, req) - except WrongPassword: - self.logWarning( - _("Could not login with account %(user)s | %(msg)s") % {"user": user - , "msg": _("Wrong Password")}) - data["valid"] = False - - except Exception, e: - self.logWarning( - _("Could not login with account %(user)s | %(msg)s") % {"user": user - , "msg": e}) - data["valid"] = False - if self.core.debug: - print_exc() - finally: - if req: req.close() - - def relogin(self, user): - req = self.getAccountRequest(user) - if req: - req.cj.clear() - req.close() - if user in self.infos: - del self.infos[user] #delete old information - - self._login(user, self.accounts[user]) - - def setAccounts(self, accounts): - self.accounts = accounts - for user, data in self.accounts.iteritems(): - self._login(user, data) - self.infos[user] = {} - - def updateAccounts(self, user, password=None, options={}): - """ updates account and return true if anything changed """ - - if user in self.accounts: - self.accounts[user]["valid"] = True #do not remove or accounts will not login - if password: - self.accounts[user]["password"] = password - self.relogin(user) - return True - if options: - before = self.accounts[user]["options"] - self.accounts[user]["options"].update(options) - return self.accounts[user]["options"] != before - else: - self.accounts[user] = {"password": password, "options": options, "valid": True} - self._login(user, self.accounts[user]) - return True - - def removeAccount(self, user): - if user in self.accounts: - del self.accounts[user] - if user in self.infos: - del self.infos[user] - if user in self.timestamps: - del self.timestamps[user] - - @lock - def getAccountInfo(self, name, force=False): - """retrieve account infos for an user, do **not** overwrite this method!\\ - just use it to retrieve infos in hoster plugins. see `loadAccountInfo` - - :param name: username - :param force: reloads cached account information - :return: dictionary with information - """ - data = Account.loadAccountInfo(self, name) - - if force or name not in self.infos: - self.logDebug("Get Account Info for %s" % name) - req = self.getAccountRequest(name) - - try: - infos = self.loadAccountInfo(name, req) - if not type(infos) == dict: - raise Exception("Wrong return format") - except Exception, e: - infos = {"error": str(e)} - - if req: req.close() - - self.logDebug("Account Info: %s" % str(infos)) - - infos["timestamp"] = time() - self.infos[name] = infos - elif "timestamp" in self.infos[name] and self.infos[name][ - "timestamp"] + self.info_threshold * 60 < time(): - self.logDebug("Reached timeout for account data") - self.scheduleRefresh(name) - - data.update(self.infos[name]) - return data - - def isPremium(self, user): - info = self.getAccountInfo(user) - return info["premium"] - - def loadAccountInfo(self, name, req=None): - """this should be overwritten in account plugin,\ - and retrieving account information for user - - :param name: - :param req: `Request` instance - :return: - """ - return { - "validuntil": None, # -1 for unlimited - "login": name, - #"password": self.accounts[name]["password"], #@XXX: security - "options": self.accounts[name]["options"], - "valid": self.accounts[name]["valid"], - "trafficleft": None, # in kb, -1 for unlimited - "maxtraffic": None, - "premium": True, #useful for free accounts - "timestamp": 0, #time this info was retrieved - "type": self.__name__, - } - - def getAllAccounts(self, force=False): - return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()] - - def getAccountRequest(self, user=None): - if not user: - user, data = self.selectAccount() - if not user: - return None - - req = self.core.requestFactory.getRequest(self.__name__, user) - return req - - def getAccountCookies(self, user=None): - if not user: - user, data = self.selectAccount() - if not user: - return None - - cj = self.core.requestFactory.getCookieJar(self.__name__, user) - return cj - - def getAccountData(self, user): - return self.accounts[user] - - def selectAccount(self): - """ returns an valid account name and data""" - usable = [] - for user, data in self.accounts.iteritems(): - if not data["valid"]: continue - - if "time" in data["options"] and data["options"]["time"]: - time_data = "" - try: - time_data = data["options"]["time"][0] - start, end = time_data.split("-") - if not compare_time(start.split(":"), end.split(":")): - continue - except: - self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data) - - if user in self.infos: - if "validuntil" in self.infos[user]: - if self.infos[user]["validuntil"] > 0 and time() > self.infos[user]["validuntil"]: - continue - if "trafficleft" in self.infos[user]: - if self.infos[user]["trafficleft"] == 0: - continue - - usable.append((user, data)) - - if not usable: return None, None - return choice(usable) - - def canUse(self): - return False if self.selectAccount() == (None, None) else True - - def parseTraffic(self, string): #returns kbyte - return parseFileSize(string) / 1024 - - def wrongPassword(self): - raise WrongPassword - - def empty(self, user): - if user in self.infos: - self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user) - - self.infos[user].update({"trafficleft": 0}) - self.scheduleRefresh(user, 30 * 60) - - def expired(self, user): - if user in self.infos: - self.logWarning(_("Account %s is expired, checking again in 1h") % user) - - self.infos[user].update({"validuntil": time() - 1}) - self.scheduleRefresh(user, 60 * 60) - - def scheduleRefresh(self, user, time=0, force=True): - """ add task to refresh account info to sheduler """ - self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time)) - self.core.scheduler.addJob(time, self.getAccountInfo, [user, force]) - - @lock - def checkLogin(self, user): - """ checks if user is still logged in """ - if user in self.timestamps: - if self.timestamps[user] + self.login_timeout * 60 < time(): - self.logDebug("Reached login timeout for %s" % user) - self.relogin(user) - return False - - return True diff --git a/module/plugins/AccountManager.py b/module/plugins/AccountManager.py deleted file mode 100644 index fc521d36c..000000000 --- a/module/plugins/AccountManager.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -from os.path import exists -from shutil import copy - -from threading import Lock - -from module.PullEvents import AccountUpdateEvent -from module.utils import chmod, lock - -ACC_VERSION = 1 - -class AccountManager(): - """manages all accounts""" - - #---------------------------------------------------------------------- - def __init__(self, core): - """Constructor""" - - self.core = core - self.lock = Lock() - - self.initPlugins() - self.saveAccounts() # save to add categories to conf - - def initPlugins(self): - self.accounts = {} # key = ( plugin ) - self.plugins = {} - - self.initAccountPlugins() - self.loadAccounts() - - - def getAccountPlugin(self, plugin): - """get account instance for plugin or None if anonymous""" - if plugin in self.accounts: - if plugin not in self.plugins: - self.plugins[plugin] = self.core.pluginManager.loadClass("accounts", plugin)(self, self.accounts[plugin]) - - return self.plugins[plugin] - else: - return None - - def getAccountPlugins(self): - """ get all account instances""" - - plugins = [] - for plugin in self.accounts.keys(): - plugins.append(self.getAccountPlugin(plugin)) - - return plugins - #---------------------------------------------------------------------- - def loadAccounts(self): - """loads all accounts available""" - - if not exists("accounts.conf"): - f = open("accounts.conf", "wb") - f.write("version: " + str(ACC_VERSION)) - f.close() - - f = open("accounts.conf", "rb") - content = f.readlines() - version = content[0].split(":")[1].strip() if content else "" - f.close() - - if not version or int(version) < ACC_VERSION: - copy("accounts.conf", "accounts.backup") - f = open("accounts.conf", "wb") - f.write("version: " + str(ACC_VERSION)) - f.close() - self.core.log.warning(_("Account settings deleted, due to new config format.")) - return - - - - plugin = "" - name = "" - - for line in content[1:]: - line = line.strip() - - if not line: continue - if line.startswith("#"): continue - if line.startswith("version"): continue - - if line.endswith(":") and line.count(":") == 1: - plugin = line[:-1] - self.accounts[plugin] = {} - - elif line.startswith("@"): - try: - option = line[1:].split() - self.accounts[plugin][name]["options"][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:]) - except: - pass - - elif ":" in line: - name, sep, pw = line.partition(":") - self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True} - #---------------------------------------------------------------------- - def saveAccounts(self): - """save all account information""" - - f = open("accounts.conf", "wb") - f.write("version: " + str(ACC_VERSION) + "\n") - - for plugin, accounts in self.accounts.iteritems(): - f.write("\n") - f.write(plugin+":\n") - - for name,data in accounts.iteritems(): - f.write("\n\t%s:%s\n" % (name,data["password"]) ) - if data["options"]: - for option, values in data["options"].iteritems(): - f.write("\t@%s %s\n" % (option, " ".join(values))) - - f.close() - chmod(f.name, 0600) - - - #---------------------------------------------------------------------- - def initAccountPlugins(self): - """init names""" - for name in self.core.pluginManager.getAccountPlugins(): - self.accounts[name] = {} - - @lock - def updateAccount(self, plugin , user, password=None, options={}): - """add or update account""" - if plugin in self.accounts: - p = self.getAccountPlugin(plugin) - updated = p.updateAccounts(user, password, options) - #since accounts is a ref in plugin self.accounts doesnt need to be updated here - - self.saveAccounts() - if updated: p.scheduleRefresh(user, force=False) - - @lock - def removeAccount(self, plugin, user): - """remove account""" - - if plugin in self.accounts: - p = self.getAccountPlugin(plugin) - p.removeAccount(user) - - self.saveAccounts() - - @lock - def getAccountInfos(self, force=True, refresh=False): - data = {} - - if refresh: - self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos) - force = False - - for p in self.accounts.keys(): - if self.accounts[p]: - p = self.getAccountPlugin(p) - data[p.__name__] = p.getAllAccounts(force) - else: - data[p] = [] - e = AccountUpdateEvent() - self.core.pullManager.addEvent(e) - return data - - def sendChange(self): - e = AccountUpdateEvent() - self.core.pullManager.addEvent(e) diff --git a/module/plugins/Container.py b/module/plugins/Container.py deleted file mode 100644 index c233d3710..000000000 --- a/module/plugins/Container.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from module.plugins.Crypter import Crypter - -from os.path import join, exists, basename -from os import remove -import re - -class Container(Crypter): - __name__ = "Container" - __version__ = "0.1" - __pattern__ = None - __type__ = "container" - __description__ = """Base container plugin""" - __author_name__ = ("mkaay") - __author_mail__ = ("mkaay@mkaay.de") - - - def preprocessing(self, thread): - """prepare""" - - self.setup() - self.thread = thread - - self.loadToDisk() - - self.decrypt(self.pyfile) - self.deleteTmp() - - self.createPackages() - - - def loadToDisk(self): - """loads container to disk if its stored remotely and overwrite url, - or check existent on several places at disk""" - - if self.pyfile.url.startswith("http"): - self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1] - content = self.load(self.pyfile.url) - self.pyfile.url = join(self.config["general"]["download_folder"], self.pyfile.name) - f = open(self.pyfile.url, "wb" ) - f.write(content) - f.close() - - else: - self.pyfile.name = basename(self.pyfile.url) - if not exists(self.pyfile.url): - if exists(join(pypath, self.pyfile.url)): - self.pyfile.url = join(pypath, self.pyfile.url) - else: - self.fail(_("File not exists.")) - - - def deleteTmp(self): - if self.pyfile.name.startswith("tmp_"): - remove(self.pyfile.url) - - diff --git a/module/plugins/Crypter.py b/module/plugins/Crypter.py deleted file mode 100644 index d1549fe80..000000000 --- a/module/plugins/Crypter.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from module.plugins.Plugin import Plugin - -class Crypter(Plugin): - __name__ = "Crypter" - __version__ = "0.1" - __pattern__ = None - __type__ = "container" - __description__ = """Base crypter plugin""" - __author_name__ = ("mkaay") - __author_mail__ = ("mkaay@mkaay.de") - - def __init__(self, pyfile): - Plugin.__init__(self, pyfile) - - #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder ) - self.packages = [] - - #: List of urls, pyLoad will generate packagenames - self.urls = [] - - self.multiDL = True - self.limitDL = 0 - - - def preprocessing(self, thread): - """prepare""" - self.setup() - self.thread = thread - - self.decrypt(self.pyfile) - - self.createPackages() - - - def decrypt(self, pyfile): - raise NotImplementedError - - def createPackages(self): - """ create new packages from self.packages """ - for pack in self.packages: - - self.log.debug("Parsed package %(name)s with %(len)d links" % { "name" : pack[0], "len" : len(pack[1]) } ) - - links = [x.decode("utf-8") for x in pack[1]] - - pid = self.core.api.addPackage(pack[0], links, self.pyfile.package().queue) - - if self.pyfile.package().password: - self.core.api.setPackageData(pid, {"password": self.pyfile.package().password}) - - if self.urls: - self.core.api.generateAndAddPackages(self.urls) - diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py deleted file mode 100644 index 5efd08bae..000000000 --- a/module/plugins/Hook.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay - @interface-version: 0.2 -""" - -from traceback import print_exc - -from Plugin import Base - -class Expose(object): - """ used for decoration to declare rpc services """ - - def __new__(cls, f, *args, **kwargs): - hookManager.addRPC(f.__module__, f.func_name, f.func_doc) - return f - -def threaded(f): - def run(*args,**kwargs): - hookManager.startThread(f, *args, **kwargs) - return run - -class Hook(Base): - """ - Base class for hook plugins. - """ - __name__ = "Hook" - __version__ = "0.2" - __type__ = "hook" - __threaded__ = [] - __config__ = [ ("name", "type", "desc" , "default") ] - __description__ = """interface for hook""" - __author_name__ = ("mkaay", "RaNaN") - __author_mail__ = ("mkaay@mkaay.de", "RaNaN@pyload.org") - - #: automatically register event listeners for functions, attribute will be deleted dont use it yourself - event_map = None - - # Alternative to event_map - #: List of events the plugin can handle, name the functions exactly like eventname. - event_list = None # dont make duplicate entries in event_map - - - #: periodic call interval in secondc - interval = 60 - - def __init__(self, core, manager): - Base.__init__(self, core) - - #: Provide information in dict here, usable by API `getInfo` - self.info = None - - #: Callback of periodical job task, used by hookmanager - self.cb = None - - #: `HookManager` - self.manager = manager - - #register events - if self.event_map: - for event, funcs in self.event_map.iteritems(): - if type(funcs) in (list, tuple): - for f in funcs: - self.manager.addEvent(event, getattr(self,f)) - else: - self.manager.addEvent(event, getattr(self,funcs)) - - #delete for various reasons - self.event_map = None - - if self.event_list: - for f in self.event_list: - self.manager.addEvent(f, getattr(self,f)) - - self.event_list = None - - self.initPeriodical() - self.setup() - - def initPeriodical(self): - if self.interval >=1: - self.cb = self.core.scheduler.addJob(0, self._periodical, threaded=False) - - def _periodical(self): - try: - if self.isActivated(): self.periodical() - except Exception, e: - self.core.log.error(_("Error executing hooks: %s") % str(e)) - if self.core.debug: - print_exc() - - self.cb = self.core.scheduler.addJob(self.interval, self._periodical, threaded=False) - - - def __repr__(self): - return "<Hook %s>" % self.__name__ - - def setup(self): - """ more init stuff if needed """ - pass - - def unload(self): - """ called when hook was deactivated """ - pass - - def isActivated(self): - """ checks if hook is activated""" - return self.config.getPlugin(self.__name__, "activated") - - - #event methods - overwrite these if needed - def coreReady(self): - pass - - def coreExiting(self): - pass - - def downloadPreparing(self, pyfile): - pass - - def downloadFinished(self, pyfile): - pass - - def downloadFailed(self, pyfile): - pass - - def packageFinished(self, pypack): - pass - - def beforeReconnecting(self, ip): - pass - - def afterReconnecting(self, ip): - pass - - def periodical(self): - pass - - def newCaptchaTask(self, task): - """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """ - pass - - def captchaCorrect(self, task): - pass - - def captchaInvalid(self, task): - pass
\ No newline at end of file diff --git a/module/plugins/Hoster.py b/module/plugins/Hoster.py deleted file mode 100644 index 814a70949..000000000 --- a/module/plugins/Hoster.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from module.plugins.Plugin import Plugin - -def getInfo(self): - #result = [ .. (name, size, status, url) .. ] - return - -class Hoster(Plugin): - __name__ = "Hoster" - __version__ = "0.1" - __pattern__ = None - __type__ = "hoster" - __description__ = """Base hoster plugin""" - __author_name__ = ("mkaay") - __author_mail__ = ("mkaay@mkaay.de") diff --git a/module/plugins/Plugin.py b/module/plugins/Plugin.py deleted file mode 100644 index 15bf3971f..000000000 --- a/module/plugins/Plugin.py +++ /dev/null @@ -1,617 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN, spoob, mkaay -""" - -from time import time, sleep -from random import randint - -import os -from os import remove, makedirs, chmod, stat -from os.path import exists, join - -if os.name != "nt": - from os import chown - from pwd import getpwnam - from grp import getgrnam - -from itertools import islice - -from module.utils import save_join, save_path, fs_encode, fs_decode - -def chunks(iterable, size): - it = iter(iterable) - item = list(islice(it, size)) - while item: - yield item - item = list(islice(it, size)) - - -class Abort(Exception): - """ raised when aborted """ - - -class Fail(Exception): - """ raised when failed """ - - -class Reconnect(Exception): - """ raised when reconnected """ - - -class Retry(Exception): - """ raised when start again from beginning """ - - -class SkipDownload(Exception): - """ raised when download should be skipped """ - - -class Base(object): - """ - A Base class with log/config/db methods *all* plugin types can use - """ - - def __init__(self, core): - #: Core instance - self.core = core - #: logging instance - self.log = core.log - #: core config - self.config = core.config - - #log functions - def logInfo(self, *args): - self.log.info("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args]))) - - def logWarning(self, *args): - self.log.warning("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args]))) - - def logError(self, *args): - self.log.error("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args]))) - - def logDebug(self, *args): - self.log.debug("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args]))) - - - def setConf(self, option, value): - """ see `setConfig` """ - self.core.config.setPlugin(self.__name__, option, value) - - def setConfig(self, option, value): - """ Set config value for current plugin - - :param option: - :param value: - :return: - """ - self.setConf(option, value) - - def getConf(self, option): - """ see `getConfig` """ - return self.core.config.getPlugin(self.__name__, option) - - def getConfig(self, option): - """ Returns config value for current plugin - - :param option: - :return: - """ - return self.getConf(option) - - def setStorage(self, key, value): - """ Saves a value persistently to the database """ - self.core.db.setStorage(self.__name__, key, value) - - def store(self, key, value): - """ same as `setStorage` """ - self.core.db.setStorage(self.__name__, key, value) - - def getStorage(self, key=None, default=None): - """ Retrieves saved value or dict of all saved entries if key is None """ - if key is not None: - return self.core.db.getStorage(self.__name__, key) or default - return self.core.db.getStorage(self.__name__, key) - - def retrieve(self, *args, **kwargs): - """ same as `getStorage` """ - return self.getStorage(*args, **kwargs) - - def delStorage(self, key): - """ Delete entry in db """ - self.core.db.delStorage(self.__name__, key) - - -class Plugin(Base): - """ - Base plugin for hoster/crypter. - Overwrite `process` / `decrypt` in your subclassed plugin. - """ - __name__ = "Plugin" - __version__ = "0.4" - __pattern__ = None - __type__ = "hoster" - __config__ = [("name", "type", "desc", "default")] - __description__ = """Base Plugin""" - __author_name__ = ("RaNaN", "spoob", "mkaay") - __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org", "mkaay@mkaay.de") - - def __init__(self, pyfile): - Base.__init__(self, pyfile.m.core) - - self.wantReconnect = False - #: enables simultaneous processing of multiple downloads - self.multiDL = True - self.limitDL = 0 - #: chunk limit - self.chunkLimit = 1 - self.resumeDownload = False - - #: time() + wait in seconds - self.waitUntil = 0 - self.waiting = False - - self.ocr = None #captcha reader instance - #: account handler instance, see :py:class:`Account` - self.account = pyfile.m.core.accountManager.getAccountPlugin(self.__name__) - - #: premium status - self.premium = False - #: username/login - self.user = None - - if self.account and not self.account.canUse(): self.account = None - if self.account: - self.user, data = self.account.selectAccount() - #: Browser instance, see `network.Browser` - self.req = self.account.getAccountRequest(self.user) - self.chunkLimit = -1 # chunk limit, -1 for unlimited - #: enables resume (will be ignored if server dont accept chunks) - self.resumeDownload = True - self.multiDL = True #every hoster with account should provide multiple downloads - #: premium status - self.premium = self.account.isPremium(self.user) - else: - self.req = pyfile.m.core.requestFactory.getRequest(self.__name__) - - #: associated pyfile instance, see `PyFile` - self.pyfile = pyfile - self.thread = None # holds thread in future - - #: location where the last call to download was saved - self.lastDownload = "" - #: re match of the last call to `checkDownload` - self.lastCheck = None - #: js engine, see `JsEngine` - self.js = self.core.js - self.cTask = None #captcha task - - self.retries = 0 # amount of retries already made - self.html = None # some plugins store html code here - - self.init() - - def getChunkCount(self): - if self.chunkLimit <= 0: - return self.config["download"]["chunks"] - return min(self.config["download"]["chunks"], self.chunkLimit) - - def __call__(self): - return self.__name__ - - def init(self): - """initialize the plugin (in addition to `__init__`)""" - pass - - def setup(self): - """ setup for enviroment and other things, called before downloading (possibly more than one time)""" - pass - - def preprocessing(self, thread): - """ handles important things to do before starting """ - self.thread = thread - - if self.account: - self.account.checkLogin(self.user) - else: - self.req.clearCookies() - - self.setup() - - self.pyfile.setStatus("starting") - - return self.process(self.pyfile) - - - def process(self, pyfile): - """the 'main' method of every plugin, you **have to** overwrite it""" - raise NotImplementedError - - def resetAccount(self): - """ dont use account and retry download """ - self.account = None - self.req = self.core.requestFactory.getRequest(self.__name__) - self.retry() - - def checksum(self, local_file=None): - """ - return codes: - 0 - checksum ok - 1 - checksum wrong - 5 - can't get checksum - 10 - not implemented - 20 - unknown error - """ - #@TODO checksum check hook - - return True, 10 - - - def setWait(self, seconds, reconnect=False): - """Set a specific wait time later used with `wait` - - :param seconds: wait time in seconds - :param reconnect: True if a reconnect would avoid wait time - """ - if reconnect: - self.wantReconnect = True - self.pyfile.waitUntil = time() + int(seconds) - - def wait(self): - """ waits the time previously set """ - self.waiting = True - self.pyfile.setStatus("waiting") - - while self.pyfile.waitUntil > time(): - self.thread.m.reconnecting.wait(2) - - if self.pyfile.abort: raise Abort - if self.thread.m.reconnecting.isSet(): - self.waiting = False - self.wantReconnect = False - raise Reconnect - - self.waiting = False - self.pyfile.setStatus("starting") - - def fail(self, reason): - """ fail and give reason """ - raise Fail(reason) - - def offline(self): - """ fail and indicate file is offline """ - raise Fail("offline") - - def tempOffline(self): - """ fail and indicates file ist temporary offline, the core may take consequences """ - raise Fail("temp. offline") - - def retry(self, max_tries=3, wait_time=1, reason=""): - """Retries and begin again from the beginning - - :param max_tries: number of maximum retries - :param wait_time: time to wait in seconds - :param reason: reason for retrying, will be passed to fail if max_tries reached - """ - if 0 < max_tries <= self.retries: - if not reason: reason = "Max retries reached" - raise Fail(reason) - - self.wantReconnect = False - self.setWait(wait_time) - self.wait() - - self.retries += 1 - raise Retry(reason) - - def invalidCaptcha(self): - if self.cTask: - self.cTask.invalid() - - def correctCaptcha(self): - if self.cTask: - self.cTask.correct() - - def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg', - result_type='textual'): - """ Loads a captcha and decrypts it with ocr, plugin, user input - - :param url: url of captcha image - :param get: get part for request - :param post: post part for request - :param cookies: True if cookies should be enabled - :param forceUser: if True, ocr is not used - :param imgtype: Type of the Image - :param result_type: 'textual' if text is written on the captcha\ - or 'positional' for captcha where the user have to click\ - on a specific region on the captcha - - :return: result of decrypting - """ - - img = self.load(url, get=get, post=post, cookies=cookies) - - id = ("%.2f" % time())[-6:].replace(".", "") - temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb") - temp_file.write(img) - temp_file.close() - - has_plugin = self.__name__ in self.core.pluginManager.captchaPlugins - - if self.core.captcha: - Ocr = self.core.pluginManager.loadClass("captcha", self.__name__) - else: - Ocr = None - - if Ocr and not forceUser: - sleep(randint(3000, 5000) / 1000.0) - if self.pyfile.abort: raise Abort - - ocr = Ocr() - result = ocr.get_captcha(temp_file.name) - else: - captchaManager = self.core.captchaManager - task = captchaManager.newTask(img, imgtype, temp_file.name, result_type) - self.cTask = task - captchaManager.handleCaptcha(task) - - while task.isWaiting(): - if self.pyfile.abort: - captchaManager.removeTask(task) - raise Abort - sleep(1) - - captchaManager.removeTask(task) - - if task.error and has_plugin: #ignore default error message since the user could use OCR - self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting")) - elif task.error: - self.fail(task.error) - elif not task.result: - self.fail(_("No captcha result obtained in appropiate time by any of the plugins.")) - - result = task.result - self.log.debug("Received captcha result: %s" % str(result)) - - if not self.core.debug: - try: - remove(temp_file.name) - except: - pass - - return result - - - def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False): - """Load content at url and returns it - - :param url: - :param get: - :param post: - :param ref: - :param cookies: - :param just_header: if True only the header will be retrieved and returned as dict - :param decode: Wether to decode the output according to http header, should be True in most cases - :return: Loaded content - """ - if self.pyfile.abort: raise Abort - #utf8 vs decode -> please use decode attribute in all future plugins - if type(url) == unicode: url = str(url) - - res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode) - - if self.core.debug: - from inspect import currentframe - - frame = currentframe() - if not exists(join("tmp", self.__name__)): - makedirs(join("tmp", self.__name__)) - - f = open( - join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno)) - , "wb") - del frame # delete the frame or it wont be cleaned - - try: - tmp = res.encode("utf8") - except: - tmp = res - - f.write(tmp) - f.close() - - if just_header: - #parse header - header = {"code": self.req.code} - for line in res.splitlines(): - line = line.strip() - if not line or ":" not in line: continue - - key, none, value = line.partition(":") - key = key.lower().strip() - value = value.strip() - - if key in header: - if type(header[key]) == list: - header[key].append(value) - else: - header[key] = [header[key], value] - else: - header[key] = value - res = header - - return res - - def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False): - """Downloads the content at url to download folder - - :param url: - :param get: - :param post: - :param ref: - :param cookies: - :param disposition: if True and server provides content-disposition header\ - the filename will be changed if needed - :return: The location where the file was saved - """ - - self.checkForSameFiles() - - self.pyfile.setStatus("downloading") - - download_folder = self.config['general']['download_folder'] - - location = save_join(download_folder, self.pyfile.package().folder) - - if not exists(location): - makedirs(location, int(self.core.config["permission"]["folder"], 8)) - - if self.core.config["permission"]["change_dl"] and os.name != "nt": - try: - uid = getpwnam(self.config["permission"]["user"])[2] - gid = getgrnam(self.config["permission"]["group"])[2] - - chown(location, uid, gid) - except Exception, e: - self.log.warning(_("Setting User and Group failed: %s") % str(e)) - - # convert back to unicode - location = fs_decode(location) - name = save_path(self.pyfile.name) - - filename = join(location, name) - - self.core.hookManager.dispatchEvent("downloadStarts", self.pyfile, url, filename) - - try: - newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies, - chunks=self.getChunkCount(), resume=self.resumeDownload, - progressNotify=self.pyfile.setProgress, disposition=disposition) - finally: - self.pyfile.size = self.req.size - - if disposition and newname and newname != name: #triple check, just to be sure - self.log.info("%(name)s saved as %(newname)s" % {"name": name, "newname": newname}) - self.pyfile.name = newname - filename = join(location, newname) - - fs_filename = fs_encode(filename) - - if self.core.config["permission"]["change_file"]: - chmod(fs_filename, int(self.core.config["permission"]["file"], 8)) - - if self.core.config["permission"]["change_dl"] and os.name != "nt": - try: - uid = getpwnam(self.config["permission"]["user"])[2] - gid = getgrnam(self.config["permission"]["group"])[2] - - chown(fs_filename, uid, gid) - except Exception, e: - self.log.warning(_("Setting User and Group failed: %s") % str(e)) - - self.lastDownload = filename - return self.lastDownload - - def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0): - """ checks the content of the last downloaded file, re match is saved to `lastCheck` - - :param rules: dict with names and rules to match (compiled regexp or strings) - :param api_size: expected file size - :param max_size: if the file is larger then it wont be checked - :param delete: delete if matched - :param read_size: amount of bytes to read from files larger then max_size - :return: dictionary key of the first rule that matched - """ - lastDownload = fs_encode(self.lastDownload) - if not exists(lastDownload): return None - - size = stat(lastDownload) - size = size.st_size - - if api_size and api_size <= size: return None - elif size > max_size and not read_size: return None - self.log.debug("Download Check triggered") - f = open(lastDownload, "rb") - content = f.read(read_size if read_size else -1) - f.close() - #produces encoding errors, better log to other file in the future? - #self.log.debug("Content: %s" % content) - for name, rule in rules.iteritems(): - if type(rule) in (str, unicode): - if rule in content: - if delete: - remove(lastDownload) - return name - elif hasattr(rule, "search"): - m = rule.search(content) - if m: - if delete: - remove(lastDownload) - self.lastCheck = m - return name - - - def getPassword(self): - """ get the password the user provided in the package""" - password = self.pyfile.package().password - if not password: return "" - return password - - - def checkForSameFiles(self, starting=False): - """ checks if same file was/is downloaded within same package - - :param starting: indicates that the current download is going to start - :raises SkipDownload: - """ - - pack = self.pyfile.package() - - for pyfile in self.core.files.cache.values(): - if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder: - if pyfile.status in (0, 12): #finished or downloading - raise SkipDownload(pyfile.pluginname) - elif pyfile.status in ( - 5, 7) and starting: #a download is waiting/starting and was appenrently started before - raise SkipDownload(pyfile.pluginname) - - download_folder = self.config['general']['download_folder'] - location = save_join(download_folder, pack.folder, self.pyfile.name) - - if starting and self.core.config['download']['skip_existing'] and exists(location): - size = os.stat(location).st_size - if size >= self.pyfile.size: - raise SkipDownload("File exists.") - - pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name) - if pyfile: - if exists(location): - raise SkipDownload(pyfile[0]) - - self.log.debug("File %s not skipped, because it does not exists." % self.pyfile.name) - - def clean(self): - """ clean everything and remove references """ - if hasattr(self, "pyfile"): - del self.pyfile - if hasattr(self, "req"): - self.req.close() - del self.req - if hasattr(self, "thread"): - del self.thread - if hasattr(self, "html"): - del self.html diff --git a/module/plugins/PluginManager.py b/module/plugins/PluginManager.py deleted file mode 100644 index f3f5f47bc..000000000 --- a/module/plugins/PluginManager.py +++ /dev/null @@ -1,380 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay, RaNaN -""" - -import re -import sys - -from os import listdir, makedirs -from os.path import isfile, join, exists, abspath -from sys import version_info -from itertools import chain -from traceback import print_exc - -from module.lib.SafeEval import const_eval as literal_eval -from module.ConfigParser import IGNORE - -class PluginManager: - ROOT = "module.plugins." - USERROOT = "userplugins." - TYPES = ("crypter", "container", "hoster", "captcha", "accounts", "hooks", "internal") - - PATTERN = re.compile(r'__pattern__.*=.*r("|\')([^"\']+)') - VERSION = re.compile(r'__version__.*=.*("|\')([0-9.]+)') - CONFIG = re.compile(r'__config__.*=.*\[([^\]]+)', re.MULTILINE) - DESC = re.compile(r'__description__.?=.?("|"""|\')([^"\']+)') - - - def __init__(self, core): - self.core = core - - #self.config = self.core.config - self.log = core.log - - self.plugins = {} - self.createIndex() - - #register for import hook - sys.meta_path.append(self) - - - def createIndex(self): - """create information for all plugins available""" - - sys.path.append(abspath("")) - - if not exists("userplugins"): - makedirs("userplugins") - if not exists(join("userplugins", "__init__.py")): - f = open(join("userplugins", "__init__.py"), "wb") - f.close() - - self.plugins["crypter"] = self.crypterPlugins = self.parse("crypter", pattern=True) - self.plugins["container"] = self.containerPlugins = self.parse("container", pattern=True) - self.plugins["hoster"] = self.hosterPlugins = self.parse("hoster", pattern=True) - - self.plugins["captcha"] = self.captchaPlugins = self.parse("captcha") - self.plugins["accounts"] = self.accountPlugins = self.parse("accounts") - self.plugins["hooks"] = self.hookPlugins = self.parse("hooks") - self.plugins["internal"] = self.internalPlugins = self.parse("internal") - - self.log.debug("created index of plugins") - - def parse(self, folder, pattern=False, home={}): - """ - returns dict with information - home contains parsed plugins from module. - - { - name : {path, version, config, (pattern, re), (plugin, class)} - } - - """ - plugins = {} - if home: - pfolder = join("userplugins", folder) - if not exists(pfolder): - makedirs(pfolder) - if not exists(join(pfolder, "__init__.py")): - f = open(join(pfolder, "__init__.py"), "wb") - f.close() - - else: - pfolder = join(pypath, "module", "plugins", folder) - - for f in listdir(pfolder): - if (isfile(join(pfolder, f)) and f.endswith(".py") or f.endswith("_25.pyc") or f.endswith( - "_26.pyc") or f.endswith("_27.pyc")) and not f.startswith("_"): - data = open(join(pfolder, f)) - content = data.read() - data.close() - - if f.endswith("_25.pyc") and version_info[0:2] != (2, 5): - continue - elif f.endswith("_26.pyc") and version_info[0:2] != (2, 6): - continue - elif f.endswith("_27.pyc") and version_info[0:2] != (2, 7): - continue - - name = f[:-3] - if name[-1] == ".": name = name[:-4] - - version = self.VERSION.findall(content) - if version: - version = float(version[0][1]) - else: - version = 0 - - # home contains plugins from pyload root - if home and name in home: - if home[name]["v"] >= version: - continue - - if name in IGNORE or (folder, name) in IGNORE: - continue - - plugins[name] = {} - plugins[name]["v"] = version - - module = f.replace(".pyc", "").replace(".py", "") - - # the plugin is loaded from user directory - plugins[name]["user"] = True if home else False - plugins[name]["name"] = module - - if pattern: - pattern = self.PATTERN.findall(content) - - if pattern: - pattern = pattern[0][1] - else: - pattern = "^unmachtable$" - - plugins[name]["pattern"] = pattern - - try: - plugins[name]["re"] = re.compile(pattern) - except: - self.log.error(_("%s has a invalid pattern.") % name) - - - # internals have no config - if folder == "internal": - self.core.config.deleteConfig(name) - continue - - config = self.CONFIG.findall(content) - if config: - config = literal_eval(config[0].strip().replace("\n", "").replace("\r", "")) - desc = self.DESC.findall(content) - desc = desc[0][1] if desc else "" - - if type(config[0]) == tuple: - config = [list(x) for x in config] - else: - config = [list(config)] - - if folder == "hooks": - append = True - for item in config: - if item[0] == "activated": append = False - - # activated flag missing - if append: config.append(["activated", "bool", "Activated", False]) - - try: - self.core.config.addPluginConfig(name, config, desc) - except: - self.log.error("Invalid config in %s: %s" % (name, config)) - - elif folder == "hooks": #force config creation - desc = self.DESC.findall(content) - desc = desc[0][1] if desc else "" - config = (["activated", "bool", "Activated", False],) - - try: - self.core.config.addPluginConfig(name, config, desc) - except: - self.log.error("Invalid config in %s: %s" % (name, config)) - - if not home: - temp = self.parse(folder, pattern, plugins) - plugins.update(temp) - - return plugins - - - def parseUrls(self, urls): - """parse plugins for given list of urls""" - - last = None - res = [] # tupels of (url, plugin) - - for url in urls: - if type(url) not in (str, unicode, buffer): continue - found = False - - if last and last[1]["re"].match(url): - res.append((url, last[0])) - continue - - for name, value in chain(self.crypterPlugins.iteritems(), self.hosterPlugins.iteritems(), - self.containerPlugins.iteritems()): - if value["re"].match(url): - res.append((url, name)) - last = (name, value) - found = True - break - - if not found: - res.append((url, "BasePlugin")) - - return res - - def findPlugin(self, name, pluginlist=("hoster", "crypter", "container")): - for ptype in pluginlist: - if name in self.plugins[ptype]: - return self.plugins[ptype][name], ptype - return None, None - - def getPlugin(self, name, original=False): - """return plugin module from hoster|decrypter|container""" - plugin, type = self.findPlugin(name) - - if not plugin: - self.log.warning("Plugin %s not found." % name) - plugin = self.hosterPlugins["BasePlugin"] - - if "new_module" in plugin and not original: - return plugin["new_module"] - - return self.loadModule(type, name) - - def getPluginName(self, name): - """ used to obtain new name if other plugin was injected""" - plugin, type = self.findPlugin(name) - - if "new_name" in plugin: - return plugin["new_name"] - - return name - - def loadModule(self, type, name): - """ Returns loaded module for plugin - - :param type: plugin type, subfolder of module.plugins - :param name: - """ - plugins = self.plugins[type] - if name in plugins: - if "module" in plugins[name]: return plugins[name]["module"] - try: - module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]["name"]), globals(), locals(), - plugins[name]["name"]) - plugins[name]["module"] = module #cache import, maybe unneeded - return module - except Exception, e: - self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)}) - if self.core.debug: - print_exc() - - def loadClass(self, type, name): - """Returns the class of a plugin with the same name""" - module = self.loadModule(type, name) - if module: return getattr(module, name) - - def getAccountPlugins(self): - """return list of account plugin names""" - return self.accountPlugins.keys() - - def find_module(self, fullname, path=None): - #redirecting imports if necesarry - if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #seperate pyload plugins - if fullname.startswith(self.USERROOT): user = 1 - else: user = 0 #used as bool and int - - split = fullname.split(".") - if len(split) != 4 - user: return - type, name = split[2 - user:4 - user] - - if type in self.plugins and name in self.plugins[type]: - #userplugin is a newer version - if not user and self.plugins[type][name]["user"]: - return self - #imported from userdir, but pyloads is newer - if user and not self.plugins[type][name]["user"]: - return self - - - def load_module(self, name, replace=True): - if name not in sys.modules: #could be already in modules - if replace: - if self.ROOT in name: - newname = name.replace(self.ROOT, self.USERROOT) - else: - newname = name.replace(self.USERROOT, self.ROOT) - else: newname = name - - base, plugin = newname.rsplit(".", 1) - - self.log.debug("Redirected import %s -> %s" % (name, newname)) - - module = __import__(newname, globals(), locals(), [plugin]) - #inject under new an old name - sys.modules[name] = module - sys.modules[newname] = module - - return sys.modules[name] - - - def reloadPlugins(self, type_plugins): - """ reloads and reindexes plugins """ - if not type_plugins: return False - - self.log.debug("Request reload of plugins: %s" % type_plugins) - - as_dict = {} - for t,n in type_plugins: - if t in as_dict: - as_dict[t].append(n) - else: - as_dict[t] = [n] - - # we do not reload hooks or internals, would cause to much side effects - if "hooks" in as_dict or "internal" in as_dict: - return False - - for type in as_dict.iterkeys(): - for plugin in as_dict[type]: - if plugin in self.plugins[type]: - if "module" in self.plugins[type][plugin]: - self.log.debug("Reloading %s" % plugin) - reload(self.plugins[type][plugin]["module"]) - - #index creation - self.plugins["crypter"] = self.crypterPlugins = self.parse("crypter", pattern=True) - self.plugins["container"] = self.containerPlugins = self.parse("container", pattern=True) - self.plugins["hoster"] = self.hosterPlugins = self.parse("hoster", pattern=True) - self.plugins["captcha"] = self.captchaPlugins = self.parse("captcha") - self.plugins["accounts"] = self.accountPlugins = self.parse("accounts") - - if "accounts" in as_dict: #accounts needs to be reloaded - self.core.accountManager.initPlugins() - self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos) - - return True - - - -if __name__ == "__main__": - _ = lambda x: x - pypath = "/home/christian/Projekte/pyload-0.4/module/plugins" - - from time import time - - p = PluginManager(None) - - a = time() - - test = ["http://www.youtube.com/watch?v=%s" % x for x in range(0, 100)] - print p.parseUrls(test) - - b = time() - - print b - a, "s" - diff --git a/module/plugins/ReCaptcha.py b/module/plugins/ReCaptcha.py deleted file mode 100644 index 6f7ebe22c..000000000 --- a/module/plugins/ReCaptcha.py +++ /dev/null @@ -1,21 +0,0 @@ -import re - -class ReCaptcha(): - def __init__(self, plugin): - self.plugin = plugin - - def challenge(self, id): - js = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", get={"k":id}, cookies=True) - - try: - challenge = re.search("challenge : '(.*?)',", js).group(1) - server = re.search("server : '(.*?)',", js).group(1) - except: - self.plugin.fail("recaptcha error") - result = self.result(server,challenge) - - return challenge, result - - def result(self, server, challenge): - return self.plugin.decryptCaptcha("%simage"%server, get={"c":challenge}, cookies=True, imgtype="jpg") - diff --git a/module/plugins/accounts/DebridItaliaCom.py b/module/plugins/accounts/DebridItaliaCom.py deleted file mode 100644 index 82acd8f8e..000000000 --- a/module/plugins/accounts/DebridItaliaCom.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU Affero 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -import re -import time - -from module.plugins.Account import Account - - -class DebridItaliaCom(Account): - __name__ = "DebridItaliaCom" - __version__ = "0.1" - __type__ = "account" - __description__ = """debriditalia.com account plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - WALID_UNTIL_PATTERN = r"Premium valid till: (?P<D>[^|]+) \|" - - def loadAccountInfo(self, user, req): - if 'Account premium not activated' in self.html: - return {"premium": False, "validuntil": None, "trafficleft": None} - - m = re.search(self.WALID_UNTIL_PATTERN, self.html) - if m: - validuntil = int(time.mktime(time.strptime(m.group('D'), "%d/%m/%Y %H:%M"))) - return {"premium": True, "validuntil": validuntil, "trafficleft": -1} - else: - self.logError('Unable to retrieve account information - Plugin may be out of date') - - def login(self, user, data, req): - self.html = req.load("http://debriditalia.com/login.php", - get={"u": user, "p": data["password"]}) - if 'NO' in self.html: - self.wrongPassword() diff --git a/module/plugins/accounts/MultiDebridCom.py b/module/plugins/accounts/MultiDebridCom.py deleted file mode 100644 index 904be5ee7..000000000 --- a/module/plugins/accounts/MultiDebridCom.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# 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. # -# # -# 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -from time import time - -from module.plugins.Account import Account -from module.common.json_layer import json_loads - - -class MultiDebridCom(Account): - __name__ = "MultiDebridCom" - __version__ = "0.01" - __type__ = "account" - __description__ = """Multi-debrid.com account plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def loadAccountInfo(self, user, req): - if 'days_left' in self.json_data: - validuntil = int(time() + self.json_data['days_left'] * 86400) - return {"premium": True, "validuntil": validuntil, "trafficleft": -1} - else: - self.logError('Unable to get account information') - - def login(self, user, data, req): - # Password to use is the API-Password written in http://multi-debrid.com/myaccount - self.html = req.load("http://multi-debrid.com/api.php", - get={"user": user, "pass": data["password"]}) - self.logDebug('JSON data: ' + self.html) - self.json_data = json_loads(self.html) - if self.json_data['status'] != 'ok': - self.logError('Invalid login. The password to use is the API-Password you find in your "My Account" page') - self.wrongPassword() diff --git a/module/plugins/accounts/UnrestrictLi.py b/module/plugins/accounts/UnrestrictLi.py deleted file mode 100644 index 58fb0f7e5..000000000 --- a/module/plugins/accounts/UnrestrictLi.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 Affero 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -from module.plugins.Account import Account -from module.common.json_layer import json_loads - - -class UnrestrictLi(Account): - __name__ = "UnrestrictLi" - __version__ = "0.03" - __type__ = "account" - __description__ = """Unrestrict.li account plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def loadAccountInfo(self, user, req): - json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json') - self.logDebug("JSON data: " + json_data) - json_data = json_loads(json_data) - - if 'vip' in json_data['result'] and json_data['result']['vip'] == 0: - return {"premium": False} - - validuntil = json_data['result']['expires'] - trafficleft = int(json_data['result']['traffic'] / 1024) - - return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft} - - def login(self, user, data, req): - req.cj.setCookie("unrestrict.li", "lang", "EN") - html = req.load("https://unrestrict.li/sign_in") - - if 'solvemedia' in html: - self.logError("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry") - return - - post_data = {"username": user, "password": data["password"], - "remember_me": "remember", "signin": "Sign in"} - self.html = req.load("https://unrestrict.li/sign_in", post=post_data) - - if 'sign_out' not in self.html: - self.wrongPassword() 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/NetloadIn.py b/module/plugins/captcha/NetloadIn.py deleted file mode 100644 index 7f2e6a8d1..000000000 --- a/module/plugins/captcha/NetloadIn.py +++ /dev/null @@ -1,24 +0,0 @@ -from captcha import OCR - -class NetloadIn(OCR): - __name__ = "NetloadIn" - def __init__(self): - OCR.__init__(self) - - def get_captcha(self, image): - self.load_image(image) - self.to_greyscale() - self.clean(3) - self.clean(3) - self.run_tesser(True, True, False, False) - - self.result_captcha = self.result_captcha.replace(" ", "")[:4] # cut to 4 numbers - - return self.result_captcha - -if __name__ == '__main__': - import urllib - ocr = NetloadIn() - urllib.urlretrieve("http://netload.in/share/includes/captcha.php", "captcha.png") - - print ocr.get_captcha('captcha.png') diff --git a/module/plugins/captcha/ShareonlineBiz.py b/module/plugins/captcha/ShareonlineBiz.py deleted file mode 100644 index b07fb9b0f..000000000 --- a/module/plugins/captcha/ShareonlineBiz.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -#Copyright (C) 2009 kingzero, RaNaN -# -#This program is free software; you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation; either version 3 of the License, -#or (at your option) any later version. -# -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -#See the GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. -# -### -from captcha import OCR - -class ShareonlineBiz(OCR): - __name__ = "ShareonlineBiz" - - def __init__(self): - OCR.__init__(self) - - def get_captcha(self, image): - self.load_image(image) - self.to_greyscale() - self.image = self.image.resize((160, 50)) - self.pixels = self.image.load() - self.threshold(1.85) - #self.eval_black_white(240) - #self.derotate_by_average() - - letters = self.split_captcha_letters() - - final = "" - for letter in letters: - self.image = letter - self.run_tesser(True, True, False, False) - final += self.result_captcha - - return final - - #tesseract at 60% - -if __name__ == '__main__': - import urllib - ocr = ShareonlineBiz() - urllib.urlretrieve("http://www.share-online.biz/captcha.php", "captcha.jpeg") - print ocr.get_captcha('captcha.jpeg') diff --git a/module/plugins/captcha/captcha.py b/module/plugins/captcha/captcha.py deleted file mode 100644 index 4cbb736c1..000000000 --- a/module/plugins/captcha/captcha.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -#Copyright (C) 2009 kingzero, RaNaN -# -#This program is free software; you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation; either version 3 of the License, -#or (at your option) any later version. -# -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -#See the GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. -# -### -from __future__ import with_statement -import os -from os.path import join -from os.path import abspath -import logging -import subprocess -#import tempfile - -import Image -import TiffImagePlugin -import PngImagePlugin -import GifImagePlugin -import JpegImagePlugin - - -class OCR(object): - - __name__ = "OCR" - - def __init__(self): - self.logger = logging.getLogger("log") - - def load_image(self, image): - self.image = Image.open(image) - self.pixels = self.image.load() - self.result_captcha = '' - - def unload(self): - """delete all tmp images""" - pass - - def threshold(self, value): - self.image = self.image.point(lambda a: a * value + 10) - - def run(self, command): - """Run a command""" - - popen = subprocess.Popen(command, bufsize = -1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - popen.wait() - output = popen.stdout.read() +" | "+ popen.stderr.read() - popen.stdout.close() - popen.stderr.close() - self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output)) - - def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True): - #self.logger.debug("create tmp tif") - - - #tmp = tempfile.NamedTemporaryFile(suffix=".tif") - tmp = open(join("tmp", "tmpTif_%s.tif" % self.__name__), "wb") - tmp.close() - #self.logger.debug("create tmp txt") - #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt") - tmpTxt = open(join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb") - tmpTxt.close() - - self.logger.debug("save tiff") - self.image.save(tmp.name, 'TIFF') - - if os.name == "nt": - tessparams = [join(pypath,"tesseract","tesseract.exe")] - else: - tessparams = ['tesseract'] - - tessparams.extend( [abspath(tmp.name), abspath(tmpTxt.name).replace(".txt", "")] ) - - if subset and (digits or lowercase or uppercase): - #self.logger.debug("create temp subset config") - #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset") - tmpSub = open(join("tmp", "tmpSub_%s.subset" % self.__name__), "wb") - tmpSub.write("tessedit_char_whitelist ") - if digits: - tmpSub.write("0123456789") - if lowercase: - tmpSub.write("abcdefghijklmnopqrstuvwxyz") - if uppercase: - tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - tmpSub.write("\n") - tessparams.append("nobatch") - tessparams.append(abspath(tmpSub.name)) - tmpSub.close() - - self.logger.debug("run tesseract") - self.run(tessparams) - self.logger.debug("read txt") - - try: - with open(tmpTxt.name, 'r') as f: - self.result_captcha = f.read().replace("\n", "") - except: - self.result_captcha = "" - - self.logger.debug(self.result_captcha) - try: - os.remove(tmp.name) - os.remove(tmpTxt.name) - if subset and (digits or lowercase or uppercase): - os.remove(tmpSub.name) - except: - pass - - def get_captcha(self, name): - raise NotImplementedError - - def to_greyscale(self): - if self.image.mode != 'L': - self.image = self.image.convert('L') - - self.pixels = self.image.load() - - def eval_black_white(self, limit): - self.pixels = self.image.load() - w, h = self.image.size - for x in xrange(w): - for y in xrange(h): - if self.pixels[x, y] > limit: - self.pixels[x, y] = 255 - else: - self.pixels[x, y] = 0 - - def clean(self, allowed): - pixels = self.pixels - - w, h = self.image.size - - for x in xrange(w): - for y in xrange(h): - if pixels[x, y] == 255: continue - # no point in processing white pixels since we only want to remove black pixel - count = 0 - - try: - if pixels[x-1, y-1] != 255: count += 1 - if pixels[x-1, y] != 255: count += 1 - if pixels[x-1, y + 1] != 255: count += 1 - if pixels[x, y + 1] != 255: count += 1 - if pixels[x + 1, y + 1] != 255: count += 1 - if pixels[x + 1, y] != 255: count += 1 - if pixels[x + 1, y-1] != 255: count += 1 - if pixels[x, y-1] != 255: count += 1 - except: - pass - - # not enough neighbors are dark pixels so mark this pixel - # to be changed to white - if count < allowed: - pixels[x, y] = 1 - - # second pass: this time set all 1's to 255 (white) - for x in xrange(w): - for y in xrange(h): - if pixels[x, y] == 1: pixels[x, y] = 255 - - self.pixels = pixels - - def derotate_by_average(self): - """rotate by checking each angle and guess most suitable""" - - w, h = self.image.size - pixels = self.pixels - - for x in xrange(w): - for y in xrange(h): - if pixels[x, y] == 0: - pixels[x, y] = 155 - - highest = {} - counts = {} - - for angle in range(-45, 45): - - tmpimage = self.image.rotate(angle) - - pixels = tmpimage.load() - - w, h = self.image.size - - for x in xrange(w): - for y in xrange(h): - if pixels[x, y] == 0: - pixels[x, y] = 255 - - - count = {} - - for x in xrange(w): - count[x] = 0 - for y in xrange(h): - if pixels[x, y] == 155: - count[x] += 1 - - sum = 0 - cnt = 0 - - for x in count.values(): - if x != 0: - sum += x - cnt += 1 - - avg = sum / cnt - counts[angle] = cnt - highest[angle] = 0 - for x in count.values(): - if x > highest[angle]: - highest[angle] = x - - highest[angle] = highest[angle] - avg - - hkey = 0 - hvalue = 0 - - for key, value in highest.iteritems(): - if value > hvalue: - hkey = key - hvalue = value - - self.image = self.image.rotate(hkey) - pixels = self.image.load() - - for x in xrange(w): - for y in xrange(h): - if pixels[x, y] == 0: - pixels[x, y] = 255 - - if pixels[x, y] == 155: - pixels[x, y] = 0 - - self.pixels = pixels - - def split_captcha_letters(self): - captcha = self.image - started = False - letters = [] - width, height = captcha.size - bottomY, topY = 0, height - pixels = captcha.load() - - for x in xrange(width): - black_pixel_in_col = False - for y in xrange(height): - if pixels[x, y] != 255: - if not started: - started = True - firstX = x - lastX = x - - if y > bottomY: bottomY = y - if y < topY: topY = y - if x > lastX: lastX = x - - black_pixel_in_col = True - - if black_pixel_in_col == False and started == True: - rect = (firstX, topY, lastX, bottomY) - new_captcha = captcha.crop(rect) - - w, h = new_captcha.size - if w > 5 and h > 5: - letters.append(new_captcha) - - started = False - bottomY, topY = 0, height - - return letters - - def correct(self, values, var=None): - - if var: - result = var - else: - result = self.result_captcha - - for key, item in values.iteritems(): - - if key.__class__ == str: - result = result.replace(key, item) - else: - for expr in key: - result = result.replace(expr, item) - - if var: - return result - else: - self.result_captcha = result - - -if __name__ == '__main__': - ocr = OCR() - ocr.load_image("B.jpg") - ocr.to_greyscale() - ocr.eval_black_white(140) - ocr.derotate_by_average() - ocr.run_tesser() - print "Tesseract", ocr.result_captcha - ocr.image.save("derotated.jpg") - diff --git a/module/plugins/container/CCF.py b/module/plugins/container/CCF.py deleted file mode 100644 index 301b033d4..000000000 --- a/module/plugins/container/CCF.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -from urllib2 import build_opener - -from module.plugins.Container import Container -from module.lib.MultipartPostHandler import MultipartPostHandler - -from os import makedirs -from os.path import exists, join - -class CCF(Container): - __name__ = "CCF" - __version__ = "0.2" - __pattern__ = r"(?!http://).*\.ccf$" - __description__ = """CCF Container Convert Plugin""" - __author_name__ = ("Willnix") - __author_mail__ = ("Willnix@pyload.org") - - def decrypt(self, pyfile): - - infile = pyfile.url.replace("\n", "") - - opener = build_opener(MultipartPostHandler) - params = {"src": "ccf", - "filename": "test.ccf", - "upload": open(infile, "rb")} - tempdlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', params).read() - - download_folder = self.config['general']['download_folder'] - location = download_folder #join(download_folder, self.pyfile.package().folder.decode(sys.getfilesystemencoding())) - if not exists(location): - makedirs(location) - - tempdlc_name = join(location, "tmp_%s.dlc" % pyfile.name) - tempdlc = open(tempdlc_name, "w") - tempdlc.write(re.search(r'<dlc>(.*)</dlc>', tempdlc_content, re.DOTALL).group(1)) - tempdlc.close() - - self.packages.append((tempdlc_name, [tempdlc_name], tempdlc_name)) - diff --git a/module/plugins/container/DLC_25.pyc b/module/plugins/container/DLC_25.pyc Binary files differdeleted file mode 100644 index b8fde0051..000000000 --- a/module/plugins/container/DLC_25.pyc +++ /dev/null diff --git a/module/plugins/container/DLC_26.pyc b/module/plugins/container/DLC_26.pyc Binary files differdeleted file mode 100644 index 41a4e0cb8..000000000 --- a/module/plugins/container/DLC_26.pyc +++ /dev/null diff --git a/module/plugins/container/DLC_27.pyc b/module/plugins/container/DLC_27.pyc Binary files differdeleted file mode 100644 index a6bffaf74..000000000 --- a/module/plugins/container/DLC_27.pyc +++ /dev/null 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 deleted file mode 100644 index ea5cd67f2..000000000 --- a/module/plugins/container/RSDF.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import base64 -import binascii -import re - -from module.plugins.Container import Container - -class RSDF(Container): - __name__ = "RSDF" - __version__ = "0.21" - __pattern__ = r".*\.rsdf" - __description__ = """RSDF Container Decode Plugin""" - __author_name__ = ("RaNaN", "spoob") - __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org") - - - def decrypt(self, pyfile): - - from Crypto.Cipher import AES - - infile = pyfile.url.replace("\n", "") - Key = binascii.unhexlify('8C35192D964DC3182C6F84F3252239EB4A320D2500000000') - - IV = binascii.unhexlify('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') - IV_Cipher = AES.new(Key, AES.MODE_ECB) - IV = IV_Cipher.encrypt(IV) - - obj = AES.new(Key, AES.MODE_CFB, IV) - - rsdf = open(infile, 'r') - - data = rsdf.read() - rsdf.close() - - if re.search(r"<title>404 - Not Found</title>", data) is None: - data = binascii.unhexlify(''.join(data.split())) - data = data.splitlines() - - links = [] - for link in data: - link = base64.b64decode(link) - link = obj.decrypt(link) - decryptedUrl = link.replace('CCF: ', '') - links.append(decryptedUrl) - - self.log.debug("%s: adding package %s with %d links" % (self.__name__,pyfile.package().name,len(links))) - self.packages.append((pyfile.package().name, links)) diff --git a/module/plugins/crypter/HoerbuchIn.py b/module/plugins/crypter/HoerbuchIn.py deleted file mode 100644 index f8cb18b19..000000000 --- a/module/plugins/crypter/HoerbuchIn.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re - -from module.plugins.Crypter import Crypter -from module.lib.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup - - -class HoerbuchIn(Crypter): - __name__ = "HoerbuchIn" - __type__ = "container" - __pattern__ = r"http://(www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out.php\?.+|protection/folder_\d+\.html)" - __version__ = "0.6" - __description__ = """Hoerbuch.in Container Plugin""" - __author_name__ = ("spoob", "mkaay") - __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de") - - article = re.compile("http://(www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/") - protection = re.compile("http://(www\.)?hoerbuch\.in/protection/folder_\d+.html") - - def decrypt(self, pyfile): - self.pyfile = pyfile - - if self.article.match(self.pyfile.url): - src = self.load(self.pyfile.url) - soup = BeautifulSoup(src, convertEntities=BeautifulStoneSoup.HTML_ENTITIES) - - abookname = soup.find("a", attrs={"rel": "bookmark"}).text - for a in soup.findAll("a", attrs={"href": self.protection}): - package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1]) - links = self.decryptFolder(a["href"]) - - self.packages.append((package, links, self.pyfile.package().folder)) - else: - links = self.decryptFolder(self.pyfile.url) - - self.packages.append((self.pyfile.package().name, links, self.pyfile.package().folder)) - - def decryptFolder(self, url): - m = self.protection.search(url) - if not m: - self.fail("Bad URL") - url = m.group(0) - - self.pyfile.url = url - src = self.req.load(url, post={"viewed": "adpg"}) - - links = [] - pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"") - for hoster, lid in pattern.findall(src): - self.req.lastURL = url - self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid)) - links.append(self.req.lastEffectiveURL) - - return links diff --git a/module/plugins/crypter/SerienjunkiesOrg.py b/module/plugins/crypter/SerienjunkiesOrg.py deleted file mode 100644 index e0eb7e240..000000000 --- a/module/plugins/crypter/SerienjunkiesOrg.py +++ /dev/null @@ -1,320 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -from time import sleep -import random -from module.plugins.Crypter import Crypter -from module.lib.BeautifulSoup import BeautifulSoup -from module.unescape import unescape - - -class SerienjunkiesOrg(Crypter): - __name__ = "SerienjunkiesOrg" - __type__ = "container" - __pattern__ = r"http://.*?(serienjunkies.org|dokujunkies.org)/.*?" - __version__ = "0.39" - __config__ = [ - ("changeNameSJ", "Packagename;Show;Season;Format;Episode", "Take SJ.org name", "Show"), - ("changeNameDJ", "Packagename;Show;Format;Episode", "Take DJ.org name", "Show"), - ("randomPreferred", "bool", "Randomize Preferred-List", False), - ("hosterListMode", "OnlyOne;OnlyPreferred(One);OnlyPreferred(All);All", - "Use for hosters (if supported)", "All"), - ("hosterList", "str", "Preferred Hoster list (comma separated)", - "RapidshareCom,UploadedTo,NetloadIn,FilefactoryCom,FreakshareNet,FilebaseTo,HotfileCom,DepositfilesCom,EasyshareCom,KickloadCom"), - ("ignoreList", "str", "Ignored Hoster list (comma separated)", "MegauploadCom") - ] - __description__ = """serienjunkies.org Container Plugin""" - __author_name__ = ("mkaay", "godofdream") - __author_mail__ = ("mkaay@mkaay.de", "soilfiction@gmail.com") - - def setup(self): - self.multiDL = False - - def getSJSrc(self, url): - src = self.req.load(str(url)) - if "This website is not available in your country" in src: - self.fail("Not available in your country") - if not src.find("Enter Serienjunkies") == -1: - sleep(1) - src = self.req.load(str(url)) - return src - - def handleShow(self, url): - src = self.getSJSrc(url) - soup = BeautifulSoup(src) - packageName = self.pyfile.package().name - if self.getConfig("changeNameSJ") == "Show": - found = unescape(soup.find("h2").find("a").string.split(' –')[0]) - if found: - packageName = found - - nav = soup.find("div", attrs={"id": "scb"}) - - package_links = [] - for a in nav.findAll("a"): - if self.getConfig("changeNameSJ") == "Show": - package_links.append(a["href"]) - else: - package_links.append(a["href"] + "#hasName") - if self.getConfig("changeNameSJ") == "Show": - self.packages.append((packageName, package_links, packageName)) - else: - self.core.files.addLinks(package_links, self.pyfile.package().id) - - def handleSeason(self, url): - src = self.getSJSrc(url) - soup = BeautifulSoup(src) - post = soup.find("div", attrs={"class": "post-content"}) - ps = post.findAll("p") - - seasonName = unescape(soup.find("a", attrs={"rel": "bookmark"}).string).replace("–", "-") - groups = {} - gid = -1 - for p in ps: - if re.search("<strong>Sprache|<strong>Format", str(p)): - var = p.findAll("strong") - opts = {"Sprache": "", "Format": ""} - for v in var: - n = unescape(v.string).strip() - n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n) - if n.strip() not in opts: - continue - val = v.nextSibling - if not val: - continue - val = val.replace("|", "").strip() - val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val) - opts[n.strip()] = val.strip() - gid += 1 - groups[gid] = {} - groups[gid]["ep"] = {} - groups[gid]["opts"] = opts - elif re.search("<strong>Download:", str(p)): - parts = str(p).split("<br />") - if re.search("<strong>", parts[0]): - ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace( - "–", "-") - groups[gid]["ep"][ename] = {} - parts.remove(parts[0]) - for part in parts: - hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part) - if hostername: - hostername = hostername.group(1) - groups[gid]["ep"][ename][hostername] = [] - links = re.findall('href="(.*?)"', part) - for link in links: - groups[gid]["ep"][ename][hostername].append(link + "#hasName") - - links = [] - for g in groups.values(): - for ename in g["ep"]: - links.extend(self.getpreferred(g["ep"][ename])) - if self.getConfig("changeNameSJ") == "Episode": - self.packages.append((ename, links, ename)) - links = [] - package = "%s (%s, %s)" % (seasonName, g["opts"]["Format"], g["opts"]["Sprache"]) - if self.getConfig("changeNameSJ") == "Format": - self.packages.append((package, links, package)) - links = [] - if (self.getConfig("changeNameSJ") == "Packagename") or re.search("#hasName", url): - self.core.files.addLinks(links, self.pyfile.package().id) - elif (self.getConfig("changeNameSJ") == "Season") or not re.search("#hasName", url): - self.packages.append((seasonName, links, seasonName)) - - def handleEpisode(self, url): - src = self.getSJSrc(url) - if not src.find( - "Du hast das Download-Limit überschritten! Bitte versuche es später nocheinmal.") == -1: - self.fail(_("Downloadlimit reached")) - else: - soup = BeautifulSoup(src) - form = soup.find("form") - h1 = soup.find("h1") - - if h1.get("class") == "wrap": - captchaTag = soup.find(attrs={"src": re.compile("^/secure/")}) - if not captchaTag: - sleep(5) - self.retry() - - captchaUrl = "http://download.serienjunkies.org" + captchaTag["src"] - result = self.decryptCaptcha(str(captchaUrl), imgtype="png") - sinp = form.find(attrs={"name": "s"}) - - self.req.lastURL = str(url) - sj = self.load(str(url), post={'s': sinp["value"], 'c': result, 'action': "Download"}) - - soup = BeautifulSoup(sj) - rawLinks = soup.findAll(attrs={"action": re.compile("^http://download.serienjunkies.org/")}) - - if not len(rawLinks) > 0: - sleep(1) - self.retry() - return - - self.correctCaptcha() - - links = [] - for link in rawLinks: - frameUrl = link["action"].replace("/go-", "/frame/go-") - links.append(self.handleFrame(frameUrl)) - if re.search("#hasName", url) or ((self.getConfig("changeNameSJ") == "Packagename") and - (self.getConfig("changeNameDJ") == "Packagename")): - self.core.files.addLinks(links, self.pyfile.package().id) - else: - if h1.text[2] == "_": - eName = h1.text[3:] - else: - eName = h1.text - self.packages.append((eName, links, eName)) - - def handleOldStyleLink(self, url): - sj = self.req.load(str(url)) - soup = BeautifulSoup(sj) - form = soup.find("form", attrs={"action": re.compile("^http://serienjunkies.org")}) - captchaTag = form.find(attrs={"src": re.compile("^/safe/secure/")}) - captchaUrl = "http://serienjunkies.org" + captchaTag["src"] - result = self.decryptCaptcha(str(captchaUrl)) - url = form["action"] - sinp = form.find(attrs={"name": "s"}) - - self.req.load(str(url), post={'s': sinp["value"], 'c': result, 'dl.start': "Download"}, cookies=False, - just_header=True) - decrypted = self.req.lastEffectiveURL - if decrypted == str(url): - self.retry() - self.core.files.addLinks([decrypted], self.pyfile.package().id) - - def handleFrame(self, url): - self.req.load(str(url)) - return self.req.lastEffectiveURL - - def handleShowDJ(self, url): - src = self.getSJSrc(url) - soup = BeautifulSoup(src) - post = soup.find("div", attrs={"id": "page_post"}) - ps = post.findAll("p") - found = unescape(soup.find("h2").find("a").string.split(' –')[0]) - if found: - seasonName = found - - groups = {} - gid = -1 - for p in ps: - if re.search("<strong>Sprache|<strong>Format", str(p)): - var = p.findAll("strong") - opts = {"Sprache": "", "Format": ""} - for v in var: - n = unescape(v.string).strip() - n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n) - if n.strip() not in opts: - continue - val = v.nextSibling - if not val: - continue - val = val.replace("|", "").strip() - val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val) - opts[n.strip()] = val.strip() - gid += 1 - groups[gid] = {} - groups[gid]["ep"] = {} - groups[gid]["opts"] = opts - elif re.search("<strong>Download:", str(p)): - parts = str(p).split("<br />") - if re.search("<strong>", parts[0]): - ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace( - "–", "-") - groups[gid]["ep"][ename] = {} - parts.remove(parts[0]) - for part in parts: - hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part) - if hostername: - hostername = hostername.group(1) - groups[gid]["ep"][ename][hostername] = [] - links = re.findall('href="(.*?)"', part) - for link in links: - groups[gid]["ep"][ename][hostername].append(link + "#hasName") - - links = [] - for g in groups.values(): - for ename in g["ep"]: - links.extend(self.getpreferred(g["ep"][ename])) - if self.getConfig("changeNameDJ") == "Episode": - self.packages.append((ename, links, ename)) - links = [] - package = "%s (%s, %s)" % (seasonName, g["opts"]["Format"], g["opts"]["Sprache"]) - if self.getConfig("changeNameDJ") == "Format": - self.packages.append((package, links, package)) - links = [] - if (self.getConfig("changeNameDJ") == "Packagename") or re.search("#hasName", url): - self.core.files.addLinks(links, self.pyfile.package().id) - elif (self.getConfig("changeNameDJ") == "Show") or not re.search("#hasName", url): - self.packages.append((seasonName, links, seasonName)) - - def handleCategoryDJ(self, url): - package_links = [] - src = self.getSJSrc(url) - soup = BeautifulSoup(src) - content = soup.find("div", attrs={"id": "content"}) - for a in content.findAll("a", attrs={"rel": "bookmark"}): - package_links.append(a["href"]) - self.core.files.addLinks(package_links, self.pyfile.package().id) - - def decrypt(self, pyfile): - showPattern = re.compile("^http://serienjunkies.org/serie/(.*)/$") - seasonPattern = re.compile("^http://serienjunkies.org/.*?/(.*)/$") - episodePattern = re.compile("^http://download.serienjunkies.org/f-.*?.html(#hasName)?$") - oldStyleLink = re.compile("^http://serienjunkies.org/safe/(.*)$") - categoryPatternDJ = re.compile("^http://dokujunkies.org/.*?(.*)$") - showPatternDJ = re.compile(r"^http://dokujunkies.org/.*?/(.*)\.html(#hasName)?$") - framePattern = re.compile("^http://download.(serienjunkies.org|dokujunkies.org)/frame/go-.*?/$") - url = pyfile.url - if framePattern.match(url): - self.packages.append((self.pyfile.package().name, [self.handleFrame(url)], self.pyfile.package().name)) - elif episodePattern.match(url): - self.handleEpisode(url) - elif oldStyleLink.match(url): - self.handleOldStyleLink(url) - elif showPattern.match(url): - self.handleShow(url) - elif showPatternDJ.match(url): - self.handleShowDJ(url) - elif seasonPattern.match(url): - self.handleSeason(url) - elif categoryPatternDJ.match(url): - self.handleCategoryDJ(url) - - #selects the preferred hoster, after that selects any hoster (ignoring the one to ignore) - def getpreferred(self, hosterlist): - - result = [] - preferredList = self.getConfig("hosterList").strip().lower().replace( - '|', ',').replace('.', '').replace(';', ',').split(',') - if (self.getConfig("randomPreferred") is True) and ( - self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]): - random.shuffle(preferredList) - # we don't want hosters be read two times - hosterlist2 = hosterlist.copy() - - for preferred in preferredList: - for Hoster in hosterlist: - if preferred == Hoster.lower().replace('.', ''): - for Part in hosterlist[Hoster]: - self.logDebug("selected " + Part) - result.append(str(Part)) - del (hosterlist2[Hoster]) - if self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]: - return result - - ignorelist = self.getConfig("ignoreList").strip().lower().replace( - '|', ',').replace('.', '').replace(';', ',').split(',') - if self.getConfig('hosterListMode') in ["OnlyOne", "All"]: - for Hoster in hosterlist2: - if Hoster.strip().lower().replace('.', '') not in ignorelist: - for Part in hosterlist2[Hoster]: - self.logDebug("selected2 " + Part) - result.append(str(Part)) - - if self.getConfig('hosterListMode') == "OnlyOne": - return result - return result diff --git a/module/plugins/hooks/IRCInterface.py b/module/plugins/hooks/IRCInterface.py deleted file mode 100644 index 8dadf08ed..000000000 --- a/module/plugins/hooks/IRCInterface.py +++ /dev/null @@ -1,422 +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 - @author: jeix - @interface-version: 0.2 -""" - -from select import select -import socket -from threading import Thread -import time -from time import sleep -from traceback import print_exc -import re -from pycurl import FORM_FILE - -from module.plugins.Hook import Hook -from module.network.RequestFactory import getURL -from module.utils import formatSize -from module.Api import PackageDoesNotExists, FileDoesNotExists - - -class IRCInterface(Thread, Hook): - __name__ = "IRCInterface" - __version__ = "0.11" - __description__ = """connect to irc and let owner perform different tasks""" - __config__ = [("activated", "bool", "Activated", "False"), - ("host", "str", "IRC-Server Address", "Enter your server here!"), - ("port", "int", "IRC-Server Port", "6667"), - ("ident", "str", "Clients ident", "pyload-irc"), - ("realname", "str", "Realname", "pyload-irc"), - ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"), - ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"), - ("info_file", "bool", "Inform about every file finished", "False"), - ("info_pack", "bool", "Inform about every package finished", "True"), - ("captcha", "bool", "Send captcha requests", "True")] - __author_name__ = ("Jeix") - __author_mail__ = ("Jeix@hasnomail.com") - - def __init__(self, core, manager): - Thread.__init__(self) - Hook.__init__(self, core, manager) - self.setDaemon(True) - # self.sm = core.server_methods - self.api = core.api # todo, only use api - - def coreReady(self): - self.new_package = {} - - self.abort = False - - self.links_added = 0 - self.more = [] - - self.start() - - def packageFinished(self, pypack): - try: - if self.getConfig("info_pack"): - self.response(_("Package finished: %s") % pypack.name) - except: - pass - - def downloadFinished(self, pyfile): - try: - if self.getConfig("info_file"): - self.response( - _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname}) - except: - pass - - def newCaptchaTask(self, task): - if self.getConfig("captcha") and task.isTextual(): - task.handler.append(self) - task.setWaiting(60) - - page = getURL("http://www.freeimagehosting.net/upload.php", - post={"attached": (FORM_FILE, task.captchaFile)}, multipart=True) - - url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).group(1) - self.response(_("New Captcha Request: %s") % url) - self.response(_("Answer with 'c %s text on the captcha'") % task.id) - - def run(self): - # connect to IRC etc. - self.sock = socket.socket() - host = self.getConfig("host") - self.sock.connect((host, self.getConfig("port"))) - nick = self.getConfig("nick") - self.sock.send("NICK %s\r\n" % nick) - self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick)) - for t in self.getConfig("owner").split(): - if t.strip().startswith("#"): - self.sock.send("JOIN %s\r\n" % t.strip()) - self.logInfo("pyLoad IRC: Connected to %s!" % host) - self.logInfo("pyLoad IRC: Switching to listening mode!") - try: - self.main_loop() - - except IRCError, ex: - self.sock.send("QUIT :byebye\r\n") - print_exc() - self.sock.close() - - def main_loop(self): - readbuffer = "" - while True: - sleep(1) - fdset = select([self.sock], [], [], 0) - if self.sock not in fdset[0]: - continue - - if self.abort: - raise IRCError("quit") - - readbuffer += self.sock.recv(1024) - temp = readbuffer.split("\n") - readbuffer = temp.pop() - - for line in temp: - line = line.rstrip() - first = line.split() - - if first[0] == "PING": - self.sock.send("PONG %s\r\n" % first[1]) - - if first[0] == "ERROR": - raise IRCError(line) - - msg = line.split(None, 3) - if len(msg) < 4: - continue - - msg = { - "origin": msg[0][1:], - "action": msg[1], - "target": msg[2], - "text": msg[3][1:] - } - - self.handle_events(msg) - - def handle_events(self, msg): - if not msg["origin"].split("!", 1)[0] in self.getConfig("owner").split(): - return - - if msg["target"].split("!", 1)[0] != self.getConfig("nick"): - return - - if msg["action"] != "PRIVMSG": - return - - # HANDLE CTCP ANTI FLOOD/BOT PROTECTION - if msg["text"] == "\x01VERSION\x01": - self.logDebug("Sending CTCP VERSION.") - self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface")) - return - elif msg["text"] == "\x01TIME\x01": - self.logDebug("Sending CTCP TIME.") - self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time())) - return - elif msg["text"] == "\x01LAG\x01": - self.logDebug("Received CTCP LAG.") # don't know how to answer - return - - trigger = "pass" - args = None - - try: - temp = msg["text"].split() - trigger = temp[0] - if len(temp) > 1: - args = temp[1:] - except: - pass - - handler = getattr(self, "event_%s" % trigger, self.event_pass) - try: - res = handler(args) - for line in res: - self.response(line, msg["origin"]) - except Exception, e: - self.logError("pyLoad IRC: " + repr(e)) - - def response(self, msg, origin=""): - if origin == "": - for t in self.getConfig("owner").split(): - self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg)) - else: - self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg)) - - #### Events - - def event_pass(self, args): - return [] - - def event_status(self, args): - downloads = self.api.statusDownloads() - if not downloads: - return ["INFO: There are no active downloads currently."] - - temp_progress = "" - lines = ["ID - Name - Status - Speed - ETA - Progress"] - for data in downloads: - - if data.status == 5: - temp_progress = data.format_wait - else: - temp_progress = "%d%% (%s)" % (data.percent, data.format_size) - - lines.append("#%d - %s - %s - %s - %s - %s" % - ( - data.fid, - data.name, - data.statusmsg, - "%s/s" % formatSize(data.speed), - "%s" % data.format_eta, - temp_progress - )) - return lines - - def event_queue(self, args): - ps = self.api.getQueueData() - - if not ps: - return ["INFO: There are no packages in queue."] - - lines = [] - for pack in ps: - lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) - - return lines - - def event_collector(self, args): - ps = self.api.getCollectorData() - if not ps: - return ["INFO: No packages in collector!"] - - lines = [] - for pack in ps: - lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) - - return lines - - def event_info(self, args): - if not args: - return ['ERROR: Use info like this: info <id>'] - - info = None - try: - info = self.api.getFileData(int(args[0])) - - except FileDoesNotExists: - return ["ERROR: Link doesn't exists."] - - return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)] - - def event_packinfo(self, args): - if not args: - return ['ERROR: Use packinfo like this: packinfo <id>'] - - lines = [] - pack = None - try: - pack = self.api.getPackageData(int(args[0])) - - except PackageDoesNotExists: - return ["ERROR: Package doesn't exists."] - - id = args[0] - - self.more = [] - - lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links))) - for pyfile in pack.links: - self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size, - pyfile.statusmsg, pyfile.plugin)) - - if len(self.more) < 6: - lines.extend(self.more) - self.more = [] - else: - lines.extend(self.more[:6]) - self.more = self.more[6:] - lines.append("%d more links do display." % len(self.more)) - - return lines - - def event_more(self, args): - if not self.more: - return ["No more information to display."] - - lines = self.more[:6] - self.more = self.more[6:] - lines.append("%d more links do display." % len(self.more)) - - return lines - - def event_start(self, args): - - self.api.unpauseServer() - return ["INFO: Starting downloads."] - - def event_stop(self, args): - - self.api.pauseServer() - return ["INFO: No new downloads will be started."] - - def event_add(self, args): - if len(args) < 2: - return ['ERROR: Add links like this: "add <packagename|id> links". ', - 'This will add the link <link> to to the package <package> / the package with id <id>!'] - - pack = args[0].strip() - links = [x.strip() for x in args[1:]] - - count_added = 0 - count_failed = 0 - try: - id = int(pack) - pack = self.api.getPackageData(id) - if not pack: - return ["ERROR: Package doesn't exists."] - - #TODO add links - - return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack["name"], id)] - - except: - # create new package - id = self.api.addPackage(pack, links, 1) - return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))] - - def event_del(self, args): - if len(args) < 2: - return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] - - if args[0] == "-p": - ret = self.api.deletePackages(map(int, args[1:])) - return ["INFO: Deleted %d packages!" % len(args[1:])] - - elif args[0] == "-l": - ret = self.api.delLinks(map(int, args[1:])) - return ["INFO: Deleted %d links!" % len(args[1:])] - - else: - return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] - - def event_push(self, args): - if not args: - return ["ERROR: Push package to queue like this: push <package id>"] - - id = int(args[0]) - try: - info = self.api.getPackageInfo(id) - except PackageDoesNotExists: - return ["ERROR: Package #%d does not exist." % id] - - self.api.pushToQueue(id) - return ["INFO: Pushed package #%d to queue." % id] - - def event_pull(self, args): - if not args: - return ["ERROR: Pull package from queue like this: pull <package id>."] - - id = int(args[0]) - if not self.api.getPackageData(id): - return ["ERROR: Package #%d does not exist." % id] - - self.api.pullFromQueue(id) - return ["INFO: Pulled package #%d from queue to collector." % id] - - def event_c(self, args): - """ captcha answer """ - if not args: - return ["ERROR: Captcha ID missing."] - - task = self.core.captchaManager.getTaskByID(args[0]) - if not task: - return ["ERROR: Captcha Task with ID %s does not exists." % args[0]] - - task.setResult(" ".join(args[1:])) - return ["INFO: Result %s saved." % " ".join(args[1:])] - - def event_help(self, args): - lines = ["The following commands are available:", - "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)", - "queue Shows all packages in the queue", - "collector Shows all packages in collector", - "del -p|-l <id> [...] Deletes all packages|links with the ids specified", - "info <id> Shows info of the link with id <id>", - "packinfo <id> Shows info of the package with id <id>", - "more Shows more info when the result was truncated", - "start Starts all downloads", - "stop Stops the download (but not abort active downloads)", - "push <id> Push package to queue", - "pull <id> Pull package from queue", - "status Show general download status", - "help Shows this help message"] - return lines - - -class IRCError(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/module/plugins/hooks/MultiDebridCom.py b/module/plugins/hooks/MultiDebridCom.py deleted file mode 100644 index c95138648..000000000 --- a/module/plugins/hooks/MultiDebridCom.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# 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. # -# # -# 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -from module.plugins.internal.MultiHoster import MultiHoster -from module.network.RequestFactory import getURL -from module.common.json_layer import json_loads - - -class MultiDebridCom(MultiHoster): - __name__ = "MultiDebridCom" - __version__ = "0.01" - __type__ = "hook" - __config__ = [("activated", "bool", "Activated", "False"), - ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), - ("hosterList", "str", "Hoster list (comma separated)", ""), - ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), - ("interval", "int", "Reload interval in hours (0 to disable)", "24")] - - __description__ = """Multi-debrid.com hook plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def getHoster(self): - json_data = getURL('http://multi-debrid.com/api.php?hosts', decode=True) - self.logDebug('JSON data: ' + json_data) - json_data = json_loads(json_data) - - return json_data['hosts'] diff --git a/module/plugins/hooks/UnrestrictLi.py b/module/plugins/hooks/UnrestrictLi.py deleted file mode 100644 index 0810a22d5..000000000 --- a/module/plugins/hooks/UnrestrictLi.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# 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. # -# # -# 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -from module.plugins.internal.MultiHoster import MultiHoster -from module.network.RequestFactory import getURL -from module.common.json_layer import json_loads - - -class UnrestrictLi(MultiHoster): - __name__ = "UnrestrictLi" - __version__ = "0.02" - __type__ = "hook" - __config__ = [("activated", "bool", "Activated", "False"), - ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), - ("hosterList", "str", "Hoster list (comma separated)", ""), - ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), - ("interval", "int", "Reload interval in hours (0 to disable)", "24"), - ("history", "bool", "Delete History", "False")] - - __description__ = """Unrestrict.li hook plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def getHoster(self): - json_data = getURL('http://unrestrict.li/api/jdownloader/hosts.php?format=json') - json_data = json_loads(json_data) - - host_list = [element['host'] for element in json_data['result']] - - return host_list diff --git a/module/plugins/hoster/BezvadataCz.py b/module/plugins/hoster/BezvadataCz.py deleted file mode 100644 index d923a0633..000000000 --- a/module/plugins/hoster/BezvadataCz.py +++ /dev/null @@ -1,100 +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: zoidberg -""" - -import re -from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo - - -class BezvadataCz(SimpleHoster): - __name__ = "BezvadataCz" - __type__ = "hoster" - __pattern__ = r"http://(\w*\.)*bezvadata.cz/stahnout/.*" - __version__ = "0.24" - __description__ = """BezvaData.cz""" - __author_name__ = ("zoidberg") - __author_mail__ = ("zoidberg@mujmail.cz") - - FILE_NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>' - FILE_SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>' - FILE_OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>' - - def setup(self): - self.multiDL = self.resumeDownload = True - - def handleFree(self): - #download button - found = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html) - if not found: - self.parseError("page1 URL") - url = "http://bezvadata.cz%s" % found.group(1) - - #captcha form - self.html = self.load(url) - self.checkErrors() - for i in range(5): - action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm') - if not inputs: - self.parseError("FreeForm") - - found = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html) - if not found: - self.parseError("captcha img") - - #captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url - self.load, proper_load = self.loadcaptcha, self.load - try: - inputs['captcha'] = self.decryptCaptcha(found.group(1), imgtype='png') - finally: - self.load = proper_load - - if '<img src="data:image/png;base64' in self.html: - self.invalidCaptcha() - else: - self.correctCaptcha() - break - else: - self.fail("No valid captcha code entered") - - #download url - self.html = self.load("http://bezvadata.cz%s" % action, post=inputs) - self.checkErrors() - found = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html) - if not found: - self.parseError("page2 URL") - url = "http://bezvadata.cz%s" % found.group(1) - self.logDebug("DL URL %s" % url) - - #countdown - found = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html) - wait_time = (int(found.group(1)) * 60 + int(found.group(2)) + 1) if found else 120 - self.setWait(wait_time, False) - self.wait() - - self.download(url) - - def checkErrors(self): - if 'images/button-download-disable.png' in self.html: - self.longWait(300, 24) # parallel dl limit - elif '<div class="infobox' in self.html: - self.tempOffline() - - def loadcaptcha(self, data, *args, **kwargs): - return data.decode("base64") - - -getInfo = create_getInfo(BezvadataCz) diff --git a/module/plugins/hoster/DebridItaliaCom.py b/module/plugins/hoster/DebridItaliaCom.py deleted file mode 100644 index 08470b984..000000000 --- a/module/plugins/hoster/DebridItaliaCom.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# 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. # -# # -# 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -import re - -from module.plugins.Hoster import Hoster - - -class DebridItaliaCom(Hoster): - __name__ = "DebridItaliaCom" - __version__ = "0.05" - __type__ = "hoster" - __pattern__ = r"https?://.*debriditalia\.com" - __description__ = """Debriditalia.com hoster plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def setup(self): - self.chunkLimit = -1 - self.resumeDownload = True - - def process(self, pyfile): - if re.match(self.__pattern__, pyfile.url): - new_url = pyfile.url - elif not self.account: - self.logError(_("Please enter your %s account or deactivate this plugin") % "DebridItalia") - self.fail("No DebridItalia account provided") - else: - self.logDebug("Old URL: %s" % pyfile.url) - url = "http://debriditalia.com/linkgen2.php?xjxfun=convertiLink&xjxargs[]=S<![CDATA[%s]]>" % pyfile.url - page = self.load(url) - self.logDebug("XML data: %s" % page) - - if 'File not available' in page: - self.fail('File not available') - else: - new_url = re.search(r'<a href="(?:[^"]+)">(?P<direct>[^<]+)</a>', page).group('direct') - - if new_url != pyfile.url: - self.logDebug("New URL: %s" % new_url) - - self.download(new_url, disposition=True) - - check = self.checkDownload({"empty": re.compile(r"^$")}) - - if check == "empty": - self.retry(5, 120, 'Empty file downloaded') diff --git a/module/plugins/hoster/FilesMailRu.py b/module/plugins/hoster/FilesMailRu.py deleted file mode 100644 index 720013657..000000000 --- a/module/plugins/hoster/FilesMailRu.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -from module.plugins.Hoster import Hoster -from module.network.RequestFactory import getURL -from module.plugins.Plugin import chunks - - -def getInfo(urls): - result = [] - for chunk in chunks(urls, 10): - for url in chunk: - src = getURL(url) - if r'<div class="errorMessage mb10">' in src: - result.append((url, 0, 1, url)) - elif r'Page cannot be displayed' in src: - result.append((url, 0, 1, url)) - else: - try: - url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' - file_name = re.search(url_pattern, src).group(0).split(', event)">')[1].split('</a>')[0] - result.append((file_name, 0, 2, url)) - except: - pass - - # status 1=OFFLINE, 2=OK, 3=UNKNOWN - # result.append((#name,#size,#status,#url)) - yield result - - -class FilesMailRu(Hoster): - __name__ = "FilesMailRu" - __type__ = "hoster" - __pattern__ = r"http://files\.mail\.ru/.*" - __version__ = "0.31" - __description__ = """Files.Mail.Ru One-Klick Hoster""" - __author_name__ = ("oZiRiz") - __author_mail__ = ("ich@oziriz.de") - - def setup(self): - if not self.account: - self.multiDL = False - - def process(self, pyfile): - self.html = self.load(pyfile.url) - self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' - - #marks the file as "offline" when the pattern was found on the html-page''' - if r'<div class="errorMessage mb10">' in self.html: - self.offline() - - elif r'Page cannot be displayed' in self.html: - self.offline() - - #the filename that will be showed in the list (e.g. test.part1.rar)''' - pyfile.name = self.getFileName() - - #prepare and download''' - if not self.account: - self.prepare() - self.download(self.getFileUrl()) - self.myPostProcess() - else: - self.download(self.getFileUrl()) - self.myPostProcess() - - def prepare(self): - """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected""" - self.setWait(10) - self.wait() - return True - - def getFileUrl(self): - """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html""" - file_url = re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[ - 0] - return file_url - - def getFileName(self): - """gives you the Name for each file. Also extracted from the HTML-Page""" - file_name = re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0] - return file_name - - def myPostProcess(self): - # searches the file for HTMl-Code. Sometimes the Redirect - # doesn't work (maybe a curl Problem) and you get only a small - # HTML file and the Download is marked as "finished" - # then the download will be restarted. It's only bad for these - # who want download a HTML-File (it's one in a million ;-) ) - # - # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB - # so i set it to check every download because sometimes there are downloads - # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file - # (Loading 100MB in to ram is not an option) - check = self.checkDownload({"html": "<meta name="}, read_size=50000) - if check == "html": - self.logInfo(_( - "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." % - self.pyfile.name)) - self.retry() diff --git a/module/plugins/hoster/HotfileCom.py b/module/plugins/hoster/HotfileCom.py deleted file mode 100644 index 6053ec1b6..000000000 --- a/module/plugins/hoster/HotfileCom.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -from module.plugins.Hoster import Hoster -from module.plugins.internal.CaptchaService import ReCaptcha - -from module.network.RequestFactory import getURL -from module.plugins.Plugin import chunks - - -def getInfo(urls): - api_url_base = "http://api.hotfile.com/" - - for chunk in chunks(urls, 90): - api_param_file = {"action": "checklinks", "links": ",".join(chunk), - "fields": "id,status,name,size"} #api only supports old style links - src = getURL(api_url_base, post=api_param_file, decode=True) - result = [] - for i, res in enumerate(src.split("\n")): - if not res: - continue - fields = res.split(",") - - if fields[1] in ("1", "2"): - status = 2 - else: - status = 1 - - result.append((fields[2], int(fields[3]), status, chunk[i])) - yield result - - -class HotfileCom(Hoster): - __name__ = "HotfileCom" - __type__ = "hoster" - __pattern__ = r"https?://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+/" - __version__ = "0.36" - __description__ = """Hotfile.com Download Hoster""" - __author_name__ = ("sitacuisses", "spoob", "mkaay", "JoKoT3") - __author_mail__ = ("sitacuisses@yhoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "jokot3@gmail.com") - - FILE_OFFLINE_PATTERN = r'File is removed' - - def setup(self): - self.html = [None, None] - self.wantReconnect = False - self.htmlwithlink = None - self.url = None - - if self.premium: - self.multiDL = self.resumeDownload = True - self.chunkLimit = -1 - else: - self.multiDL = False - self.chunkLimit = 1 - - def apiCall(self, method, post, login=False): - if not self.account and login: - return - elif self.account and login: - return self.account.apiCall(method, post, self.user) - post.update({"action": method}) - return self.load("http://api.hotfile.com/", post=post, decode=True) - - def process(self, pyfile): - self.wantReconnect = False - - args = {"links": self.pyfile.url, "fields": "id,status,name,size,sha1"} - resp = self.apiCall("checklinks", args) - self.api_data = {} - for k, v in zip(args["fields"].split(","), resp.strip().split(",")): - self.api_data[k] = v - - if self.api_data["status"] == "0": - self.offline() - - pyfile.name = self.api_data["name"] - - if not self.premium: - self.downloadHTML() - - if self.FILE_OFFLINE_PATTERN in self.html[0]: - self.offline() - - self.setWait(self.getWaitTime()) - self.wait() - - self.freeDownload() - else: - dl = self.account.apiCall("getdirectdownloadlink", {"link": self.pyfile.url}, self.user) - #dl = unquote(dl).strip() <- Made problems - dl = dl.strip() - self.download(dl) - - def downloadHTML(self): - self.html[0] = self.load(self.pyfile.url, get={"lang": "en"}) - - def freeDownload(self): - - form_content = re.search(r"<form style=.*(\n<.*>\s*)*?[\n\t]?<tr>", self.html[0]) - if form_content is None: - print self.html[0] - self.fail("Form not found in HTML. Can not proceed.") - - form_content = form_content.group(0) - form_posts = dict(re.findall(r"<input\stype=hidden\sname=(\S*)\svalue=(\S*)>", form_content)) - - self.html[1] = self.load(self.pyfile.url, post=form_posts) - - challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=([0-9A-Za-z]+)", self.html[1]) - - if challenge: - re_captcha = ReCaptcha(self) - challenge, result = re_captcha.challenge(challenge.group(1)) - - url = re.search(r'<form action="(/dl/[^"]+)', self.html[1]) - - self.html[1] = self.load("http://hotfile.com" + url.group(1), post={"action": "checkcaptcha", - "recaptcha_challenge_field": challenge, - "recaptcha_response_field": result}) - - if "Wrong Code. Please try again." in self.html[1]: - self.freeDownload() - return - - file_url = re.search(r'a href="(http://hotfile\.com/get/\S*)"', self.html[1]).group(1) - self.download(file_url) - - def getWaitTime(self): - free_limit_pattern = re.compile(r"timerend=d\.getTime\(\)\+(\d+);") - matches = free_limit_pattern.findall(self.html[0]) - if matches: - wait_time = (sum([int(match) for match in matches]) / 1000) or 60 - if wait_time > 300: - self.wantReconnect = True - return wait_time + 1 - else: - self.fail("Don't know how long to wait. Cannot proceed.") diff --git a/module/plugins/hoster/MultiDebridCom.py b/module/plugins/hoster/MultiDebridCom.py deleted file mode 100644 index 83f477f34..000000000 --- a/module/plugins/hoster/MultiDebridCom.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################ -# 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. # -# # -# 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 Affero General Public License for more details. # -# # -# You should have received a copy of the GNU Affero General Public License # -# along with this program. If not, see <http://www.gnu.org/licenses/>. # -############################################################################ - -import re - -from module.plugins.Hoster import Hoster -from module.common.json_layer import json_loads - - -class MultiDebridCom(Hoster): - __name__ = "MultiDebridCom" - __version__ = "0.03" - __type__ = "hoster" - __pattern__ = r"http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/" - __description__ = """Multi-debrid.com hoster plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - def setup(self): - self.chunkLimit = -1 - self.resumeDownload = True - - def process(self, pyfile): - if re.match(self.__pattern__, pyfile.url): - new_url = pyfile.url - elif not self.account: - self.logError(_("Please enter your %s account or deactivate this plugin") % "Multi-debrid.com") - self.fail("No Multi-debrid.com account provided") - else: - self.logDebug("Original URL: %s" % pyfile.url) - page = self.req.load('http://multi-debrid.com/api.php', - get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'], - 'link': pyfile.url}) - self.logDebug("JSON data: " + page) - page = json_loads(page) - if page['status'] != 'ok': - self.fail('Unable to unrestrict link') - new_url = page['link'] - - if new_url != pyfile.url: - self.logDebug("Unrestricted URL: " + new_url) - - self.download(new_url, disposition=True) diff --git a/module/plugins/internal/AbstractExtractor.py b/module/plugins/internal/AbstractExtractor.py deleted file mode 100644 index f730a8434..000000000 --- a/module/plugins/internal/AbstractExtractor.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -class ArchiveError(Exception): - pass - -class CRCError(Exception): - pass - -class WrongPassword(Exception): - pass - -class AbtractExtractor: - - __version__ = "0.1" - - @staticmethod - def checkDeps(): - """ Check if system statisfy dependencies - :return: boolean - """ - return True - - @staticmethod - def getTargets(files_ids): - """ Filter suited targets from list of filename id tuple list - :param files_ids: List of filepathes - :return: List of targets, id tuple list - """ - raise NotImplementedError - - - def __init__(self, m, file, out, fullpath, overwrite, excludefiles, renice): - """Initialize extractor for specific file - - :param m: ExtractArchive Hook plugin - :param file: Absolute filepath - :param out: Absolute path to destination directory - :param fullpath: extract to fullpath - :param overwrite: Overwrite existing archives - :param renice: Renice value - """ - self.m = m - self.file = file - self.out = out - self.fullpath = fullpath - self.overwrite = overwrite - self.excludefiles = excludefiles - self.renice = renice - self.files = [] # Store extracted files here - - - def init(self): - """ Initialize additional data structures """ - pass - - - def checkArchive(self): - """Check if password if needed. Raise ArchiveError if integrity is - questionable. - - :return: boolean - :raises ArchiveError - """ - return False - - def checkPassword(self, password): - """ Check if the given password is/might be correct. - If it can not be decided at this point return true. - - :param password: - :return: boolean - """ - return True - - def extract(self, progress, password=None): - """Extract the archive. Raise specific errors in case of failure. - - :param progress: Progress function, call this to update status - :param password password to use - :raises WrongPassword - :raises CRCError - :raises ArchiveError - :return: - """ - raise NotImplementedError - - def getDeleteFiles(self): - """Return list of files to delete, do *not* delete them here. - - :return: List with paths of files to delete - """ - raise NotImplementedError - - def getExtractedFiles(self): - """Populate self.files at some point while extracting""" - return self.files diff --git a/module/plugins/internal/CaptchaService.py b/module/plugins/internal/CaptchaService.py deleted file mode 100644 index b912436a7..000000000 --- a/module/plugins/internal/CaptchaService.py +++ /dev/null @@ -1,77 +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: zoidberg -""" - -import re - -class CaptchaService(): - __version__ = "0.02" - - def __init__(self, plugin): - self.plugin = plugin - -class ReCaptcha(): - def __init__(self, plugin): - self.plugin = plugin - - def challenge(self, id): - js = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", get={"k":id}, cookies=True) - - try: - challenge = re.search("challenge : '(.*?)',", js).group(1) - server = re.search("server : '(.*?)',", js).group(1) - except: - self.plugin.fail("recaptcha error") - result = self.result(server,challenge) - - return challenge, result - - def result(self, server, challenge): - return self.plugin.decryptCaptcha("%simage"%server, get={"c":challenge}, cookies=True, forceUser=True, imgtype="jpg") - -class AdsCaptcha(CaptchaService): - def challenge(self, src): - js = self.plugin.req.load(src, cookies=True) - - try: - challenge = re.search("challenge: '(.*?)',", js).group(1) - server = re.search("server: '(.*?)',", js).group(1) - except: - self.plugin.fail("adscaptcha error") - result = self.result(server,challenge) - - return challenge, result - - def result(self, server, challenge): - return self.plugin.decryptCaptcha("%sChallenge.aspx" % server, get={"cid": challenge, "dummy": random()}, cookies=True, imgtype="jpg") - -class SolveMedia(CaptchaService): - def __init__(self,plugin): - self.plugin = plugin - - def challenge(self, src): - html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript?k=%s" % src, cookies=True) - try: - challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="([^"]+)">', html).group(1) - except: - self.plugin.fail("solvmedia error") - result = self.result(challenge) - - return challenge, result - - def result(self,challenge): - return self.plugin.decryptCaptcha("http://api.solvemedia.com/papi/media?c=%s" % challenge,imgtype="gif")
\ No newline at end of file diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py deleted file mode 100644 index a8961aafc..000000000 --- a/module/plugins/internal/MultiHoster.py +++ /dev/null @@ -1,189 +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.19" - - replacements = [("2shared.com", "twoshared.com"), ("4shared.com", "fourshared.com"), ("cloudnator.com", "shragle.com"), - ("ifile.it", "filecloud.io"), ("easy-share.com","crocko.com"), ("freakshare.net","freakshare.com"), - ("hellshare.com", "hellshare.cz"), ("share-rapid.cz","sharerapid.com"), ("sharerapid.cz","sharerapid.com"), - ("ul.to","uploaded.to"), ("uploaded.net","uploaded.to"), ("1fichier.com", "onefichier.com")] - ignored = [] - interval = 24 * 60 * 60 # reload hosters daily - - def setup(self): - self.hosters = [] - self.supported = [] - self.new_supported = [] - - def getConfig(self, option, default = ''): - """getConfig with default value - sublass may not implements all config options""" - try: - return self.getConf(option) - except KeyError: - return default - - def getHosterCached(self): - if not self.hosters: - - try: - hosterSet = self.toHosterSet(self.getHoster()) - set(self.ignored) - except Exception, e: - self.logError("%s" % str(e)) - return [] - - try: - configMode = self.getConfig('hosterListMode', 'all') - if configMode in ("listed", "unlisted"): - configSet = self.toHosterSet(self.getConfig('hosterList', '').replace('|',',').replace(';',',').split(',')) - - if configMode == "listed": - hosterSet &= configSet - else: - hosterSet -= configSet - - except Exception, e: - self.logError("%s" % str(e)) - - self.hosters = list(hosterSet) - - return self.hosters - - def toHosterSet(self, hosters): - hosters = set((str(x).strip().lower() for x in hosters)) - - for rep in self.replacements: - if rep[0] in hosters: - hosters.remove(rep[0]) - hosters.add(rep[1]) - - hosters.discard('') - return hosters - - def getHoster(self): - """Load list of supported hoster - - :return: List of domain names - """ - raise NotImplementedError - - def coreReady(self): - if self.cb: - self.core.scheduler.removeJob(self.cb) - - self.setConfig("activated", True) # config not in sync after plugin reload - - cfg_interval = self.getConfig("interval", None) # reload interval in hours - if cfg_interval is not None: - self.interval = cfg_interval * 60 * 60 - - if self.interval: - self._periodical() - else: - self.periodical() - - def initPeriodical(self): - pass - - def periodical(self): - """reload hoster list periodically""" - self.logInfo("Reloading supported hoster list") - - old_supported = self.supported - self.supported, self.new_supported, self.hosters = [], [], [] - - self.overridePlugins() - - old_supported = [hoster for hoster in old_supported if hoster not in self.supported] - if old_supported: - self.logDebug("UNLOAD: %s" % ", ".join(old_supported)) - for hoster in old_supported: - self.unloadHoster(hoster) - - def overridePlugins(self): - pluginMap = {} - for name in self.core.pluginManager.hosterPlugins.keys(): - pluginMap[name.lower()] = name - - accountList = [ name.lower() for name, data in self.core.accountManager.accounts.items() if data ] - excludedList = [] - - for hoster in self.getHosterCached(): - name = remove_chars(hoster.lower(), "-.") - - if name in accountList: - excludedList.append(hoster) - else: - if name in pluginMap: - self.supported.append(pluginMap[name]) - else: - self.new_supported.append(hoster) - - if not self.supported and not self.new_supported: - self.logError(_("No Hoster loaded")) - return - - module = self.core.pluginManager.getPlugin(self.__name__) - klass = getattr(module, self.__name__) - - # inject plugin plugin - self.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(self.supported))) - for hoster in self.supported: - dict = self.core.pluginManager.hosterPlugins[hoster] - dict["new_module"] = module - dict["new_name"] = self.__name__ - - if excludedList: - self.logInfo("The following hosters were not overwritten - account exists: %s" % ", ".join(sorted(excludedList))) - - if self.new_supported: - self.logDebug("New Hosters: %s" % ", ".join(sorted(self.new_supported))) - - # create new regexp - regexp = r".*(%s).*" % "|".join([x.replace(".", "\\.") for x in self.new_supported]) - if hasattr(klass, "__pattern__") and isinstance(klass.__pattern__, basestring) and '://' in klass.__pattern__: - regexp = r"%s|%s" % (klass.__pattern__, regexp) - - self.logDebug("Regexp: %s" % regexp) - - dict = self.core.pluginManager.hosterPlugins[self.__name__] - dict["pattern"] = regexp - dict["re"] = re.compile(regexp) - - def unloadHoster(self, hoster): - dict = self.core.pluginManager.hosterPlugins[hoster] - if "module" in dict: - del dict["module"] - - if "new_module" in dict: - del dict["new_module"] - del dict["new_name"] - - def unload(self): - """Remove override for all hosters. Scheduler job is removed by hookmanager""" - for hoster in self.supported: - self.unloadHoster(hoster) - - # reset pattern - klass = getattr(self.core.pluginManager.getPlugin(self.__name__), self.__name__) - dict = self.core.pluginManager.hosterPlugins[self.__name__] - dict["pattern"] = getattr(klass, '__pattern__', r"^unmatchable$") - dict["re"] = re.compile(dict["pattern"]) - - def downloadFailed(self, pyfile): - """remove plugin override if download fails but not if file is offline/temp.offline""" - if pyfile.hasStatus("failed") and self.getConfig("unloadFailing", True): - hdict = self.core.pluginManager.hosterPlugins[pyfile.pluginname] - if "new_name" in hdict and hdict['new_name'] == self.__name__: - self.logDebug("Unload MultiHoster", pyfile.pluginname, hdict) - self.unloadHoster(pyfile.pluginname) - pyfile.setStatus("queued")
\ No newline at end of file diff --git a/module/plugins/internal/SimpleCrypter.py b/module/plugins/internal/SimpleCrypter.py deleted file mode 100644 index f0fe0b764..000000000 --- a/module/plugins/internal/SimpleCrypter.py +++ /dev/null @@ -1,101 +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: zoidberg -""" - -import re - -from module.plugins.Crypter import Crypter -from module.utils import html_unescape - - -class SimpleCrypter(Crypter): - __name__ = "SimpleCrypter" - __version__ = "0.06" - __pattern__ = None - __type__ = "crypter" - __description__ = """Base crypter plugin""" - __author_name__ = ("stickell", "zoidberg") - __author_mail__ = ("l.stickell@yahoo.it", "zoidberg@mujmail.cz") - """ - These patterns should be defined by each crypter: - - LINK_PATTERN: group(1) must be a download link - example: <div class="link"><a href="(http://speedload.org/\w+) - - TITLE_PATTERN: (optional) the group defined by 'title' should be the title - example: <title>Files of: (?P<title>[^<]+) folder</title> - - If it's impossible to extract the links using the LINK_PATTERN only you can override the getLinks method. - - If the links are disposed on multiple pages you need to define a pattern: - - PAGES_PATTERN: the group defined by 'pages' must be the total number of pages - - and a function: - - loadPage(self, page_n): - must return the html of the page number 'page_n' - """ - - def decrypt(self, pyfile): - self.html = self.load(pyfile.url, decode=True) - - package_name, folder_name = self.getPackageNameAndFolder() - - self.package_links = self.getLinks() - - if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'): - self.handleMultiPages() - - self.logDebug('Package has %d links' % len(self.package_links)) - - if self.package_links: - self.packages = [(package_name, self.package_links, folder_name)] - else: - self.fail('Could not extract any links') - - def getLinks(self): - """ - Returns the links extracted from self.html - You should override this only if it's impossible to extract links using only the LINK_PATTERN. - """ - return re.findall(self.LINK_PATTERN, self.html) - - def getPackageNameAndFolder(self): - if hasattr(self, 'TITLE_PATTERN'): - m = re.search(self.TITLE_PATTERN, self.html) - if m: - name = folder = html_unescape(m.group('title').strip()) - self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) - return name, folder - - name = self.pyfile.package().name - folder = self.pyfile.package().folder - self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) - return name, folder - - def handleMultiPages(self): - pages = re.search(self.PAGES_PATTERN, self.html) - if pages: - pages = int(pages.group('pages')) - else: - pages = 1 - - for p in range(2, pages + 1): - self.html = self.loadPage(p) - self.package_links += self.getLinks() diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py deleted file mode 100644 index 80ee39cdf..000000000 --- a/module/plugins/internal/UnRar.py +++ /dev/null @@ -1,216 +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 os -import re -from os.path import join -from glob import glob -from subprocess import Popen, PIPE -from string import digits - -from module.utils import save_join, decode -from module.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError - -class UnRar(AbtractExtractor): - __name__ = "UnRar" - __version__ = "0.14" - - # there are some more uncovered rar formats - re_splitfile = re.compile(r"(.*)\.part(\d+)\.rar$", re.I) - re_partfiles = re.compile(r".*\.(rar|r[0-9]+)", re.I) - re_filelist = re.compile(r"(.+)\s+(\d+)\s+(\d+)\s+") - re_wrongpwd = re.compile("(Corrupt file or wrong password|password incorrect)", re.I) - CMD = "unrar" - - @staticmethod - def checkDeps(): - if os.name == "nt": - UnRar.CMD = join(pypath, "UnRAR.exe") - p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) - p.communicate() - else: - try: - p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) - p.communicate() - except OSError: - - #fallback to rar - UnRar.CMD = "rar" - p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) - p.communicate() - - return True - - @staticmethod - def getTargets(files_ids): - result = [] - - for file, id in files_ids: - if not file.endswith(".rar"): continue - - match = UnRar.re_splitfile.findall(file) - if match: - #only add first parts - if int(match[0][1]) == 1: - result.append((file, id)) - else: - result.append((file, id)) - - return result - - - def init(self): - self.passwordProtected = False - self.headerProtected = False #list files will not work without password - self.smallestFile = None #small file to test passwords - self.password = "" #save the correct password - - def checkArchive(self): - p = self.call_unrar("l", "-v", self.file) - out, err = p.communicate() - if self.re_wrongpwd.search(err): - self.passwordProtected = True - self.headerProtected = True - return True - - # output only used to check if passworded files are present - for name, size, packed in self.re_filelist.findall(out): - if name.startswith("*"): - self.passwordProtected = True - return True - - self.listContent() - if not self.files: - raise ArchiveError("Empty Archive") - - return False - - def checkPassword(self, password): - #at this point we can only verify header protected files - if self.headerProtected: - p = self.call_unrar("l", "-v", self.file, password=password) - out, err = p.communicate() - if self.re_wrongpwd.search(err): - return False - - return True - - - def extract(self, progress, password=None): - command = "x" if self.fullpath else "e" - - p = self.call_unrar(command, self.file, self.out, password=password) - renice(p.pid, self.renice) - - progress(0) - progressstring = "" - while True: - c = p.stdout.read(1) - # quit loop on eof - if not c: - break - # reading a percentage sign -> set progress and restart - if c == '%': - progress(int(progressstring)) - progressstring = "" - # not reading a digit -> therefore restart - elif c not in digits: - progressstring = "" - # add digit to progressstring - else: - progressstring = progressstring + c - progress(100) - - # retrieve stderr - err = p.stderr.read() - - if "CRC failed" in err and not password and not self.passwordProtected: - raise CRCError - elif "CRC failed" in err: - raise WrongPassword - if err.strip(): #raise error if anything is on stderr - raise ArchiveError(err.strip()) - if p.returncode: - raise ArchiveError("Process terminated") - - if not self.files: - self.password = password - self.listContent() - - - def getDeleteFiles(self): - if ".part" in self.file: - return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.IGNORECASE)) - # get files which matches .r* and filter unsuited files out - parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.IGNORECASE)) - return filter(lambda x: self.re_partfiles.match(x), parts) - - def listContent(self): - command = "vb" if self.fullpath else "lb" - p = self.call_unrar(command, "-v", self.file, password=self.password) - out, err = p.communicate() - - if "Cannot open" in err: - raise ArchiveError("Cannot open file") - - if err.strip(): # only log error at this point - self.m.logError(err.strip()) - - result = set() - - for f in decode(out).splitlines(): - f = f.strip() - result.add(save_join(self.out, f)) - - self.files = result - - - def call_unrar(self, command, *xargs, **kwargs): - args = [] - #overwrite flag - args.append("-o+") if self.overwrite else args.append("-o-") - - if self.excludefiles: - for word in self.excludefiles.split(';'): - args.append("-x%s" % word ) - - # assume yes on all queries - args.append("-y") - - #set a password - if "password" in kwargs and kwargs["password"]: - args.append("-p%s" % kwargs["password"]) - else: - args.append("-p-") - - #NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue - call = [self.CMD, command] + args + list(xargs) - self.m.logDebug(" ".join(call)) - - p = Popen(call, stdout=PIPE, stderr=PIPE) - - return p - - -def renice(pid, value): - if os.name != "nt" and value: - try: - Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1) - except: - print "Renice failed" diff --git a/module/remote/RemoteManager.py b/module/remote/RemoteManager.py deleted file mode 100644 index 36eb52a4a..000000000 --- a/module/remote/RemoteManager.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -from threading import Thread -from traceback import print_exc - -class BackendBase(Thread): - def __init__(self, manager): - Thread.__init__(self) - self.m = manager - self.core = manager.core - self.enabled = True - self.running = False - - def run(self): - self.running = True - try: - self.serve() - except Exception, e: - self.core.log.error(_("Remote backend error: %s") % e) - if self.core.debug: - print_exc() - finally: - self.running = False - - def setup(self, host, port): - pass - - def checkDeps(self): - return True - - def serve(self): - pass - - def shutdown(self): - pass - - def stop(self): - self.enabled = False# set flag and call shutdowm message, so thread can react - self.shutdown() - - -class RemoteManager(): - available = [] - - def __init__(self, core): - self.core = core - self.backends = [] - - if self.core.remote: - self.available.append("ThriftBackend") -# else: -# self.available.append("SocketBackend") - - - def startBackends(self): - host = self.core.config["remote"]["listenaddr"] - port = self.core.config["remote"]["port"] - - for b in self.available: - klass = getattr(__import__("module.remote.%s" % b, globals(), locals(), [b], -1), b) - backend = klass(self) - if not backend.checkDeps(): - continue - try: - backend.setup(host, port) - self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port}) - except Exception, e: - self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)}) - if self.core.debug: - print_exc() - else: - backend.start() - self.backends.append(backend) - - port += 1 diff --git a/module/remote/SocketBackend.py b/module/remote/SocketBackend.py deleted file mode 100644 index 1a157cf1d..000000000 --- a/module/remote/SocketBackend.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -import SocketServer - -from RemoteManager import BackendBase - -class RequestHandler(SocketServer.BaseRequestHandler): - - def setup(self): - pass - - def handle(self): - - print self.request.recv(1024) - - - -class SocketBackend(BackendBase): - - def setup(self, host, port): - #local only - self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler) - - def serve(self): - self.server.serve_forever() diff --git a/module/remote/ThriftBackend.py b/module/remote/ThriftBackend.py deleted file mode 100644 index b4a2bb25e..000000000 --- a/module/remote/ThriftBackend.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay, RaNaN -""" -from os.path import exists - -from module.remote.RemoteManager import BackendBase - -from thriftbackend.Processor import Processor -from thriftbackend.Protocol import ProtocolFactory -from thriftbackend.Socket import ServerSocket -from thriftbackend.Transport import TransportFactory -#from thriftbackend.Transport import TransportFactoryCompressed - -from thrift.server import TServer - -class ThriftBackend(BackendBase): - def setup(self, host, port): - processor = Processor(self.core.api) - - key = None - cert = None - - if self.core.config['ssl']['activated']: - if exists(self.core.config['ssl']['cert']) and exists(self.core.config['ssl']['key']): - self.core.log.info(_("Using SSL ThriftBackend")) - key = self.core.config['ssl']['key'] - cert = self.core.config['ssl']['cert'] - - transport = ServerSocket(port, host, key, cert) - - -# tfactory = TransportFactoryCompressed() - tfactory = TransportFactory() - pfactory = ProtocolFactory() - - self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory) - #self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory) - - #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory) - - def serve(self): - self.server.serve() diff --git a/module/remote/__init__.py b/module/remote/__init__.py deleted file mode 100644 index 9298f5337..000000000 --- a/module/remote/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -activated = True diff --git a/module/remote/socketbackend/__init__.py b/module/remote/socketbackend/__init__.py deleted file mode 100644 index de6d13128..000000000 --- a/module/remote/socketbackend/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -__author__ = 'christian' -
\ No newline at end of file diff --git a/module/remote/socketbackend/create_ttypes.py b/module/remote/socketbackend/create_ttypes.py deleted file mode 100644 index 05662cb50..000000000 --- a/module/remote/socketbackend/create_ttypes.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import inspect -import sys -from os.path import abspath, dirname, join - -path = dirname(abspath(__file__)) -module = join(path, "..", "..") - -sys.path.append(join(module, "lib")) -sys.path.append(join(module, "remote")) - -from thriftbackend.thriftgen.pyload import ttypes -from thriftbackend.thriftgen.pyload.Pyload import Iface - - -def main(): - - enums = [] - classes = [] - - print "generating lightweight ttypes.py" - - for name in dir(ttypes): - klass = getattr(ttypes, name) - - if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)): - continue - - if hasattr(klass, "thrift_spec"): - classes.append(klass) - else: - enums.append(klass) - - - f = open(join(path, "ttypes.py"), "wb") - - f.write( - """#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Autogenerated by pyload -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING - -class BaseObject(object): -\t__slots__ = [] - -""") - - ## generate enums - for enum in enums: - name = enum.__name__ - f.write("class %s:\n" % name) - - for attr in dir(enum): - if attr.startswith("_") or attr in ("read", "write"): continue - - f.write("\t%s = %s\n" % (attr, getattr(enum, attr))) - - f.write("\n") - - for klass in classes: - name = klass.__name__ - base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject" - f.write("class %s(%s):\n" % (name, base)) - f.write("\t__slots__ = %s\n\n" % klass.__slots__) - - #create init - args = ["self"] + ["%s=None" % x for x in klass.__slots__] - - f.write("\tdef __init__(%s):\n" % ", ".join(args)) - for attr in klass.__slots__: - f.write("\t\tself.%s = %s\n" % (attr, attr)) - - f.write("\n") - - f.write("class Iface:\n") - - for name in dir(Iface): - if name.startswith("_"): continue - - func = inspect.getargspec(getattr(Iface, name)) - - f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args))) - - f.write("\n") - - f.close() - -if __name__ == "__main__": - main()
\ No newline at end of file diff --git a/module/remote/socketbackend/ttypes.py b/module/remote/socketbackend/ttypes.py deleted file mode 100644 index f8ea121fa..000000000 --- a/module/remote/socketbackend/ttypes.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Autogenerated by pyload -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING - -class BaseObject(object): - __slots__ = [] - -class Destination: - Collector = 0 - Queue = 1 - -class DownloadStatus: - Aborted = 9 - Custom = 11 - Decrypting = 10 - Downloading = 12 - Failed = 8 - Finished = 0 - Offline = 1 - Online = 2 - Processing = 13 - Queued = 3 - Skipped = 4 - Starting = 7 - TempOffline = 6 - Unknown = 14 - Waiting = 5 - -class ElementType: - File = 1 - Package = 0 - -class Input: - BOOL = 4 - CHOICE = 6 - CLICK = 5 - LIST = 8 - MULTIPLE = 7 - NONE = 0 - PASSWORD = 3 - TABLE = 9 - TEXT = 1 - TEXTBOX = 2 - -class Output: - CAPTCHA = 1 - NOTIFICATION = 4 - QUESTION = 2 - -class AccountInfo(BaseObject): - __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type'] - - def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None): - self.validuntil = validuntil - self.login = login - self.options = options - self.valid = valid - self.trafficleft = trafficleft - self.maxtraffic = maxtraffic - self.premium = premium - self.type = type - -class CaptchaTask(BaseObject): - __slots__ = ['tid', 'data', 'type', 'resultType'] - - def __init__(self, tid=None, data=None, type=None, resultType=None): - self.tid = tid - self.data = data - self.type = type - self.resultType = resultType - -class ConfigItem(BaseObject): - __slots__ = ['name', 'description', 'value', 'type'] - - def __init__(self, name=None, description=None, value=None, type=None): - self.name = name - self.description = description - self.value = value - self.type = type - -class ConfigSection(BaseObject): - __slots__ = ['name', 'description', 'items', 'outline'] - - def __init__(self, name=None, description=None, items=None, outline=None): - self.name = name - self.description = description - self.items = items - self.outline = outline - -class DownloadInfo(BaseObject): - __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin'] - - def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None): - self.fid = fid - self.name = name - self.speed = speed - self.eta = eta - self.format_eta = format_eta - self.bleft = bleft - self.size = size - self.format_size = format_size - self.percent = percent - self.status = status - self.statusmsg = statusmsg - self.format_wait = format_wait - self.wait_until = wait_until - self.packageID = packageID - self.packageName = packageName - self.plugin = plugin - -class EventInfo(BaseObject): - __slots__ = ['eventname', 'id', 'type', 'destination'] - - def __init__(self, eventname=None, id=None, type=None, destination=None): - self.eventname = eventname - self.id = id - self.type = type - self.destination = destination - -class FileData(BaseObject): - __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order'] - - def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None): - self.fid = fid - self.url = url - self.name = name - self.plugin = plugin - self.size = size - self.format_size = format_size - self.status = status - self.statusmsg = statusmsg - self.packageID = packageID - self.error = error - self.order = order - -class FileDoesNotExists(Exception): - __slots__ = ['fid'] - - def __init__(self, fid=None): - self.fid = fid - -class InteractionTask(BaseObject): - __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin'] - - def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None): - self.iid = iid - self.input = input - self.structure = structure - self.preset = preset - self.output = output - self.data = data - self.title = title - self.description = description - self.plugin = plugin - -class OnlineCheck(BaseObject): - __slots__ = ['rid', 'data'] - - def __init__(self, rid=None, data=None): - self.rid = rid - self.data = data - -class OnlineStatus(BaseObject): - __slots__ = ['name', 'plugin', 'packagename', 'status', 'size'] - - def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None): - self.name = name - self.plugin = plugin - self.packagename = packagename - self.status = status - self.size = size - -class PackageData(BaseObject): - __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids'] - - def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None): - self.pid = pid - self.name = name - self.folder = folder - self.site = site - self.password = password - self.dest = dest - self.order = order - self.linksdone = linksdone - self.sizedone = sizedone - self.sizetotal = sizetotal - self.linkstotal = linkstotal - self.links = links - self.fids = fids - -class PackageDoesNotExists(Exception): - __slots__ = ['pid'] - - def __init__(self, pid=None): - self.pid = pid - -class ServerStatus(BaseObject): - __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect'] - - def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None): - self.pause = pause - self.active = active - self.queue = queue - self.total = total - self.speed = speed - self.download = download - self.reconnect = reconnect - -class ServiceCall(BaseObject): - __slots__ = ['plugin', 'func', 'arguments', 'parseArguments'] - - def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None): - self.plugin = plugin - self.func = func - self.arguments = arguments - self.parseArguments = parseArguments - -class ServiceDoesNotExists(Exception): - __slots__ = ['plugin', 'func'] - - def __init__(self, plugin=None, func=None): - self.plugin = plugin - self.func = func - -class ServiceException(Exception): - __slots__ = ['msg'] - - def __init__(self, msg=None): - self.msg = msg - -class UserData(BaseObject): - __slots__ = ['name', 'email', 'role', 'permission', 'templateName'] - - def __init__(self, name=None, email=None, role=None, permission=None, templateName=None): - self.name = name - self.email = email - self.role = role - self.permission = permission - self.templateName = templateName - -class Iface: - def addFiles(self, pid, links): - pass - def addPackage(self, name, links, dest): - pass - def call(self, info): - pass - def checkOnlineStatus(self, urls): - pass - def checkOnlineStatusContainer(self, urls, filename, data): - pass - def checkURLs(self, urls): - pass - def deleteFiles(self, fids): - pass - def deleteFinished(self): - pass - def deletePackages(self, pids): - pass - def freeSpace(self): - pass - def generateAndAddPackages(self, links, dest): - pass - def generatePackages(self, links): - pass - def getAccountTypes(self): - pass - def getAccounts(self, refresh): - pass - def getAllInfo(self): - pass - def getAllUserData(self): - pass - def getCaptchaTask(self, exclusive): - pass - def getCaptchaTaskStatus(self, tid): - pass - def getCollector(self): - pass - def getCollectorData(self): - pass - def getConfig(self): - pass - def getConfigValue(self, category, option, section): - pass - def getEvents(self, uuid): - pass - def getFileData(self, fid): - pass - def getFileOrder(self, pid): - pass - def getInfoByPlugin(self, plugin): - pass - def getLog(self, offset): - pass - def getPackageData(self, pid): - pass - def getPackageInfo(self, pid): - pass - def getPackageOrder(self, destination): - pass - def getPluginConfig(self): - pass - def getQueue(self): - pass - def getQueueData(self): - pass - def getServerVersion(self): - pass - def getServices(self): - pass - def getUserData(self, username, password): - pass - def hasService(self, plugin, func): - pass - def isCaptchaWaiting(self): - pass - def isTimeDownload(self): - pass - def isTimeReconnect(self): - pass - def kill(self): - pass - def login(self, username, password): - pass - def moveFiles(self, fids, pid): - pass - def movePackage(self, destination, pid): - pass - def orderFile(self, fid, position): - pass - def orderPackage(self, pid, position): - pass - def parseURLs(self, html, url): - pass - def pauseServer(self): - pass - def pollResults(self, rid): - pass - def pullFromQueue(self, pid): - pass - def pushToQueue(self, pid): - pass - def recheckPackage(self, pid): - pass - def removeAccount(self, plugin, account): - pass - def restart(self): - pass - def restartFailed(self): - pass - def restartFile(self, fid): - pass - def restartPackage(self, pid): - pass - def setCaptchaResult(self, tid, result): - pass - def setConfigValue(self, category, option, value, section): - pass - def setPackageData(self, pid, data): - pass - def setPackageName(self, pid, name): - pass - def statusDownloads(self): - pass - def statusServer(self): - pass - def stopAllDownloads(self): - pass - def stopDownloads(self, fids): - pass - def togglePause(self): - pass - def toggleReconnect(self): - pass - def unpauseServer(self): - pass - def updateAccount(self, plugin, account, password, options): - pass - def uploadContainer(self, filename, data): - pass - diff --git a/module/remote/thriftbackend/Processor.py b/module/remote/thriftbackend/Processor.py deleted file mode 100644 index a8b87c82c..000000000 --- a/module/remote/thriftbackend/Processor.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- - -from thriftgen.pyload import Pyload - -class Processor(Pyload.Processor): - def __init__(self, *args, **kwargs): - Pyload.Processor.__init__(self, *args, **kwargs) - self.authenticated = {} - - def process(self, iprot, oprot): - trans = oprot.trans - if trans not in self.authenticated: - self.authenticated[trans] = False - oldclose = trans.close - - def wrap(): - if self in self.authenticated: - del self.authenticated[trans] - oldclose() - - trans.close = wrap - authenticated = self.authenticated[trans] - (name, type, seqid) = iprot.readMessageBegin() - - # unknown method - if name not in self._processMap: - iprot.skip(Pyload.TType.STRUCT) - iprot.readMessageEnd() - x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name) - oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) - x.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - return - - # not logged in - elif not authenticated and not name == "login": - iprot.skip(Pyload.TType.STRUCT) - iprot.readMessageEnd() - # 20 - Not logged in (in situ declared error code) - x = Pyload.TApplicationException(20, 'Not logged in') - oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) - x.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - return - - elif not authenticated and name == "login": - args = Pyload.login_args() - args.read(iprot) - iprot.readMessageEnd() - result = Pyload.login_result() - # api login - self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0]) - - result.success = True if self.authenticated[trans] else False - oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - elif self._handler.isAuthorized(name, authenticated): - self._processMap[name](self, seqid, iprot, oprot) - - else: - #no permission - iprot.skip(Pyload.TType.STRUCT) - iprot.readMessageEnd() - # 21 - Not authorized - x = Pyload.TApplicationException(21, 'Not authorized') - oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) - x.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - return - - return True diff --git a/module/remote/thriftbackend/Protocol.py b/module/remote/thriftbackend/Protocol.py deleted file mode 100644 index c42d01459..000000000 --- a/module/remote/thriftbackend/Protocol.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- - -from thrift.protocol import TBinaryProtocol - -class Protocol(TBinaryProtocol.TBinaryProtocol): - def writeString(self, str): - try: - str = str.encode("utf8", "ignore") - except Exception, e: - pass - - self.writeI32(len(str)) - self.trans.write(str) - - def readString(self): - len = self.readI32() - str = self.trans.readAll(len) - try: - str = str.decode("utf8", "ignore") - except: - pass - - return str - - -class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory): - - def getProtocol(self, trans): - prot = Protocol(trans, self.strictRead, self.strictWrite) - return prot
\ No newline at end of file diff --git a/module/remote/thriftbackend/Socket.py b/module/remote/thriftbackend/Socket.py deleted file mode 100644 index 2243f9df2..000000000 --- a/module/remote/thriftbackend/Socket.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import socket -import errno - -from time import sleep - -from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException - -WantReadError = Exception #overwritten when ssl is used - -class SecureSocketConnection: - def __init__(self, connection): - self.__dict__["connection"] = connection - - def __getattr__(self, name): - return getattr(self.__dict__["connection"], name) - - def __setattr__(self, name, value): - setattr(self.__dict__["connection"], name, value) - - def shutdown(self, how=1): - self.__dict__["connection"].shutdown() - - def accept(self): - connection, address = self.__dict__["connection"].accept() - return SecureSocketConnection(connection), address - - def send(self, buff): - try: - return self.__dict__["connection"].send(buff) - except WantReadError: - sleep(0.1) - return self.send(buff) - - def recv(self, buff): - try: - return self.__dict__["connection"].recv(buff) - except WantReadError: - sleep(0.1) - return self.recv(buff) - -class Socket(TSocket): - def __init__(self, host='localhost', port=7228, ssl=False): - TSocket.__init__(self, host, port) - self.ssl = ssl - - def open(self): - if self.ssl: - SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL - WantReadError = SSL.WantReadError - ctx = SSL.Context(SSL.SSLv23_METHOD) - c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) - c.set_connect_state() - self.handle = SecureSocketConnection(c) - else: - self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - #errno 104 connection reset - - self.handle.settimeout(self._timeout) - self.handle.connect((self.host, self.port)) - - def read(self, sz): - try: - buff = self.handle.recv(sz) - except socket.error, e: - if (e.args[0] == errno.ECONNRESET and - (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))): - # freebsd and Mach don't follow POSIX semantic of recv - # and fail with ECONNRESET if peer performed shutdown. - # See corresponding comment and code in TSocket::read() - # in lib/cpp/src/transport/TSocket.cpp. - self.close() - # Trigger the check to raise the END_OF_FILE exception below. - buff = '' - else: - raise - except Exception, e: - # SSL connection was closed - if e.args == (-1, 'Unexpected EOF'): - buff = '' - elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],): - #a socket not using ssl tried to connect - buff = '' - else: - raise - - if not len(buff): - raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes') - return buff - - -class ServerSocket(TServerSocket, Socket): - def __init__(self, port=7228, host="0.0.0.0", key="", cert=""): - self.host = host - self.port = port - self.key = key - self.cert = cert - self.handle = None - - def listen(self): - if self.cert and self.key: - SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL - WantReadError = SSL.WantReadError - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_privatekey_file(self.key) - ctx.use_certificate_file(self.cert) - - tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) - tmpConnection.set_accept_state() - self.handle = SecureSocketConnection(tmpConnection) - - else: - self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - - self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if hasattr(self.handle, 'set_timeout'): - self.handle.set_timeout(None) - self.handle.bind((self.host, self.port)) - self.handle.listen(128) - - def accept(self): - client, addr = self.handle.accept() - result = Socket() - result.setHandle(client) - return result diff --git a/module/remote/thriftbackend/ThriftClient.py b/module/remote/thriftbackend/ThriftClient.py deleted file mode 100644 index 74363cf62..000000000 --- a/module/remote/thriftbackend/ThriftClient.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -from socket import error -from os.path import dirname, abspath, join -from traceback import print_exc - -try: - import thrift -except ImportError: - sys.path.append(abspath(join(dirname(abspath(__file__)), "..", "..", "lib"))) - -from thrift.transport import TTransport -#from thrift.transport.TZlibTransport import TZlibTransport -from Socket import Socket -from Protocol import Protocol - -# modules should import ttypes from here, when want to avoid importing API - -from thriftgen.pyload import Pyload -from thriftgen.pyload.ttypes import * - -ConnectionClosed = TTransport.TTransportException - -class WrongLogin(Exception): - pass - -class NoConnection(Exception): - pass - -class NoSSL(Exception): - pass - -class ThriftClient: - def __init__(self, host="localhost", port=7227, user="", password=""): - - self.createConnection(host, port) - try: - self.transport.open() - except error, e: - if e.args and e.args[0] in (111, 10061): - raise NoConnection - else: - print_exc() - raise NoConnection - - try: - correct = self.client.login(user, password) - except error, e: - if e.args and e.args[0] == 104: - #connection reset by peer, probably wants ssl - try: - self.createConnection(host, port, True) - #set timeout or a ssl socket will block when querying none ssl server - self.socket.setTimeout(10) - - except ImportError: - #@TODO untested - raise NoSSL - try: - self.transport.open() - correct = self.client.login(user, password) - finally: - self.socket.setTimeout(None) - elif e.args and e.args[0] == 32: - raise NoConnection - else: - print_exc() - raise NoConnection - - if not correct: - self.transport.close() - raise WrongLogin - - def createConnection(self, host, port, ssl=False): - self.socket = Socket(host, port, ssl) - self.transport = TTransport.TBufferedTransport(self.socket) -# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket)) - - protocol = Protocol(self.transport) - self.client = Pyload.Client(protocol) - - def close(self): - self.transport.close() - - def __getattr__(self, item): - return getattr(self.client, item) - -if __name__ == "__main__": - - client = ThriftClient(user="User", password="pwhere") - - print client.getServerVersion() - print client.statusServer() - print client.statusDownloads() - q = client.getQueue() - -# for p in q: -# data = client.getPackageData(p.pid) -# print data -# print "Package Name: ", data.name - - - print client.getServices() - print client.call(Pyload.ServiceCall("UpdateManager", "recheckForUpdates")) - - print client.getConfigValue("download", "limit_speed", "core") - - client.close()
\ No newline at end of file diff --git a/module/remote/thriftbackend/ThriftTest.py b/module/remote/thriftbackend/ThriftTest.py deleted file mode 100644 index 69ac6a745..000000000 --- a/module/remote/thriftbackend/ThriftTest.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -from os.path import join,abspath,dirname - -path = join((abspath(dirname(__file__))), "..","..", "lib") -sys.path.append(path) - -from thriftgen.pyload import Pyload -from thriftgen.pyload.ttypes import * -from Socket import Socket - -from thrift import Thrift -from thrift.transport import TTransport - -from Protocol import Protocol - -from time import time - -import xmlrpclib - -def bench(f, *args, **kwargs): - s = time() - ret = [f(*args, **kwargs) for i in range(0,100)] - e = time() - try: - print "%s: %f s" % (f._Method__name, e-s) - except : - print "%s: %f s" % (f.__name__, e-s) - return ret - -from getpass import getpass -user = raw_input("user ") -passwd = getpass("password ") - -server_url = "http%s://%s:%s@%s:%s/" % ( - "", - user, - passwd, - "127.0.0.1", - 7227 -) -proxy = xmlrpclib.ServerProxy(server_url, allow_none=True) - -bench(proxy.get_server_version) -bench(proxy.status_server) -bench(proxy.status_downloads) -#bench(proxy.get_queue) -#bench(proxy.get_collector) -print -try: - - # Make socket - transport = Socket('localhost', 7228, False) - - # Buffering is critical. Raw sockets are very slow - transport = TTransport.TBufferedTransport(transport) - - # Wrap in a protocol - protocol = Protocol(transport) - - # Create a client to use the protocol encoder - client = Pyload.Client(protocol) - - # Connect! - transport.open() - - print "Login", client.login(user, passwd) - - bench(client.getServerVersion) - bench(client.statusServer) - bench(client.statusDownloads) - #bench(client.getQueue) - #bench(client.getCollector) - - print - print client.getServerVersion() - print client.statusServer() - print client.statusDownloads() - q = client.getQueue() - - for p in q: - data = client.getPackageData(p.pid) - print data - print "Package Name: ", data.name - - # Close! - transport.close() - -except Thrift.TException, tx: - print 'ThriftExpection: %s' % tx.message diff --git a/module/remote/thriftbackend/Transport.py b/module/remote/thriftbackend/Transport.py deleted file mode 100644 index 5772c5a9e..000000000 --- a/module/remote/thriftbackend/Transport.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -from thrift.transport.TTransport import TBufferedTransport -from thrift.transport.TZlibTransport import TZlibTransport - -class Transport(TBufferedTransport): - DEFAULT_BUFFER = 4096 - - def __init__(self, trans, rbuf_size = DEFAULT_BUFFER): - TBufferedTransport.__init__(self, trans, rbuf_size) - self.handle = trans.handle - self.remoteaddr = trans.handle.getpeername() - -class TransportCompressed(TZlibTransport): - DEFAULT_BUFFER = 4096 - - def __init__(self, trans, rbuf_size = DEFAULT_BUFFER): - TZlibTransport.__init__(self, trans, rbuf_size) - self.handle = trans.handle - self.remoteaddr = trans.handle.getpeername() - -class TransportFactory: - def getTransport(self, trans): - buffered = Transport(trans) - return buffered - -class TransportFactoryCompressed: - _last_trans = None - _last_z = None - - def getTransport(self, trans, compresslevel=9): - if trans == self._last_trans: - return self._last_z - ztrans = TransportCompressed(Transport(trans), compresslevel) - self._last_trans = trans - self._last_z = ztrans - return ztrans
\ No newline at end of file diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift deleted file mode 100644 index 1542e651a..000000000 --- a/module/remote/thriftbackend/pyload.thrift +++ /dev/null @@ -1,337 +0,0 @@ -namespace java org.pyload.thrift - -typedef i32 FileID -typedef i32 PackageID -typedef i32 TaskID -typedef i32 ResultID -typedef i32 InteractionID -typedef list<string> LinkList -typedef string PluginName -typedef byte Progress -typedef byte Priority - - -enum DownloadStatus { - Finished - Offline, - Online, - Queued, - Skipped, - Waiting, - TempOffline, - Starting, - Failed, - Aborted, - Decrypting, - Custom, - Downloading, - Processing, - Unknown -} - -enum Destination { - Collector, - Queue -} - -enum ElementType { - Package, - File -} - -// types for user interaction -// some may only be place holder currently not supported -// also all input - output combination are not reasonable, see InteractionManager for further info -enum Input { - NONE, - TEXT, - TEXTBOX, - PASSWORD, - BOOL, // confirm like, yes or no dialog - CLICK, // for positional captchas - CHOICE, // choice from list - MULTIPLE, // multiple choice from list of elements - LIST, // arbitary list of elements - TABLE // table like data structure -} -// more can be implemented by need - -// this describes the type of the outgoing interaction -// ensure they can be logcial or'ed -enum Output { - CAPTCHA = 1, - QUESTION = 2, - NOTIFICATION = 4, -} - -struct DownloadInfo { - 1: FileID fid, - 2: string name, - 3: i64 speed, - 4: i32 eta, - 5: string format_eta, - 6: i64 bleft, - 7: i64 size, - 8: string format_size, - 9: Progress percent, - 10: DownloadStatus status, - 11: string statusmsg, - 12: string format_wait, - 13: i64 wait_until, - 14: PackageID packageID, - 15: string packageName, - 16: PluginName plugin, -} - -struct ServerStatus { - 1: bool pause, - 2: i16 active, - 3: i16 queue, - 4: i16 total, - 5: i64 speed, - 6: bool download, - 7: bool reconnect -} - -struct ConfigItem { - 1: string name, - 2: string description, - 3: string value, - 4: string type, -} - -struct ConfigSection { - 1: string name, - 2: string description, - 3: list<ConfigItem> items, - 4: optional string outline -} - -struct FileData { - 1: FileID fid, - 2: string url, - 3: string name, - 4: PluginName plugin, - 5: i64 size, - 6: string format_size, - 7: DownloadStatus status, - 8: string statusmsg, - 9: PackageID packageID, - 10: string error, - 11: i16 order -} - -struct PackageData { - 1: PackageID pid, - 2: string name, - 3: string folder, - 4: string site, - 5: string password, - 6: Destination dest, - 7: i16 order, - 8: optional i16 linksdone, - 9: optional i64 sizedone, - 10: optional i64 sizetotal, - 11: optional i16 linkstotal, - 12: optional list<FileData> links, - 13: optional list<FileID> fids -} - -struct InteractionTask { - 1: InteractionID iid, - 2: Input input, - 3: list<string> structure, - 4: list<string> preset, - 5: Output output, - 6: list<string> data, - 7: string title, - 8: string description, - 9: string plugin, -} - -struct CaptchaTask { - 1: i16 tid, - 2: binary data, - 3: string type, - 4: string resultType -} - -struct EventInfo { - 1: string eventname, - 2: optional i32 id, - 3: optional ElementType type, - 4: optional Destination destination -} - -struct UserData { - 1: string name, - 2: string email, - 3: i32 role, - 4: i32 permission, - 5: string templateName -} - -struct AccountInfo { - 1: i64 validuntil, - 2: string login, - 3: map<string, list<string>> options, - 4: bool valid, - 5: i64 trafficleft, - 6: i64 maxtraffic, - 7: bool premium, - 8: string type, -} - -struct ServiceCall { - 1: PluginName plugin, - 2: string func, - 3: optional list<string> arguments, - 4: optional bool parseArguments, //default False -} - -struct OnlineStatus { - 1: string name, - 2: PluginName plugin, - 3: string packagename, - 4: DownloadStatus status, - 5: i64 size, // size <= 0 : unknown -} - -struct OnlineCheck { - 1: ResultID rid, // -1 -> nothing more to get - 2: map<string, OnlineStatus> data, //url to result -} - - -// exceptions - -exception PackageDoesNotExists{ - 1: PackageID pid -} - -exception FileDoesNotExists{ - 1: FileID fid -} - -exception ServiceDoesNotExists{ - 1: string plugin - 2: string func -} - -exception ServiceException{ - 1: string msg -} - -service Pyload { - - //config - string getConfigValue(1: string category, 2: string option, 3: string section), - void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section), - map<string, ConfigSection> getConfig(), - map<string, ConfigSection> getPluginConfig(), - - // server status - void pauseServer(), - void unpauseServer(), - bool togglePause(), - ServerStatus statusServer(), - i64 freeSpace(), - string getServerVersion(), - void kill(), - void restart(), - list<string> getLog(1: i32 offset), - bool isTimeDownload(), - bool isTimeReconnect(), - bool toggleReconnect(), - - // download preparing - - // packagename - urls - map<string, LinkList> generatePackages(1: LinkList links), - map<PluginName, LinkList> checkURLs(1: LinkList urls), - map<PluginName, LinkList> parseURLs(1: string html, 2: string url), - - // parses results and generates packages - OnlineCheck checkOnlineStatus(1: LinkList urls), - OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data) - - // poll results from previosly started online check - OnlineCheck pollResults(1: ResultID rid), - - // downloads - information - list<DownloadInfo> statusDownloads(), - PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e), - PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e), - FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e), - list<PackageData> getQueue(), - list<PackageData> getCollector(), - list<PackageData> getQueueData(), - list<PackageData> getCollectorData(), - map<i16, PackageID> getPackageOrder(1: Destination destination), - map<i16, FileID> getFileOrder(1: PackageID pid) - - // downloads - adding/deleting - list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest), - PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest), - void addFiles(1: PackageID pid, 2: LinkList links), - void uploadContainer(1: string filename, 2: binary data), - void deleteFiles(1: list<FileID> fids), - void deletePackages(1: list<PackageID> pids), - - // downloads - modifying - void pushToQueue(1: PackageID pid), - void pullFromQueue(1: PackageID pid), - void restartPackage(1: PackageID pid), - void restartFile(1: FileID fid), - void recheckPackage(1: PackageID pid), - void stopAllDownloads(), - void stopDownloads(1: list<FileID> fids), - void setPackageName(1: PackageID pid, 2: string name), - void movePackage(1: Destination destination, 2: PackageID pid), - void moveFiles(1: list<FileID> fids, 2: PackageID pid), - void orderPackage(1: PackageID pid, 2: i16 position), - void orderFile(1: FileID fid, 2: i16 position), - void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e), - list<PackageID> deleteFinished(), - void restartFailed(), - - //events - list<EventInfo> getEvents(1: string uuid) - - //accounts - list<AccountInfo> getAccounts(1: bool refresh), - list<string> getAccountTypes() - void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options), - void removeAccount(1: PluginName plugin, 2: string account), - - //auth - bool login(1: string username, 2: string password), - UserData getUserData(1: string username, 2:string password), - map<string, UserData> getAllUserData(), - - //services - - // servicename : description - map<PluginName, map<string, string>> getServices(), - bool hasService(1: PluginName plugin, 2: string func), - string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e), - - - //info - // {plugin: {name: value}} - map<PluginName, map<string,string>> getAllInfo(), - map<string, string> getInfoByPlugin(1: PluginName plugin), - - //scheduler - - // TODO - - - // User interaction - - //captcha - bool isCaptchaWaiting(), - CaptchaTask getCaptchaTask(1: bool exclusive), - string getCaptchaTaskStatus(1: TaskID tid), - void setCaptchaResult(1: TaskID tid, 2: string result), -} diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote deleted file mode 100755 index bfaf5b078..000000000 --- a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote +++ /dev/null @@ -1,571 +0,0 @@ -#!/usr/bin/env python -# -# Autogenerated by Thrift Compiler (0.9.0-dev) -# -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING -# -# options string: py:slots,dynamic -# - -import sys -import pprint -from urlparse import urlparse -from thrift.transport import TTransport -from thrift.transport import TSocket -from thrift.transport import THttpClient -from thrift.protocol import TBinaryProtocol - -import Pyload -from ttypes import * - -if len(sys.argv) <= 1 or sys.argv[1] == '--help': - print '' - print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]' - print '' - print 'Functions:' - print ' string getConfigValue(string category, string option, string section)' - print ' void setConfigValue(string category, string option, string value, string section)' - print ' getConfig()' - print ' getPluginConfig()' - print ' void pauseServer()' - print ' void unpauseServer()' - print ' bool togglePause()' - print ' ServerStatus statusServer()' - print ' i64 freeSpace()' - print ' string getServerVersion()' - print ' void kill()' - print ' void restart()' - print ' getLog(i32 offset)' - print ' bool isTimeDownload()' - print ' bool isTimeReconnect()' - print ' bool toggleReconnect()' - print ' generatePackages(LinkList links)' - print ' checkURLs(LinkList urls)' - print ' parseURLs(string html, string url)' - print ' OnlineCheck checkOnlineStatus(LinkList urls)' - print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)' - print ' OnlineCheck pollResults(ResultID rid)' - print ' statusDownloads()' - print ' PackageData getPackageData(PackageID pid)' - print ' PackageData getPackageInfo(PackageID pid)' - print ' FileData getFileData(FileID fid)' - print ' getQueue()' - print ' getCollector()' - print ' getQueueData()' - print ' getCollectorData()' - print ' getPackageOrder(Destination destination)' - print ' getFileOrder(PackageID pid)' - print ' generateAndAddPackages(LinkList links, Destination dest)' - print ' PackageID addPackage(string name, LinkList links, Destination dest)' - print ' void addFiles(PackageID pid, LinkList links)' - print ' void uploadContainer(string filename, string data)' - print ' void deleteFiles( fids)' - print ' void deletePackages( pids)' - print ' void pushToQueue(PackageID pid)' - print ' void pullFromQueue(PackageID pid)' - print ' void restartPackage(PackageID pid)' - print ' void restartFile(FileID fid)' - print ' void recheckPackage(PackageID pid)' - print ' void stopAllDownloads()' - print ' void stopDownloads( fids)' - print ' void setPackageName(PackageID pid, string name)' - print ' void movePackage(Destination destination, PackageID pid)' - print ' void moveFiles( fids, PackageID pid)' - print ' void orderPackage(PackageID pid, i16 position)' - print ' void orderFile(FileID fid, i16 position)' - print ' void setPackageData(PackageID pid, data)' - print ' deleteFinished()' - print ' void restartFailed()' - print ' getEvents(string uuid)' - print ' getAccounts(bool refresh)' - print ' getAccountTypes()' - print ' void updateAccount(PluginName plugin, string account, string password, options)' - print ' void removeAccount(PluginName plugin, string account)' - print ' bool login(string username, string password)' - print ' UserData getUserData(string username, string password)' - print ' getAllUserData()' - print ' getServices()' - print ' bool hasService(PluginName plugin, string func)' - print ' string call(ServiceCall info)' - print ' getAllInfo()' - print ' getInfoByPlugin(PluginName plugin)' - print ' bool isCaptchaWaiting()' - print ' CaptchaTask getCaptchaTask(bool exclusive)' - print ' string getCaptchaTaskStatus(TaskID tid)' - print ' void setCaptchaResult(TaskID tid, string result)' - print '' - sys.exit(0) - -pp = pprint.PrettyPrinter(indent = 2) -host = 'localhost' -port = 9090 -uri = '' -framed = False -http = False -argi = 1 - -if sys.argv[argi] == '-h': - parts = sys.argv[argi+1].split(':') - host = parts[0] - if len(parts) > 1: - port = int(parts[1]) - argi += 2 - -if sys.argv[argi] == '-u': - url = urlparse(sys.argv[argi+1]) - parts = url[1].split(':') - host = parts[0] - if len(parts) > 1: - port = int(parts[1]) - else: - port = 80 - uri = url[2] - if url[4]: - uri += '?%s' % url[4] - http = True - argi += 2 - -if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed': - framed = True - argi += 1 - -cmd = sys.argv[argi] -args = sys.argv[argi+1:] - -if http: - transport = THttpClient.THttpClient(host, port, uri) -else: - socket = TSocket.TSocket(host, port) - if framed: - transport = TTransport.TFramedTransport(socket) - else: - transport = TTransport.TBufferedTransport(socket) -protocol = TBinaryProtocol.TBinaryProtocol(transport) -client = Pyload.Client(protocol) -transport.open() - -if cmd == 'getConfigValue': - if len(args) != 3: - print 'getConfigValue requires 3 args' - sys.exit(1) - pp.pprint(client.getConfigValue(args[0],args[1],args[2],)) - -elif cmd == 'setConfigValue': - if len(args) != 4: - print 'setConfigValue requires 4 args' - sys.exit(1) - pp.pprint(client.setConfigValue(args[0],args[1],args[2],args[3],)) - -elif cmd == 'getConfig': - if len(args) != 0: - print 'getConfig requires 0 args' - sys.exit(1) - pp.pprint(client.getConfig()) - -elif cmd == 'getPluginConfig': - if len(args) != 0: - print 'getPluginConfig requires 0 args' - sys.exit(1) - pp.pprint(client.getPluginConfig()) - -elif cmd == 'pauseServer': - if len(args) != 0: - print 'pauseServer requires 0 args' - sys.exit(1) - pp.pprint(client.pauseServer()) - -elif cmd == 'unpauseServer': - if len(args) != 0: - print 'unpauseServer requires 0 args' - sys.exit(1) - pp.pprint(client.unpauseServer()) - -elif cmd == 'togglePause': - if len(args) != 0: - print 'togglePause requires 0 args' - sys.exit(1) - pp.pprint(client.togglePause()) - -elif cmd == 'statusServer': - if len(args) != 0: - print 'statusServer requires 0 args' - sys.exit(1) - pp.pprint(client.statusServer()) - -elif cmd == 'freeSpace': - if len(args) != 0: - print 'freeSpace requires 0 args' - sys.exit(1) - pp.pprint(client.freeSpace()) - -elif cmd == 'getServerVersion': - if len(args) != 0: - print 'getServerVersion requires 0 args' - sys.exit(1) - pp.pprint(client.getServerVersion()) - -elif cmd == 'kill': - if len(args) != 0: - print 'kill requires 0 args' - sys.exit(1) - pp.pprint(client.kill()) - -elif cmd == 'restart': - if len(args) != 0: - print 'restart requires 0 args' - sys.exit(1) - pp.pprint(client.restart()) - -elif cmd == 'getLog': - if len(args) != 1: - print 'getLog requires 1 args' - sys.exit(1) - pp.pprint(client.getLog(eval(args[0]),)) - -elif cmd == 'isTimeDownload': - if len(args) != 0: - print 'isTimeDownload requires 0 args' - sys.exit(1) - pp.pprint(client.isTimeDownload()) - -elif cmd == 'isTimeReconnect': - if len(args) != 0: - print 'isTimeReconnect requires 0 args' - sys.exit(1) - pp.pprint(client.isTimeReconnect()) - -elif cmd == 'toggleReconnect': - if len(args) != 0: - print 'toggleReconnect requires 0 args' - sys.exit(1) - pp.pprint(client.toggleReconnect()) - -elif cmd == 'generatePackages': - if len(args) != 1: - print 'generatePackages requires 1 args' - sys.exit(1) - pp.pprint(client.generatePackages(eval(args[0]),)) - -elif cmd == 'checkURLs': - if len(args) != 1: - print 'checkURLs requires 1 args' - sys.exit(1) - pp.pprint(client.checkURLs(eval(args[0]),)) - -elif cmd == 'parseURLs': - if len(args) != 2: - print 'parseURLs requires 2 args' - sys.exit(1) - pp.pprint(client.parseURLs(args[0],args[1],)) - -elif cmd == 'checkOnlineStatus': - if len(args) != 1: - print 'checkOnlineStatus requires 1 args' - sys.exit(1) - pp.pprint(client.checkOnlineStatus(eval(args[0]),)) - -elif cmd == 'checkOnlineStatusContainer': - if len(args) != 3: - print 'checkOnlineStatusContainer requires 3 args' - sys.exit(1) - pp.pprint(client.checkOnlineStatusContainer(eval(args[0]),args[1],args[2],)) - -elif cmd == 'pollResults': - if len(args) != 1: - print 'pollResults requires 1 args' - sys.exit(1) - pp.pprint(client.pollResults(eval(args[0]),)) - -elif cmd == 'statusDownloads': - if len(args) != 0: - print 'statusDownloads requires 0 args' - sys.exit(1) - pp.pprint(client.statusDownloads()) - -elif cmd == 'getPackageData': - if len(args) != 1: - print 'getPackageData requires 1 args' - sys.exit(1) - pp.pprint(client.getPackageData(eval(args[0]),)) - -elif cmd == 'getPackageInfo': - if len(args) != 1: - print 'getPackageInfo requires 1 args' - sys.exit(1) - pp.pprint(client.getPackageInfo(eval(args[0]),)) - -elif cmd == 'getFileData': - if len(args) != 1: - print 'getFileData requires 1 args' - sys.exit(1) - pp.pprint(client.getFileData(eval(args[0]),)) - -elif cmd == 'getQueue': - if len(args) != 0: - print 'getQueue requires 0 args' - sys.exit(1) - pp.pprint(client.getQueue()) - -elif cmd == 'getCollector': - if len(args) != 0: - print 'getCollector requires 0 args' - sys.exit(1) - pp.pprint(client.getCollector()) - -elif cmd == 'getQueueData': - if len(args) != 0: - print 'getQueueData requires 0 args' - sys.exit(1) - pp.pprint(client.getQueueData()) - -elif cmd == 'getCollectorData': - if len(args) != 0: - print 'getCollectorData requires 0 args' - sys.exit(1) - pp.pprint(client.getCollectorData()) - -elif cmd == 'getPackageOrder': - if len(args) != 1: - print 'getPackageOrder requires 1 args' - sys.exit(1) - pp.pprint(client.getPackageOrder(eval(args[0]),)) - -elif cmd == 'getFileOrder': - if len(args) != 1: - print 'getFileOrder requires 1 args' - sys.exit(1) - pp.pprint(client.getFileOrder(eval(args[0]),)) - -elif cmd == 'generateAndAddPackages': - if len(args) != 2: - print 'generateAndAddPackages requires 2 args' - sys.exit(1) - pp.pprint(client.generateAndAddPackages(eval(args[0]),eval(args[1]),)) - -elif cmd == 'addPackage': - if len(args) != 3: - print 'addPackage requires 3 args' - sys.exit(1) - pp.pprint(client.addPackage(args[0],eval(args[1]),eval(args[2]),)) - -elif cmd == 'addFiles': - if len(args) != 2: - print 'addFiles requires 2 args' - sys.exit(1) - pp.pprint(client.addFiles(eval(args[0]),eval(args[1]),)) - -elif cmd == 'uploadContainer': - if len(args) != 2: - print 'uploadContainer requires 2 args' - sys.exit(1) - pp.pprint(client.uploadContainer(args[0],args[1],)) - -elif cmd == 'deleteFiles': - if len(args) != 1: - print 'deleteFiles requires 1 args' - sys.exit(1) - pp.pprint(client.deleteFiles(eval(args[0]),)) - -elif cmd == 'deletePackages': - if len(args) != 1: - print 'deletePackages requires 1 args' - sys.exit(1) - pp.pprint(client.deletePackages(eval(args[0]),)) - -elif cmd == 'pushToQueue': - if len(args) != 1: - print 'pushToQueue requires 1 args' - sys.exit(1) - pp.pprint(client.pushToQueue(eval(args[0]),)) - -elif cmd == 'pullFromQueue': - if len(args) != 1: - print 'pullFromQueue requires 1 args' - sys.exit(1) - pp.pprint(client.pullFromQueue(eval(args[0]),)) - -elif cmd == 'restartPackage': - if len(args) != 1: - print 'restartPackage requires 1 args' - sys.exit(1) - pp.pprint(client.restartPackage(eval(args[0]),)) - -elif cmd == 'restartFile': - if len(args) != 1: - print 'restartFile requires 1 args' - sys.exit(1) - pp.pprint(client.restartFile(eval(args[0]),)) - -elif cmd == 'recheckPackage': - if len(args) != 1: - print 'recheckPackage requires 1 args' - sys.exit(1) - pp.pprint(client.recheckPackage(eval(args[0]),)) - -elif cmd == 'stopAllDownloads': - if len(args) != 0: - print 'stopAllDownloads requires 0 args' - sys.exit(1) - pp.pprint(client.stopAllDownloads()) - -elif cmd == 'stopDownloads': - if len(args) != 1: - print 'stopDownloads requires 1 args' - sys.exit(1) - pp.pprint(client.stopDownloads(eval(args[0]),)) - -elif cmd == 'setPackageName': - if len(args) != 2: - print 'setPackageName requires 2 args' - sys.exit(1) - pp.pprint(client.setPackageName(eval(args[0]),args[1],)) - -elif cmd == 'movePackage': - if len(args) != 2: - print 'movePackage requires 2 args' - sys.exit(1) - pp.pprint(client.movePackage(eval(args[0]),eval(args[1]),)) - -elif cmd == 'moveFiles': - if len(args) != 2: - print 'moveFiles requires 2 args' - sys.exit(1) - pp.pprint(client.moveFiles(eval(args[0]),eval(args[1]),)) - -elif cmd == 'orderPackage': - if len(args) != 2: - print 'orderPackage requires 2 args' - sys.exit(1) - pp.pprint(client.orderPackage(eval(args[0]),eval(args[1]),)) - -elif cmd == 'orderFile': - if len(args) != 2: - print 'orderFile requires 2 args' - sys.exit(1) - pp.pprint(client.orderFile(eval(args[0]),eval(args[1]),)) - -elif cmd == 'setPackageData': - if len(args) != 2: - print 'setPackageData requires 2 args' - sys.exit(1) - pp.pprint(client.setPackageData(eval(args[0]),eval(args[1]),)) - -elif cmd == 'deleteFinished': - if len(args) != 0: - print 'deleteFinished requires 0 args' - sys.exit(1) - pp.pprint(client.deleteFinished()) - -elif cmd == 'restartFailed': - if len(args) != 0: - print 'restartFailed requires 0 args' - sys.exit(1) - pp.pprint(client.restartFailed()) - -elif cmd == 'getEvents': - if len(args) != 1: - print 'getEvents requires 1 args' - sys.exit(1) - pp.pprint(client.getEvents(args[0],)) - -elif cmd == 'getAccounts': - if len(args) != 1: - print 'getAccounts requires 1 args' - sys.exit(1) - pp.pprint(client.getAccounts(eval(args[0]),)) - -elif cmd == 'getAccountTypes': - if len(args) != 0: - print 'getAccountTypes requires 0 args' - sys.exit(1) - pp.pprint(client.getAccountTypes()) - -elif cmd == 'updateAccount': - if len(args) != 4: - print 'updateAccount requires 4 args' - sys.exit(1) - pp.pprint(client.updateAccount(eval(args[0]),args[1],args[2],eval(args[3]),)) - -elif cmd == 'removeAccount': - if len(args) != 2: - print 'removeAccount requires 2 args' - sys.exit(1) - pp.pprint(client.removeAccount(eval(args[0]),args[1],)) - -elif cmd == 'login': - if len(args) != 2: - print 'login requires 2 args' - sys.exit(1) - pp.pprint(client.login(args[0],args[1],)) - -elif cmd == 'getUserData': - if len(args) != 2: - print 'getUserData requires 2 args' - sys.exit(1) - pp.pprint(client.getUserData(args[0],args[1],)) - -elif cmd == 'getAllUserData': - if len(args) != 0: - print 'getAllUserData requires 0 args' - sys.exit(1) - pp.pprint(client.getAllUserData()) - -elif cmd == 'getServices': - if len(args) != 0: - print 'getServices requires 0 args' - sys.exit(1) - pp.pprint(client.getServices()) - -elif cmd == 'hasService': - if len(args) != 2: - print 'hasService requires 2 args' - sys.exit(1) - pp.pprint(client.hasService(eval(args[0]),args[1],)) - -elif cmd == 'call': - if len(args) != 1: - print 'call requires 1 args' - sys.exit(1) - pp.pprint(client.call(eval(args[0]),)) - -elif cmd == 'getAllInfo': - if len(args) != 0: - print 'getAllInfo requires 0 args' - sys.exit(1) - pp.pprint(client.getAllInfo()) - -elif cmd == 'getInfoByPlugin': - if len(args) != 1: - print 'getInfoByPlugin requires 1 args' - sys.exit(1) - pp.pprint(client.getInfoByPlugin(eval(args[0]),)) - -elif cmd == 'isCaptchaWaiting': - if len(args) != 0: - print 'isCaptchaWaiting requires 0 args' - sys.exit(1) - pp.pprint(client.isCaptchaWaiting()) - -elif cmd == 'getCaptchaTask': - if len(args) != 1: - print 'getCaptchaTask requires 1 args' - sys.exit(1) - pp.pprint(client.getCaptchaTask(eval(args[0]),)) - -elif cmd == 'getCaptchaTaskStatus': - if len(args) != 1: - print 'getCaptchaTaskStatus requires 1 args' - sys.exit(1) - pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),)) - -elif cmd == 'setCaptchaResult': - if len(args) != 2: - print 'setCaptchaResult requires 2 args' - sys.exit(1) - pp.pprint(client.setCaptchaResult(eval(args[0]),args[1],)) - -else: - print 'Unrecognized method %s' % cmd - sys.exit(1) - -transport.close() diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py deleted file mode 100644 index 78a42f16a..000000000 --- a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py +++ /dev/null @@ -1,5534 +0,0 @@ -# -# Autogenerated by Thrift Compiler (0.9.0-dev) -# -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING -# -# options string: py:slots,dynamic -# - -from thrift.Thrift import TType, TMessageType, TException -from ttypes import * -from thrift.Thrift import TProcessor -from thrift.protocol.TBase import TBase, TExceptionBase - - -class Iface(object): - def getConfigValue(self, category, option, section): - """ - Parameters: - - category - - option - - section - """ - pass - - def setConfigValue(self, category, option, value, section): - """ - Parameters: - - category - - option - - value - - section - """ - pass - - def getConfig(self, ): - pass - - def getPluginConfig(self, ): - pass - - def pauseServer(self, ): - pass - - def unpauseServer(self, ): - pass - - def togglePause(self, ): - pass - - def statusServer(self, ): - pass - - def freeSpace(self, ): - pass - - def getServerVersion(self, ): - pass - - def kill(self, ): - pass - - def restart(self, ): - pass - - def getLog(self, offset): - """ - Parameters: - - offset - """ - pass - - def isTimeDownload(self, ): - pass - - def isTimeReconnect(self, ): - pass - - def toggleReconnect(self, ): - pass - - def generatePackages(self, links): - """ - Parameters: - - links - """ - pass - - def checkURLs(self, urls): - """ - Parameters: - - urls - """ - pass - - def parseURLs(self, html, url): - """ - Parameters: - - html - - url - """ - pass - - def checkOnlineStatus(self, urls): - """ - Parameters: - - urls - """ - pass - - def checkOnlineStatusContainer(self, urls, filename, data): - """ - Parameters: - - urls - - filename - - data - """ - pass - - def pollResults(self, rid): - """ - Parameters: - - rid - """ - pass - - def statusDownloads(self, ): - pass - - def getPackageData(self, pid): - """ - Parameters: - - pid - """ - pass - - def getPackageInfo(self, pid): - """ - Parameters: - - pid - """ - pass - - def getFileData(self, fid): - """ - Parameters: - - fid - """ - pass - - def getQueue(self, ): - pass - - def getCollector(self, ): - pass - - def getQueueData(self, ): - pass - - def getCollectorData(self, ): - pass - - def getPackageOrder(self, destination): - """ - Parameters: - - destination - """ - pass - - def getFileOrder(self, pid): - """ - Parameters: - - pid - """ - pass - - def generateAndAddPackages(self, links, dest): - """ - Parameters: - - links - - dest - """ - pass - - def addPackage(self, name, links, dest): - """ - Parameters: - - name - - links - - dest - """ - pass - - def addFiles(self, pid, links): - """ - Parameters: - - pid - - links - """ - pass - - def uploadContainer(self, filename, data): - """ - Parameters: - - filename - - data - """ - pass - - def deleteFiles(self, fids): - """ - Parameters: - - fids - """ - pass - - def deletePackages(self, pids): - """ - Parameters: - - pids - """ - pass - - def pushToQueue(self, pid): - """ - Parameters: - - pid - """ - pass - - def pullFromQueue(self, pid): - """ - Parameters: - - pid - """ - pass - - def restartPackage(self, pid): - """ - Parameters: - - pid - """ - pass - - def restartFile(self, fid): - """ - Parameters: - - fid - """ - pass - - def recheckPackage(self, pid): - """ - Parameters: - - pid - """ - pass - - def stopAllDownloads(self, ): - pass - - def stopDownloads(self, fids): - """ - Parameters: - - fids - """ - pass - - def setPackageName(self, pid, name): - """ - Parameters: - - pid - - name - """ - pass - - def movePackage(self, destination, pid): - """ - Parameters: - - destination - - pid - """ - pass - - def moveFiles(self, fids, pid): - """ - Parameters: - - fids - - pid - """ - pass - - def orderPackage(self, pid, position): - """ - Parameters: - - pid - - position - """ - pass - - def orderFile(self, fid, position): - """ - Parameters: - - fid - - position - """ - pass - - def setPackageData(self, pid, data): - """ - Parameters: - - pid - - data - """ - pass - - def deleteFinished(self, ): - pass - - def restartFailed(self, ): - pass - - def getEvents(self, uuid): - """ - Parameters: - - uuid - """ - pass - - def getAccounts(self, refresh): - """ - Parameters: - - refresh - """ - pass - - def getAccountTypes(self, ): - pass - - def updateAccount(self, plugin, account, password, options): - """ - Parameters: - - plugin - - account - - password - - options - """ - pass - - def removeAccount(self, plugin, account): - """ - Parameters: - - plugin - - account - """ - pass - - def login(self, username, password): - """ - Parameters: - - username - - password - """ - pass - - def getUserData(self, username, password): - """ - Parameters: - - username - - password - """ - pass - - def getAllUserData(self, ): - pass - - def getServices(self, ): - pass - - def hasService(self, plugin, func): - """ - Parameters: - - plugin - - func - """ - pass - - def call(self, info): - """ - Parameters: - - info - """ - pass - - def getAllInfo(self, ): - pass - - def getInfoByPlugin(self, plugin): - """ - Parameters: - - plugin - """ - pass - - def isCaptchaWaiting(self, ): - pass - - def getCaptchaTask(self, exclusive): - """ - Parameters: - - exclusive - """ - pass - - def getCaptchaTaskStatus(self, tid): - """ - Parameters: - - tid - """ - pass - - def setCaptchaResult(self, tid, result): - """ - Parameters: - - tid - - result - """ - pass - - -class Client(Iface): - def __init__(self, iprot, oprot=None): - self._iprot = self._oprot = iprot - if oprot is not None: - self._oprot = oprot - self._seqid = 0 - - def getConfigValue(self, category, option, section): - """ - Parameters: - - category - - option - - section - """ - self.send_getConfigValue(category, option, section) - return self.recv_getConfigValue() - - def send_getConfigValue(self, category, option, section): - self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid) - args = getConfigValue_args() - args.category = category - args.option = option - args.section = section - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getConfigValue(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getConfigValue_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result"); - - def setConfigValue(self, category, option, value, section): - """ - Parameters: - - category - - option - - value - - section - """ - self.send_setConfigValue(category, option, value, section) - self.recv_setConfigValue() - - def send_setConfigValue(self, category, option, value, section): - self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid) - args = setConfigValue_args() - args.category = category - args.option = option - args.value = value - args.section = section - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_setConfigValue(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = setConfigValue_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def getConfig(self, ): - self.send_getConfig() - return self.recv_getConfig() - - def send_getConfig(self, ): - self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid) - args = getConfig_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getConfig(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getConfig_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result"); - - def getPluginConfig(self, ): - self.send_getPluginConfig() - return self.recv_getPluginConfig() - - def send_getPluginConfig(self, ): - self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid) - args = getPluginConfig_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getPluginConfig(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getPluginConfig_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result"); - - def pauseServer(self, ): - self.send_pauseServer() - self.recv_pauseServer() - - def send_pauseServer(self, ): - self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid) - args = pauseServer_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_pauseServer(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = pauseServer_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def unpauseServer(self, ): - self.send_unpauseServer() - self.recv_unpauseServer() - - def send_unpauseServer(self, ): - self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid) - args = unpauseServer_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_unpauseServer(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = unpauseServer_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def togglePause(self, ): - self.send_togglePause() - return self.recv_togglePause() - - def send_togglePause(self, ): - self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid) - args = togglePause_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_togglePause(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = togglePause_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result"); - - def statusServer(self, ): - self.send_statusServer() - return self.recv_statusServer() - - def send_statusServer(self, ): - self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid) - args = statusServer_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_statusServer(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = statusServer_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result"); - - def freeSpace(self, ): - self.send_freeSpace() - return self.recv_freeSpace() - - def send_freeSpace(self, ): - self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid) - args = freeSpace_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_freeSpace(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = freeSpace_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result"); - - def getServerVersion(self, ): - self.send_getServerVersion() - return self.recv_getServerVersion() - - def send_getServerVersion(self, ): - self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid) - args = getServerVersion_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getServerVersion(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getServerVersion_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result"); - - def kill(self, ): - self.send_kill() - self.recv_kill() - - def send_kill(self, ): - self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid) - args = kill_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_kill(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = kill_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def restart(self, ): - self.send_restart() - self.recv_restart() - - def send_restart(self, ): - self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid) - args = restart_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_restart(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = restart_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def getLog(self, offset): - """ - Parameters: - - offset - """ - self.send_getLog(offset) - return self.recv_getLog() - - def send_getLog(self, offset): - self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid) - args = getLog_args() - args.offset = offset - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getLog(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getLog_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result"); - - def isTimeDownload(self, ): - self.send_isTimeDownload() - return self.recv_isTimeDownload() - - def send_isTimeDownload(self, ): - self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid) - args = isTimeDownload_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_isTimeDownload(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = isTimeDownload_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result"); - - def isTimeReconnect(self, ): - self.send_isTimeReconnect() - return self.recv_isTimeReconnect() - - def send_isTimeReconnect(self, ): - self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid) - args = isTimeReconnect_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_isTimeReconnect(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = isTimeReconnect_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result"); - - def toggleReconnect(self, ): - self.send_toggleReconnect() - return self.recv_toggleReconnect() - - def send_toggleReconnect(self, ): - self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid) - args = toggleReconnect_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_toggleReconnect(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = toggleReconnect_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result"); - - def generatePackages(self, links): - """ - Parameters: - - links - """ - self.send_generatePackages(links) - return self.recv_generatePackages() - - def send_generatePackages(self, links): - self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid) - args = generatePackages_args() - args.links = links - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_generatePackages(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = generatePackages_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result"); - - def checkURLs(self, urls): - """ - Parameters: - - urls - """ - self.send_checkURLs(urls) - return self.recv_checkURLs() - - def send_checkURLs(self, urls): - self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid) - args = checkURLs_args() - args.urls = urls - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_checkURLs(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = checkURLs_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result"); - - def parseURLs(self, html, url): - """ - Parameters: - - html - - url - """ - self.send_parseURLs(html, url) - return self.recv_parseURLs() - - def send_parseURLs(self, html, url): - self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid) - args = parseURLs_args() - args.html = html - args.url = url - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_parseURLs(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = parseURLs_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result"); - - def checkOnlineStatus(self, urls): - """ - Parameters: - - urls - """ - self.send_checkOnlineStatus(urls) - return self.recv_checkOnlineStatus() - - def send_checkOnlineStatus(self, urls): - self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid) - args = checkOnlineStatus_args() - args.urls = urls - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_checkOnlineStatus(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = checkOnlineStatus_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result"); - - def checkOnlineStatusContainer(self, urls, filename, data): - """ - Parameters: - - urls - - filename - - data - """ - self.send_checkOnlineStatusContainer(urls, filename, data) - return self.recv_checkOnlineStatusContainer() - - def send_checkOnlineStatusContainer(self, urls, filename, data): - self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid) - args = checkOnlineStatusContainer_args() - args.urls = urls - args.filename = filename - args.data = data - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_checkOnlineStatusContainer(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = checkOnlineStatusContainer_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result"); - - def pollResults(self, rid): - """ - Parameters: - - rid - """ - self.send_pollResults(rid) - return self.recv_pollResults() - - def send_pollResults(self, rid): - self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid) - args = pollResults_args() - args.rid = rid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_pollResults(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = pollResults_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result"); - - def statusDownloads(self, ): - self.send_statusDownloads() - return self.recv_statusDownloads() - - def send_statusDownloads(self, ): - self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid) - args = statusDownloads_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_statusDownloads(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = statusDownloads_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result"); - - def getPackageData(self, pid): - """ - Parameters: - - pid - """ - self.send_getPackageData(pid) - return self.recv_getPackageData() - - def send_getPackageData(self, pid): - self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid) - args = getPackageData_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getPackageData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getPackageData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - if result.e is not None: - raise result.e - raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result"); - - def getPackageInfo(self, pid): - """ - Parameters: - - pid - """ - self.send_getPackageInfo(pid) - return self.recv_getPackageInfo() - - def send_getPackageInfo(self, pid): - self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid) - args = getPackageInfo_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getPackageInfo(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getPackageInfo_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - if result.e is not None: - raise result.e - raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result"); - - def getFileData(self, fid): - """ - Parameters: - - fid - """ - self.send_getFileData(fid) - return self.recv_getFileData() - - def send_getFileData(self, fid): - self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid) - args = getFileData_args() - args.fid = fid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getFileData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getFileData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - if result.e is not None: - raise result.e - raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result"); - - def getQueue(self, ): - self.send_getQueue() - return self.recv_getQueue() - - def send_getQueue(self, ): - self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid) - args = getQueue_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getQueue(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getQueue_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result"); - - def getCollector(self, ): - self.send_getCollector() - return self.recv_getCollector() - - def send_getCollector(self, ): - self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid) - args = getCollector_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getCollector(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getCollector_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result"); - - def getQueueData(self, ): - self.send_getQueueData() - return self.recv_getQueueData() - - def send_getQueueData(self, ): - self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid) - args = getQueueData_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getQueueData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getQueueData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result"); - - def getCollectorData(self, ): - self.send_getCollectorData() - return self.recv_getCollectorData() - - def send_getCollectorData(self, ): - self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid) - args = getCollectorData_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getCollectorData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getCollectorData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result"); - - def getPackageOrder(self, destination): - """ - Parameters: - - destination - """ - self.send_getPackageOrder(destination) - return self.recv_getPackageOrder() - - def send_getPackageOrder(self, destination): - self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid) - args = getPackageOrder_args() - args.destination = destination - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getPackageOrder(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getPackageOrder_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result"); - - def getFileOrder(self, pid): - """ - Parameters: - - pid - """ - self.send_getFileOrder(pid) - return self.recv_getFileOrder() - - def send_getFileOrder(self, pid): - self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid) - args = getFileOrder_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getFileOrder(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getFileOrder_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result"); - - def generateAndAddPackages(self, links, dest): - """ - Parameters: - - links - - dest - """ - self.send_generateAndAddPackages(links, dest) - return self.recv_generateAndAddPackages() - - def send_generateAndAddPackages(self, links, dest): - self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid) - args = generateAndAddPackages_args() - args.links = links - args.dest = dest - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_generateAndAddPackages(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = generateAndAddPackages_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result"); - - def addPackage(self, name, links, dest): - """ - Parameters: - - name - - links - - dest - """ - self.send_addPackage(name, links, dest) - return self.recv_addPackage() - - def send_addPackage(self, name, links, dest): - self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid) - args = addPackage_args() - args.name = name - args.links = links - args.dest = dest - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_addPackage(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = addPackage_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result"); - - def addFiles(self, pid, links): - """ - Parameters: - - pid - - links - """ - self.send_addFiles(pid, links) - self.recv_addFiles() - - def send_addFiles(self, pid, links): - self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid) - args = addFiles_args() - args.pid = pid - args.links = links - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_addFiles(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = addFiles_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def uploadContainer(self, filename, data): - """ - Parameters: - - filename - - data - """ - self.send_uploadContainer(filename, data) - self.recv_uploadContainer() - - def send_uploadContainer(self, filename, data): - self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid) - args = uploadContainer_args() - args.filename = filename - args.data = data - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_uploadContainer(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = uploadContainer_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def deleteFiles(self, fids): - """ - Parameters: - - fids - """ - self.send_deleteFiles(fids) - self.recv_deleteFiles() - - def send_deleteFiles(self, fids): - self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid) - args = deleteFiles_args() - args.fids = fids - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_deleteFiles(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = deleteFiles_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def deletePackages(self, pids): - """ - Parameters: - - pids - """ - self.send_deletePackages(pids) - self.recv_deletePackages() - - def send_deletePackages(self, pids): - self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid) - args = deletePackages_args() - args.pids = pids - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_deletePackages(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = deletePackages_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def pushToQueue(self, pid): - """ - Parameters: - - pid - """ - self.send_pushToQueue(pid) - self.recv_pushToQueue() - - def send_pushToQueue(self, pid): - self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid) - args = pushToQueue_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_pushToQueue(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = pushToQueue_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def pullFromQueue(self, pid): - """ - Parameters: - - pid - """ - self.send_pullFromQueue(pid) - self.recv_pullFromQueue() - - def send_pullFromQueue(self, pid): - self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid) - args = pullFromQueue_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_pullFromQueue(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = pullFromQueue_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def restartPackage(self, pid): - """ - Parameters: - - pid - """ - self.send_restartPackage(pid) - self.recv_restartPackage() - - def send_restartPackage(self, pid): - self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid) - args = restartPackage_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_restartPackage(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = restartPackage_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def restartFile(self, fid): - """ - Parameters: - - fid - """ - self.send_restartFile(fid) - self.recv_restartFile() - - def send_restartFile(self, fid): - self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid) - args = restartFile_args() - args.fid = fid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_restartFile(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = restartFile_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def recheckPackage(self, pid): - """ - Parameters: - - pid - """ - self.send_recheckPackage(pid) - self.recv_recheckPackage() - - def send_recheckPackage(self, pid): - self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid) - args = recheckPackage_args() - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_recheckPackage(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = recheckPackage_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def stopAllDownloads(self, ): - self.send_stopAllDownloads() - self.recv_stopAllDownloads() - - def send_stopAllDownloads(self, ): - self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid) - args = stopAllDownloads_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_stopAllDownloads(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = stopAllDownloads_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def stopDownloads(self, fids): - """ - Parameters: - - fids - """ - self.send_stopDownloads(fids) - self.recv_stopDownloads() - - def send_stopDownloads(self, fids): - self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid) - args = stopDownloads_args() - args.fids = fids - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_stopDownloads(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = stopDownloads_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def setPackageName(self, pid, name): - """ - Parameters: - - pid - - name - """ - self.send_setPackageName(pid, name) - self.recv_setPackageName() - - def send_setPackageName(self, pid, name): - self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid) - args = setPackageName_args() - args.pid = pid - args.name = name - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_setPackageName(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = setPackageName_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def movePackage(self, destination, pid): - """ - Parameters: - - destination - - pid - """ - self.send_movePackage(destination, pid) - self.recv_movePackage() - - def send_movePackage(self, destination, pid): - self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid) - args = movePackage_args() - args.destination = destination - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_movePackage(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = movePackage_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def moveFiles(self, fids, pid): - """ - Parameters: - - fids - - pid - """ - self.send_moveFiles(fids, pid) - self.recv_moveFiles() - - def send_moveFiles(self, fids, pid): - self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid) - args = moveFiles_args() - args.fids = fids - args.pid = pid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_moveFiles(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = moveFiles_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def orderPackage(self, pid, position): - """ - Parameters: - - pid - - position - """ - self.send_orderPackage(pid, position) - self.recv_orderPackage() - - def send_orderPackage(self, pid, position): - self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid) - args = orderPackage_args() - args.pid = pid - args.position = position - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_orderPackage(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = orderPackage_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def orderFile(self, fid, position): - """ - Parameters: - - fid - - position - """ - self.send_orderFile(fid, position) - self.recv_orderFile() - - def send_orderFile(self, fid, position): - self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid) - args = orderFile_args() - args.fid = fid - args.position = position - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_orderFile(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = orderFile_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def setPackageData(self, pid, data): - """ - Parameters: - - pid - - data - """ - self.send_setPackageData(pid, data) - self.recv_setPackageData() - - def send_setPackageData(self, pid, data): - self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid) - args = setPackageData_args() - args.pid = pid - args.data = data - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_setPackageData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = setPackageData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.e is not None: - raise result.e - return - - def deleteFinished(self, ): - self.send_deleteFinished() - return self.recv_deleteFinished() - - def send_deleteFinished(self, ): - self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid) - args = deleteFinished_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_deleteFinished(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = deleteFinished_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result"); - - def restartFailed(self, ): - self.send_restartFailed() - self.recv_restartFailed() - - def send_restartFailed(self, ): - self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid) - args = restartFailed_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_restartFailed(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = restartFailed_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def getEvents(self, uuid): - """ - Parameters: - - uuid - """ - self.send_getEvents(uuid) - return self.recv_getEvents() - - def send_getEvents(self, uuid): - self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid) - args = getEvents_args() - args.uuid = uuid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getEvents(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getEvents_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result"); - - def getAccounts(self, refresh): - """ - Parameters: - - refresh - """ - self.send_getAccounts(refresh) - return self.recv_getAccounts() - - def send_getAccounts(self, refresh): - self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid) - args = getAccounts_args() - args.refresh = refresh - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getAccounts(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getAccounts_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result"); - - def getAccountTypes(self, ): - self.send_getAccountTypes() - return self.recv_getAccountTypes() - - def send_getAccountTypes(self, ): - self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid) - args = getAccountTypes_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getAccountTypes(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getAccountTypes_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result"); - - def updateAccount(self, plugin, account, password, options): - """ - Parameters: - - plugin - - account - - password - - options - """ - self.send_updateAccount(plugin, account, password, options) - self.recv_updateAccount() - - def send_updateAccount(self, plugin, account, password, options): - self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid) - args = updateAccount_args() - args.plugin = plugin - args.account = account - args.password = password - args.options = options - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_updateAccount(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = updateAccount_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def removeAccount(self, plugin, account): - """ - Parameters: - - plugin - - account - """ - self.send_removeAccount(plugin, account) - self.recv_removeAccount() - - def send_removeAccount(self, plugin, account): - self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid) - args = removeAccount_args() - args.plugin = plugin - args.account = account - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_removeAccount(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = removeAccount_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - def login(self, username, password): - """ - Parameters: - - username - - password - """ - self.send_login(username, password) - return self.recv_login() - - def send_login(self, username, password): - self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid) - args = login_args() - args.username = username - args.password = password - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_login(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = login_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result"); - - def getUserData(self, username, password): - """ - Parameters: - - username - - password - """ - self.send_getUserData(username, password) - return self.recv_getUserData() - - def send_getUserData(self, username, password): - self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid) - args = getUserData_args() - args.username = username - args.password = password - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getUserData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getUserData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result"); - - def getAllUserData(self, ): - self.send_getAllUserData() - return self.recv_getAllUserData() - - def send_getAllUserData(self, ): - self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid) - args = getAllUserData_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getAllUserData(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getAllUserData_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result"); - - def getServices(self, ): - self.send_getServices() - return self.recv_getServices() - - def send_getServices(self, ): - self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid) - args = getServices_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getServices(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getServices_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result"); - - def hasService(self, plugin, func): - """ - Parameters: - - plugin - - func - """ - self.send_hasService(plugin, func) - return self.recv_hasService() - - def send_hasService(self, plugin, func): - self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid) - args = hasService_args() - args.plugin = plugin - args.func = func - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_hasService(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = hasService_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result"); - - def call(self, info): - """ - Parameters: - - info - """ - self.send_call(info) - return self.recv_call() - - def send_call(self, info): - self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid) - args = call_args() - args.info = info - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_call(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = call_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - if result.ex is not None: - raise result.ex - if result.e is not None: - raise result.e - raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result"); - - def getAllInfo(self, ): - self.send_getAllInfo() - return self.recv_getAllInfo() - - def send_getAllInfo(self, ): - self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid) - args = getAllInfo_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getAllInfo(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getAllInfo_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result"); - - def getInfoByPlugin(self, plugin): - """ - Parameters: - - plugin - """ - self.send_getInfoByPlugin(plugin) - return self.recv_getInfoByPlugin() - - def send_getInfoByPlugin(self, plugin): - self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid) - args = getInfoByPlugin_args() - args.plugin = plugin - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getInfoByPlugin(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getInfoByPlugin_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result"); - - def isCaptchaWaiting(self, ): - self.send_isCaptchaWaiting() - return self.recv_isCaptchaWaiting() - - def send_isCaptchaWaiting(self, ): - self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid) - args = isCaptchaWaiting_args() - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_isCaptchaWaiting(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = isCaptchaWaiting_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result"); - - def getCaptchaTask(self, exclusive): - """ - Parameters: - - exclusive - """ - self.send_getCaptchaTask(exclusive) - return self.recv_getCaptchaTask() - - def send_getCaptchaTask(self, exclusive): - self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid) - args = getCaptchaTask_args() - args.exclusive = exclusive - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getCaptchaTask(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getCaptchaTask_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result"); - - def getCaptchaTaskStatus(self, tid): - """ - Parameters: - - tid - """ - self.send_getCaptchaTaskStatus(tid) - return self.recv_getCaptchaTaskStatus() - - def send_getCaptchaTaskStatus(self, tid): - self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid) - args = getCaptchaTaskStatus_args() - args.tid = tid - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_getCaptchaTaskStatus(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = getCaptchaTaskStatus_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - if result.success is not None: - return result.success - raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result"); - - def setCaptchaResult(self, tid, result): - """ - Parameters: - - tid - - result - """ - self.send_setCaptchaResult(tid, result) - self.recv_setCaptchaResult() - - def send_setCaptchaResult(self, tid, result): - self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid) - args = setCaptchaResult_args() - args.tid = tid - args.result = result - args.write(self._oprot) - self._oprot.writeMessageEnd() - self._oprot.trans.flush() - - def recv_setCaptchaResult(self, ): - (fname, mtype, rseqid) = self._iprot.readMessageBegin() - if mtype == TMessageType.EXCEPTION: - x = TApplicationException() - x.read(self._iprot) - self._iprot.readMessageEnd() - raise x - result = setCaptchaResult_result() - result.read(self._iprot) - self._iprot.readMessageEnd() - return - - -class Processor(Iface, TProcessor): - def __init__(self, handler): - self._handler = handler - self._processMap = {} - self._processMap["getConfigValue"] = Processor.process_getConfigValue - self._processMap["setConfigValue"] = Processor.process_setConfigValue - self._processMap["getConfig"] = Processor.process_getConfig - self._processMap["getPluginConfig"] = Processor.process_getPluginConfig - self._processMap["pauseServer"] = Processor.process_pauseServer - self._processMap["unpauseServer"] = Processor.process_unpauseServer - self._processMap["togglePause"] = Processor.process_togglePause - self._processMap["statusServer"] = Processor.process_statusServer - self._processMap["freeSpace"] = Processor.process_freeSpace - self._processMap["getServerVersion"] = Processor.process_getServerVersion - self._processMap["kill"] = Processor.process_kill - self._processMap["restart"] = Processor.process_restart - self._processMap["getLog"] = Processor.process_getLog - self._processMap["isTimeDownload"] = Processor.process_isTimeDownload - self._processMap["isTimeReconnect"] = Processor.process_isTimeReconnect - self._processMap["toggleReconnect"] = Processor.process_toggleReconnect - self._processMap["generatePackages"] = Processor.process_generatePackages - self._processMap["checkURLs"] = Processor.process_checkURLs - self._processMap["parseURLs"] = Processor.process_parseURLs - self._processMap["checkOnlineStatus"] = Processor.process_checkOnlineStatus - self._processMap["checkOnlineStatusContainer"] = Processor.process_checkOnlineStatusContainer - self._processMap["pollResults"] = Processor.process_pollResults - self._processMap["statusDownloads"] = Processor.process_statusDownloads - self._processMap["getPackageData"] = Processor.process_getPackageData - self._processMap["getPackageInfo"] = Processor.process_getPackageInfo - self._processMap["getFileData"] = Processor.process_getFileData - self._processMap["getQueue"] = Processor.process_getQueue - self._processMap["getCollector"] = Processor.process_getCollector - self._processMap["getQueueData"] = Processor.process_getQueueData - self._processMap["getCollectorData"] = Processor.process_getCollectorData - self._processMap["getPackageOrder"] = Processor.process_getPackageOrder - self._processMap["getFileOrder"] = Processor.process_getFileOrder - self._processMap["generateAndAddPackages"] = Processor.process_generateAndAddPackages - self._processMap["addPackage"] = Processor.process_addPackage - self._processMap["addFiles"] = Processor.process_addFiles - self._processMap["uploadContainer"] = Processor.process_uploadContainer - self._processMap["deleteFiles"] = Processor.process_deleteFiles - self._processMap["deletePackages"] = Processor.process_deletePackages - self._processMap["pushToQueue"] = Processor.process_pushToQueue - self._processMap["pullFromQueue"] = Processor.process_pullFromQueue - self._processMap["restartPackage"] = Processor.process_restartPackage - self._processMap["restartFile"] = Processor.process_restartFile - self._processMap["recheckPackage"] = Processor.process_recheckPackage - self._processMap["stopAllDownloads"] = Processor.process_stopAllDownloads - self._processMap["stopDownloads"] = Processor.process_stopDownloads - self._processMap["setPackageName"] = Processor.process_setPackageName - self._processMap["movePackage"] = Processor.process_movePackage - self._processMap["moveFiles"] = Processor.process_moveFiles - self._processMap["orderPackage"] = Processor.process_orderPackage - self._processMap["orderFile"] = Processor.process_orderFile - self._processMap["setPackageData"] = Processor.process_setPackageData - self._processMap["deleteFinished"] = Processor.process_deleteFinished - self._processMap["restartFailed"] = Processor.process_restartFailed - self._processMap["getEvents"] = Processor.process_getEvents - self._processMap["getAccounts"] = Processor.process_getAccounts - self._processMap["getAccountTypes"] = Processor.process_getAccountTypes - self._processMap["updateAccount"] = Processor.process_updateAccount - self._processMap["removeAccount"] = Processor.process_removeAccount - self._processMap["login"] = Processor.process_login - self._processMap["getUserData"] = Processor.process_getUserData - self._processMap["getAllUserData"] = Processor.process_getAllUserData - self._processMap["getServices"] = Processor.process_getServices - self._processMap["hasService"] = Processor.process_hasService - self._processMap["call"] = Processor.process_call - self._processMap["getAllInfo"] = Processor.process_getAllInfo - self._processMap["getInfoByPlugin"] = Processor.process_getInfoByPlugin - self._processMap["isCaptchaWaiting"] = Processor.process_isCaptchaWaiting - self._processMap["getCaptchaTask"] = Processor.process_getCaptchaTask - self._processMap["getCaptchaTaskStatus"] = Processor.process_getCaptchaTaskStatus - self._processMap["setCaptchaResult"] = Processor.process_setCaptchaResult - - def process(self, iprot, oprot): - (name, type, seqid) = iprot.readMessageBegin() - if name not in self._processMap: - iprot.skip(TType.STRUCT) - iprot.readMessageEnd() - x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name)) - oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid) - x.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - return - else: - self._processMap[name](self, seqid, iprot, oprot) - return True - - def process_getConfigValue(self, seqid, iprot, oprot): - args = getConfigValue_args() - args.read(iprot) - iprot.readMessageEnd() - result = getConfigValue_result() - result.success = self._handler.getConfigValue(args.category, args.option, args.section) - oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_setConfigValue(self, seqid, iprot, oprot): - args = setConfigValue_args() - args.read(iprot) - iprot.readMessageEnd() - result = setConfigValue_result() - self._handler.setConfigValue(args.category, args.option, args.value, args.section) - oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getConfig(self, seqid, iprot, oprot): - args = getConfig_args() - args.read(iprot) - iprot.readMessageEnd() - result = getConfig_result() - result.success = self._handler.getConfig() - oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getPluginConfig(self, seqid, iprot, oprot): - args = getPluginConfig_args() - args.read(iprot) - iprot.readMessageEnd() - result = getPluginConfig_result() - result.success = self._handler.getPluginConfig() - oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_pauseServer(self, seqid, iprot, oprot): - args = pauseServer_args() - args.read(iprot) - iprot.readMessageEnd() - result = pauseServer_result() - self._handler.pauseServer() - oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_unpauseServer(self, seqid, iprot, oprot): - args = unpauseServer_args() - args.read(iprot) - iprot.readMessageEnd() - result = unpauseServer_result() - self._handler.unpauseServer() - oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_togglePause(self, seqid, iprot, oprot): - args = togglePause_args() - args.read(iprot) - iprot.readMessageEnd() - result = togglePause_result() - result.success = self._handler.togglePause() - oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_statusServer(self, seqid, iprot, oprot): - args = statusServer_args() - args.read(iprot) - iprot.readMessageEnd() - result = statusServer_result() - result.success = self._handler.statusServer() - oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_freeSpace(self, seqid, iprot, oprot): - args = freeSpace_args() - args.read(iprot) - iprot.readMessageEnd() - result = freeSpace_result() - result.success = self._handler.freeSpace() - oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getServerVersion(self, seqid, iprot, oprot): - args = getServerVersion_args() - args.read(iprot) - iprot.readMessageEnd() - result = getServerVersion_result() - result.success = self._handler.getServerVersion() - oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_kill(self, seqid, iprot, oprot): - args = kill_args() - args.read(iprot) - iprot.readMessageEnd() - result = kill_result() - self._handler.kill() - oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_restart(self, seqid, iprot, oprot): - args = restart_args() - args.read(iprot) - iprot.readMessageEnd() - result = restart_result() - self._handler.restart() - oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getLog(self, seqid, iprot, oprot): - args = getLog_args() - args.read(iprot) - iprot.readMessageEnd() - result = getLog_result() - result.success = self._handler.getLog(args.offset) - oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_isTimeDownload(self, seqid, iprot, oprot): - args = isTimeDownload_args() - args.read(iprot) - iprot.readMessageEnd() - result = isTimeDownload_result() - result.success = self._handler.isTimeDownload() - oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_isTimeReconnect(self, seqid, iprot, oprot): - args = isTimeReconnect_args() - args.read(iprot) - iprot.readMessageEnd() - result = isTimeReconnect_result() - result.success = self._handler.isTimeReconnect() - oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_toggleReconnect(self, seqid, iprot, oprot): - args = toggleReconnect_args() - args.read(iprot) - iprot.readMessageEnd() - result = toggleReconnect_result() - result.success = self._handler.toggleReconnect() - oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_generatePackages(self, seqid, iprot, oprot): - args = generatePackages_args() - args.read(iprot) - iprot.readMessageEnd() - result = generatePackages_result() - result.success = self._handler.generatePackages(args.links) - oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_checkURLs(self, seqid, iprot, oprot): - args = checkURLs_args() - args.read(iprot) - iprot.readMessageEnd() - result = checkURLs_result() - result.success = self._handler.checkURLs(args.urls) - oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_parseURLs(self, seqid, iprot, oprot): - args = parseURLs_args() - args.read(iprot) - iprot.readMessageEnd() - result = parseURLs_result() - result.success = self._handler.parseURLs(args.html, args.url) - oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_checkOnlineStatus(self, seqid, iprot, oprot): - args = checkOnlineStatus_args() - args.read(iprot) - iprot.readMessageEnd() - result = checkOnlineStatus_result() - result.success = self._handler.checkOnlineStatus(args.urls) - oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_checkOnlineStatusContainer(self, seqid, iprot, oprot): - args = checkOnlineStatusContainer_args() - args.read(iprot) - iprot.readMessageEnd() - result = checkOnlineStatusContainer_result() - result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data) - oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_pollResults(self, seqid, iprot, oprot): - args = pollResults_args() - args.read(iprot) - iprot.readMessageEnd() - result = pollResults_result() - result.success = self._handler.pollResults(args.rid) - oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_statusDownloads(self, seqid, iprot, oprot): - args = statusDownloads_args() - args.read(iprot) - iprot.readMessageEnd() - result = statusDownloads_result() - result.success = self._handler.statusDownloads() - oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getPackageData(self, seqid, iprot, oprot): - args = getPackageData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getPackageData_result() - try: - result.success = self._handler.getPackageData(args.pid) - except PackageDoesNotExists, e: - result.e = e - oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getPackageInfo(self, seqid, iprot, oprot): - args = getPackageInfo_args() - args.read(iprot) - iprot.readMessageEnd() - result = getPackageInfo_result() - try: - result.success = self._handler.getPackageInfo(args.pid) - except PackageDoesNotExists, e: - result.e = e - oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getFileData(self, seqid, iprot, oprot): - args = getFileData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getFileData_result() - try: - result.success = self._handler.getFileData(args.fid) - except FileDoesNotExists, e: - result.e = e - oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getQueue(self, seqid, iprot, oprot): - args = getQueue_args() - args.read(iprot) - iprot.readMessageEnd() - result = getQueue_result() - result.success = self._handler.getQueue() - oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getCollector(self, seqid, iprot, oprot): - args = getCollector_args() - args.read(iprot) - iprot.readMessageEnd() - result = getCollector_result() - result.success = self._handler.getCollector() - oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getQueueData(self, seqid, iprot, oprot): - args = getQueueData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getQueueData_result() - result.success = self._handler.getQueueData() - oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getCollectorData(self, seqid, iprot, oprot): - args = getCollectorData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getCollectorData_result() - result.success = self._handler.getCollectorData() - oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getPackageOrder(self, seqid, iprot, oprot): - args = getPackageOrder_args() - args.read(iprot) - iprot.readMessageEnd() - result = getPackageOrder_result() - result.success = self._handler.getPackageOrder(args.destination) - oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getFileOrder(self, seqid, iprot, oprot): - args = getFileOrder_args() - args.read(iprot) - iprot.readMessageEnd() - result = getFileOrder_result() - result.success = self._handler.getFileOrder(args.pid) - oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_generateAndAddPackages(self, seqid, iprot, oprot): - args = generateAndAddPackages_args() - args.read(iprot) - iprot.readMessageEnd() - result = generateAndAddPackages_result() - result.success = self._handler.generateAndAddPackages(args.links, args.dest) - oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_addPackage(self, seqid, iprot, oprot): - args = addPackage_args() - args.read(iprot) - iprot.readMessageEnd() - result = addPackage_result() - result.success = self._handler.addPackage(args.name, args.links, args.dest) - oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_addFiles(self, seqid, iprot, oprot): - args = addFiles_args() - args.read(iprot) - iprot.readMessageEnd() - result = addFiles_result() - self._handler.addFiles(args.pid, args.links) - oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_uploadContainer(self, seqid, iprot, oprot): - args = uploadContainer_args() - args.read(iprot) - iprot.readMessageEnd() - result = uploadContainer_result() - self._handler.uploadContainer(args.filename, args.data) - oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_deleteFiles(self, seqid, iprot, oprot): - args = deleteFiles_args() - args.read(iprot) - iprot.readMessageEnd() - result = deleteFiles_result() - self._handler.deleteFiles(args.fids) - oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_deletePackages(self, seqid, iprot, oprot): - args = deletePackages_args() - args.read(iprot) - iprot.readMessageEnd() - result = deletePackages_result() - self._handler.deletePackages(args.pids) - oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_pushToQueue(self, seqid, iprot, oprot): - args = pushToQueue_args() - args.read(iprot) - iprot.readMessageEnd() - result = pushToQueue_result() - self._handler.pushToQueue(args.pid) - oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_pullFromQueue(self, seqid, iprot, oprot): - args = pullFromQueue_args() - args.read(iprot) - iprot.readMessageEnd() - result = pullFromQueue_result() - self._handler.pullFromQueue(args.pid) - oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_restartPackage(self, seqid, iprot, oprot): - args = restartPackage_args() - args.read(iprot) - iprot.readMessageEnd() - result = restartPackage_result() - self._handler.restartPackage(args.pid) - oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_restartFile(self, seqid, iprot, oprot): - args = restartFile_args() - args.read(iprot) - iprot.readMessageEnd() - result = restartFile_result() - self._handler.restartFile(args.fid) - oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_recheckPackage(self, seqid, iprot, oprot): - args = recheckPackage_args() - args.read(iprot) - iprot.readMessageEnd() - result = recheckPackage_result() - self._handler.recheckPackage(args.pid) - oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_stopAllDownloads(self, seqid, iprot, oprot): - args = stopAllDownloads_args() - args.read(iprot) - iprot.readMessageEnd() - result = stopAllDownloads_result() - self._handler.stopAllDownloads() - oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_stopDownloads(self, seqid, iprot, oprot): - args = stopDownloads_args() - args.read(iprot) - iprot.readMessageEnd() - result = stopDownloads_result() - self._handler.stopDownloads(args.fids) - oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_setPackageName(self, seqid, iprot, oprot): - args = setPackageName_args() - args.read(iprot) - iprot.readMessageEnd() - result = setPackageName_result() - self._handler.setPackageName(args.pid, args.name) - oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_movePackage(self, seqid, iprot, oprot): - args = movePackage_args() - args.read(iprot) - iprot.readMessageEnd() - result = movePackage_result() - self._handler.movePackage(args.destination, args.pid) - oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_moveFiles(self, seqid, iprot, oprot): - args = moveFiles_args() - args.read(iprot) - iprot.readMessageEnd() - result = moveFiles_result() - self._handler.moveFiles(args.fids, args.pid) - oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_orderPackage(self, seqid, iprot, oprot): - args = orderPackage_args() - args.read(iprot) - iprot.readMessageEnd() - result = orderPackage_result() - self._handler.orderPackage(args.pid, args.position) - oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_orderFile(self, seqid, iprot, oprot): - args = orderFile_args() - args.read(iprot) - iprot.readMessageEnd() - result = orderFile_result() - self._handler.orderFile(args.fid, args.position) - oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_setPackageData(self, seqid, iprot, oprot): - args = setPackageData_args() - args.read(iprot) - iprot.readMessageEnd() - result = setPackageData_result() - try: - self._handler.setPackageData(args.pid, args.data) - except PackageDoesNotExists, e: - result.e = e - oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_deleteFinished(self, seqid, iprot, oprot): - args = deleteFinished_args() - args.read(iprot) - iprot.readMessageEnd() - result = deleteFinished_result() - result.success = self._handler.deleteFinished() - oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_restartFailed(self, seqid, iprot, oprot): - args = restartFailed_args() - args.read(iprot) - iprot.readMessageEnd() - result = restartFailed_result() - self._handler.restartFailed() - oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getEvents(self, seqid, iprot, oprot): - args = getEvents_args() - args.read(iprot) - iprot.readMessageEnd() - result = getEvents_result() - result.success = self._handler.getEvents(args.uuid) - oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getAccounts(self, seqid, iprot, oprot): - args = getAccounts_args() - args.read(iprot) - iprot.readMessageEnd() - result = getAccounts_result() - result.success = self._handler.getAccounts(args.refresh) - oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getAccountTypes(self, seqid, iprot, oprot): - args = getAccountTypes_args() - args.read(iprot) - iprot.readMessageEnd() - result = getAccountTypes_result() - result.success = self._handler.getAccountTypes() - oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_updateAccount(self, seqid, iprot, oprot): - args = updateAccount_args() - args.read(iprot) - iprot.readMessageEnd() - result = updateAccount_result() - self._handler.updateAccount(args.plugin, args.account, args.password, args.options) - oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_removeAccount(self, seqid, iprot, oprot): - args = removeAccount_args() - args.read(iprot) - iprot.readMessageEnd() - result = removeAccount_result() - self._handler.removeAccount(args.plugin, args.account) - oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_login(self, seqid, iprot, oprot): - args = login_args() - args.read(iprot) - iprot.readMessageEnd() - result = login_result() - result.success = self._handler.login(args.username, args.password) - oprot.writeMessageBegin("login", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getUserData(self, seqid, iprot, oprot): - args = getUserData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getUserData_result() - result.success = self._handler.getUserData(args.username, args.password) - oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getAllUserData(self, seqid, iprot, oprot): - args = getAllUserData_args() - args.read(iprot) - iprot.readMessageEnd() - result = getAllUserData_result() - result.success = self._handler.getAllUserData() - oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getServices(self, seqid, iprot, oprot): - args = getServices_args() - args.read(iprot) - iprot.readMessageEnd() - result = getServices_result() - result.success = self._handler.getServices() - oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_hasService(self, seqid, iprot, oprot): - args = hasService_args() - args.read(iprot) - iprot.readMessageEnd() - result = hasService_result() - result.success = self._handler.hasService(args.plugin, args.func) - oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_call(self, seqid, iprot, oprot): - args = call_args() - args.read(iprot) - iprot.readMessageEnd() - result = call_result() - try: - result.success = self._handler.call(args.info) - except ServiceDoesNotExists, ex: - result.ex = ex - except ServiceException, e: - result.e = e - oprot.writeMessageBegin("call", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getAllInfo(self, seqid, iprot, oprot): - args = getAllInfo_args() - args.read(iprot) - iprot.readMessageEnd() - result = getAllInfo_result() - result.success = self._handler.getAllInfo() - oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getInfoByPlugin(self, seqid, iprot, oprot): - args = getInfoByPlugin_args() - args.read(iprot) - iprot.readMessageEnd() - result = getInfoByPlugin_result() - result.success = self._handler.getInfoByPlugin(args.plugin) - oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_isCaptchaWaiting(self, seqid, iprot, oprot): - args = isCaptchaWaiting_args() - args.read(iprot) - iprot.readMessageEnd() - result = isCaptchaWaiting_result() - result.success = self._handler.isCaptchaWaiting() - oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getCaptchaTask(self, seqid, iprot, oprot): - args = getCaptchaTask_args() - args.read(iprot) - iprot.readMessageEnd() - result = getCaptchaTask_result() - result.success = self._handler.getCaptchaTask(args.exclusive) - oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_getCaptchaTaskStatus(self, seqid, iprot, oprot): - args = getCaptchaTaskStatus_args() - args.read(iprot) - iprot.readMessageEnd() - result = getCaptchaTaskStatus_result() - result.success = self._handler.getCaptchaTaskStatus(args.tid) - oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - def process_setCaptchaResult(self, seqid, iprot, oprot): - args = setCaptchaResult_args() - args.read(iprot) - iprot.readMessageEnd() - result = setCaptchaResult_result() - self._handler.setCaptchaResult(args.tid, args.result) - oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid) - result.write(oprot) - oprot.writeMessageEnd() - oprot.trans.flush() - - -# HELPER FUNCTIONS AND STRUCTURES - -class getConfigValue_args(TBase): - """ - Attributes: - - category - - option - - section - """ - - __slots__ = [ - 'category', - 'option', - 'section', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'category', None, None, ), # 1 - (2, TType.STRING, 'option', None, None, ), # 2 - (3, TType.STRING, 'section', None, None, ), # 3 - ) - - def __init__(self, category=None, option=None, section=None,): - self.category = category - self.option = option - self.section = section - - -class getConfigValue_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRING, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class setConfigValue_args(TBase): - """ - Attributes: - - category - - option - - value - - section - """ - - __slots__ = [ - 'category', - 'option', - 'value', - 'section', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'category', None, None, ), # 1 - (2, TType.STRING, 'option', None, None, ), # 2 - (3, TType.STRING, 'value', None, None, ), # 3 - (4, TType.STRING, 'section', None, None, ), # 4 - ) - - def __init__(self, category=None, option=None, value=None, section=None,): - self.category = category - self.option = option - self.value = value - self.section = section - - -class setConfigValue_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getConfig_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getConfig_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getPluginConfig_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getPluginConfig_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class pauseServer_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class pauseServer_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class unpauseServer_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class unpauseServer_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class togglePause_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class togglePause_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class statusServer_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class statusServer_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class freeSpace_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class freeSpace_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.I64, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getServerVersion_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getServerVersion_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRING, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class kill_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class kill_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class restart_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class restart_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getLog_args(TBase): - """ - Attributes: - - offset - """ - - __slots__ = [ - 'offset', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'offset', None, None, ), # 1 - ) - - def __init__(self, offset=None,): - self.offset = offset - - -class getLog_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class isTimeDownload_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class isTimeDownload_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class isTimeReconnect_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class isTimeReconnect_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class toggleReconnect_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class toggleReconnect_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class generatePackages_args(TBase): - """ - Attributes: - - links - """ - - __slots__ = [ - 'links', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1 - ) - - def __init__(self, links=None,): - self.links = links - - -class generatePackages_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class checkURLs_args(TBase): - """ - Attributes: - - urls - """ - - __slots__ = [ - 'urls', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1 - ) - - def __init__(self, urls=None,): - self.urls = urls - - -class checkURLs_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class parseURLs_args(TBase): - """ - Attributes: - - html - - url - """ - - __slots__ = [ - 'html', - 'url', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'html', None, None, ), # 1 - (2, TType.STRING, 'url', None, None, ), # 2 - ) - - def __init__(self, html=None, url=None,): - self.html = html - self.url = url - - -class parseURLs_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class checkOnlineStatus_args(TBase): - """ - Attributes: - - urls - """ - - __slots__ = [ - 'urls', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1 - ) - - def __init__(self, urls=None,): - self.urls = urls - - -class checkOnlineStatus_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class checkOnlineStatusContainer_args(TBase): - """ - Attributes: - - urls - - filename - - data - """ - - __slots__ = [ - 'urls', - 'filename', - 'data', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1 - (2, TType.STRING, 'filename', None, None, ), # 2 - (3, TType.STRING, 'data', None, None, ), # 3 - ) - - def __init__(self, urls=None, filename=None, data=None,): - self.urls = urls - self.filename = filename - self.data = data - - -class checkOnlineStatusContainer_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class pollResults_args(TBase): - """ - Attributes: - - rid - """ - - __slots__ = [ - 'rid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'rid', None, None, ), # 1 - ) - - def __init__(self, rid=None,): - self.rid = rid - - -class pollResults_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class statusDownloads_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class statusDownloads_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(DownloadInfo, DownloadInfo.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getPackageData_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class getPackageData_result(TBase): - """ - Attributes: - - success - - e - """ - - __slots__ = [ - 'success', - 'e', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0 - (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1 - ) - - def __init__(self, success=None, e=None,): - self.success = success - self.e = e - - -class getPackageInfo_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class getPackageInfo_result(TBase): - """ - Attributes: - - success - - e - """ - - __slots__ = [ - 'success', - 'e', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0 - (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1 - ) - - def __init__(self, success=None, e=None,): - self.success = success - self.e = e - - -class getFileData_args(TBase): - """ - Attributes: - - fid - """ - - __slots__ = [ - 'fid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - ) - - def __init__(self, fid=None,): - self.fid = fid - - -class getFileData_result(TBase): - """ - Attributes: - - success - - e - """ - - __slots__ = [ - 'success', - 'e', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None, ), # 0 - (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None, ), # 1 - ) - - def __init__(self, success=None, e=None,): - self.success = success - self.e = e - - -class getQueue_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getQueue_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getCollector_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getCollector_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getQueueData_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getQueueData_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getCollectorData_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getCollectorData_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getPackageOrder_args(TBase): - """ - Attributes: - - destination - """ - - __slots__ = [ - 'destination', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'destination', None, None, ), # 1 - ) - - def __init__(self, destination=None,): - self.destination = destination - - -class getPackageOrder_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getFileOrder_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class getFileOrder_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class generateAndAddPackages_args(TBase): - """ - Attributes: - - links - - dest - """ - - __slots__ = [ - 'links', - 'dest', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1 - (2, TType.I32, 'dest', None, None, ), # 2 - ) - - def __init__(self, links=None, dest=None,): - self.links = links - self.dest = dest - - -class generateAndAddPackages_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class addPackage_args(TBase): - """ - Attributes: - - name - - links - - dest - """ - - __slots__ = [ - 'name', - 'links', - 'dest', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'name', None, None, ), # 1 - (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2 - (3, TType.I32, 'dest', None, None, ), # 3 - ) - - def __init__(self, name=None, links=None, dest=None,): - self.name = name - self.links = links - self.dest = dest - - -class addPackage_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.I32, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class addFiles_args(TBase): - """ - Attributes: - - pid - - links - """ - - __slots__ = [ - 'pid', - 'links', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2 - ) - - def __init__(self, pid=None, links=None,): - self.pid = pid - self.links = links - - -class addFiles_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class uploadContainer_args(TBase): - """ - Attributes: - - filename - - data - """ - - __slots__ = [ - 'filename', - 'data', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'filename', None, None, ), # 1 - (2, TType.STRING, 'data', None, None, ), # 2 - ) - - def __init__(self, filename=None, data=None,): - self.filename = filename - self.data = data - - -class uploadContainer_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class deleteFiles_args(TBase): - """ - Attributes: - - fids - """ - - __slots__ = [ - 'fids', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1 - ) - - def __init__(self, fids=None,): - self.fids = fids - - -class deleteFiles_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class deletePackages_args(TBase): - """ - Attributes: - - pids - """ - - __slots__ = [ - 'pids', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'pids', (TType.I32,None), None, ), # 1 - ) - - def __init__(self, pids=None,): - self.pids = pids - - -class deletePackages_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class pushToQueue_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class pushToQueue_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class pullFromQueue_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class pullFromQueue_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class restartPackage_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class restartPackage_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class restartFile_args(TBase): - """ - Attributes: - - fid - """ - - __slots__ = [ - 'fid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - ) - - def __init__(self, fid=None,): - self.fid = fid - - -class restartFile_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class recheckPackage_args(TBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - -class recheckPackage_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class stopAllDownloads_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class stopAllDownloads_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class stopDownloads_args(TBase): - """ - Attributes: - - fids - """ - - __slots__ = [ - 'fids', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1 - ) - - def __init__(self, fids=None,): - self.fids = fids - - -class stopDownloads_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class setPackageName_args(TBase): - """ - Attributes: - - pid - - name - """ - - __slots__ = [ - 'pid', - 'name', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - (2, TType.STRING, 'name', None, None, ), # 2 - ) - - def __init__(self, pid=None, name=None,): - self.pid = pid - self.name = name - - -class setPackageName_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class movePackage_args(TBase): - """ - Attributes: - - destination - - pid - """ - - __slots__ = [ - 'destination', - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'destination', None, None, ), # 1 - (2, TType.I32, 'pid', None, None, ), # 2 - ) - - def __init__(self, destination=None, pid=None,): - self.destination = destination - self.pid = pid - - -class movePackage_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class moveFiles_args(TBase): - """ - Attributes: - - fids - - pid - """ - - __slots__ = [ - 'fids', - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1 - (2, TType.I32, 'pid', None, None, ), # 2 - ) - - def __init__(self, fids=None, pid=None,): - self.fids = fids - self.pid = pid - - -class moveFiles_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class orderPackage_args(TBase): - """ - Attributes: - - pid - - position - """ - - __slots__ = [ - 'pid', - 'position', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - (2, TType.I16, 'position', None, None, ), # 2 - ) - - def __init__(self, pid=None, position=None,): - self.pid = pid - self.position = position - - -class orderPackage_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class orderFile_args(TBase): - """ - Attributes: - - fid - - position - """ - - __slots__ = [ - 'fid', - 'position', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - (2, TType.I16, 'position', None, None, ), # 2 - ) - - def __init__(self, fid=None, position=None,): - self.fid = fid - self.position = position - - -class orderFile_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class setPackageData_args(TBase): - """ - Attributes: - - pid - - data - """ - - __slots__ = [ - 'pid', - 'data', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - (2, TType.MAP, 'data', (TType.STRING,None,TType.STRING,None), None, ), # 2 - ) - - def __init__(self, pid=None, data=None,): - self.pid = pid - self.data = data - - -class setPackageData_result(TBase): - """ - Attributes: - - e - """ - - __slots__ = [ - 'e', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1 - ) - - def __init__(self, e=None,): - self.e = e - - -class deleteFinished_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class deleteFinished_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class restartFailed_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class restartFailed_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getEvents_args(TBase): - """ - Attributes: - - uuid - """ - - __slots__ = [ - 'uuid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'uuid', None, None, ), # 1 - ) - - def __init__(self, uuid=None,): - self.uuid = uuid - - -class getEvents_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(EventInfo, EventInfo.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getAccounts_args(TBase): - """ - Attributes: - - refresh - """ - - __slots__ = [ - 'refresh', - ] - - thrift_spec = ( - None, # 0 - (1, TType.BOOL, 'refresh', None, None, ), # 1 - ) - - def __init__(self, refresh=None,): - self.refresh = refresh - - -class getAccounts_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRUCT,(AccountInfo, AccountInfo.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getAccountTypes_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getAccountTypes_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class updateAccount_args(TBase): - """ - Attributes: - - plugin - - account - - password - - options - """ - - __slots__ = [ - 'plugin', - 'account', - 'password', - 'options', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - (2, TType.STRING, 'account', None, None, ), # 2 - (3, TType.STRING, 'password', None, None, ), # 3 - (4, TType.MAP, 'options', (TType.STRING,None,TType.STRING,None), None, ), # 4 - ) - - def __init__(self, plugin=None, account=None, password=None, options=None,): - self.plugin = plugin - self.account = account - self.password = password - self.options = options - - -class updateAccount_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class removeAccount_args(TBase): - """ - Attributes: - - plugin - - account - """ - - __slots__ = [ - 'plugin', - 'account', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - (2, TType.STRING, 'account', None, None, ), # 2 - ) - - def __init__(self, plugin=None, account=None,): - self.plugin = plugin - self.account = account - - -class removeAccount_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class login_args(TBase): - """ - Attributes: - - username - - password - """ - - __slots__ = [ - 'username', - 'password', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'username', None, None, ), # 1 - (2, TType.STRING, 'password', None, None, ), # 2 - ) - - def __init__(self, username=None, password=None,): - self.username = username - self.password = password - - -class login_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getUserData_args(TBase): - """ - Attributes: - - username - - password - """ - - __slots__ = [ - 'username', - 'password', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'username', None, None, ), # 1 - (2, TType.STRING, 'password', None, None, ), # 2 - ) - - def __init__(self, username=None, password=None,): - self.username = username - self.password = password - - -class getUserData_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getAllUserData_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getAllUserData_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(UserData, UserData.thrift_spec)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getServices_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getServices_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class hasService_args(TBase): - """ - Attributes: - - plugin - - func - """ - - __slots__ = [ - 'plugin', - 'func', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - (2, TType.STRING, 'func', None, None, ), # 2 - ) - - def __init__(self, plugin=None, func=None,): - self.plugin = plugin - self.func = func - - -class hasService_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class call_args(TBase): - """ - Attributes: - - info - """ - - __slots__ = [ - 'info', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None, ), # 1 - ) - - def __init__(self, info=None,): - self.info = info - - -class call_result(TBase): - """ - Attributes: - - success - - ex - - e - """ - - __slots__ = [ - 'success', - 'ex', - 'e', - ] - - thrift_spec = ( - (0, TType.STRING, 'success', None, None, ), # 0 - (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None, ), # 1 - (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None, ), # 2 - ) - - def __init__(self, success=None, ex=None, e=None,): - self.success = success - self.ex = ex - self.e = e - - -class getAllInfo_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class getAllInfo_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getInfoByPlugin_args(TBase): - """ - Attributes: - - plugin - """ - - __slots__ = [ - 'plugin', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - ) - - def __init__(self, plugin=None,): - self.plugin = plugin - - -class getInfoByPlugin_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.MAP, 'success', (TType.STRING,None,TType.STRING,None), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class isCaptchaWaiting_args(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - - -class isCaptchaWaiting_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.BOOL, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getCaptchaTask_args(TBase): - """ - Attributes: - - exclusive - """ - - __slots__ = [ - 'exclusive', - ] - - thrift_spec = ( - None, # 0 - (1, TType.BOOL, 'exclusive', None, None, ), # 1 - ) - - def __init__(self, exclusive=None,): - self.exclusive = exclusive - - -class getCaptchaTask_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class getCaptchaTaskStatus_args(TBase): - """ - Attributes: - - tid - """ - - __slots__ = [ - 'tid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'tid', None, None, ), # 1 - ) - - def __init__(self, tid=None,): - self.tid = tid - - -class getCaptchaTaskStatus_result(TBase): - """ - Attributes: - - success - """ - - __slots__ = [ - 'success', - ] - - thrift_spec = ( - (0, TType.STRING, 'success', None, None, ), # 0 - ) - - def __init__(self, success=None,): - self.success = success - - -class setCaptchaResult_args(TBase): - """ - Attributes: - - tid - - result - """ - - __slots__ = [ - 'tid', - 'result', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'tid', None, None, ), # 1 - (2, TType.STRING, 'result', None, None, ), # 2 - ) - - def __init__(self, tid=None, result=None,): - self.tid = tid - self.result = result - - -class setCaptchaResult_result(TBase): - - __slots__ = [ - ] - - thrift_spec = ( - ) - diff --git a/module/remote/thriftbackend/thriftgen/pyload/__init__.py b/module/remote/thriftbackend/thriftgen/pyload/__init__.py deleted file mode 100644 index ce7f52598..000000000 --- a/module/remote/thriftbackend/thriftgen/pyload/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ['ttypes', 'constants', 'Pyload'] diff --git a/module/remote/thriftbackend/thriftgen/pyload/constants.py b/module/remote/thriftbackend/thriftgen/pyload/constants.py deleted file mode 100644 index f8960dc63..000000000 --- a/module/remote/thriftbackend/thriftgen/pyload/constants.py +++ /dev/null @@ -1,11 +0,0 @@ -# -# Autogenerated by Thrift Compiler (0.9.0-dev) -# -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING -# -# options string: py:slots,dynamic -# - -from thrift.Thrift import TType, TMessageType, TException -from ttypes import * - diff --git a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py deleted file mode 100644 index 1299b515d..000000000 --- a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py +++ /dev/null @@ -1,835 +0,0 @@ -# -# Autogenerated by Thrift Compiler (0.9.0-dev) -# -# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING -# -# options string: py:slots,dynamic -# - -from thrift.Thrift import TType, TMessageType, TException - -from thrift.protocol.TBase import TBase, TExceptionBase - - -class DownloadStatus(TBase): - Finished = 0 - Offline = 1 - Online = 2 - Queued = 3 - Skipped = 4 - Waiting = 5 - TempOffline = 6 - Starting = 7 - Failed = 8 - Aborted = 9 - Decrypting = 10 - Custom = 11 - Downloading = 12 - Processing = 13 - Unknown = 14 - - _VALUES_TO_NAMES = { - 0: "Finished", - 1: "Offline", - 2: "Online", - 3: "Queued", - 4: "Skipped", - 5: "Waiting", - 6: "TempOffline", - 7: "Starting", - 8: "Failed", - 9: "Aborted", - 10: "Decrypting", - 11: "Custom", - 12: "Downloading", - 13: "Processing", - 14: "Unknown", - } - - _NAMES_TO_VALUES = { - "Finished": 0, - "Offline": 1, - "Online": 2, - "Queued": 3, - "Skipped": 4, - "Waiting": 5, - "TempOffline": 6, - "Starting": 7, - "Failed": 8, - "Aborted": 9, - "Decrypting": 10, - "Custom": 11, - "Downloading": 12, - "Processing": 13, - "Unknown": 14, - } - -class Destination(TBase): - Collector = 0 - Queue = 1 - - _VALUES_TO_NAMES = { - 0: "Collector", - 1: "Queue", - } - - _NAMES_TO_VALUES = { - "Collector": 0, - "Queue": 1, - } - -class ElementType(TBase): - Package = 0 - File = 1 - - _VALUES_TO_NAMES = { - 0: "Package", - 1: "File", - } - - _NAMES_TO_VALUES = { - "Package": 0, - "File": 1, - } - -class Input(TBase): - NONE = 0 - TEXT = 1 - TEXTBOX = 2 - PASSWORD = 3 - BOOL = 4 - CLICK = 5 - CHOICE = 6 - MULTIPLE = 7 - LIST = 8 - TABLE = 9 - - _VALUES_TO_NAMES = { - 0: "NONE", - 1: "TEXT", - 2: "TEXTBOX", - 3: "PASSWORD", - 4: "BOOL", - 5: "CLICK", - 6: "CHOICE", - 7: "MULTIPLE", - 8: "LIST", - 9: "TABLE", - } - - _NAMES_TO_VALUES = { - "NONE": 0, - "TEXT": 1, - "TEXTBOX": 2, - "PASSWORD": 3, - "BOOL": 4, - "CLICK": 5, - "CHOICE": 6, - "MULTIPLE": 7, - "LIST": 8, - "TABLE": 9, - } - -class Output(TBase): - CAPTCHA = 1 - QUESTION = 2 - NOTIFICATION = 4 - - _VALUES_TO_NAMES = { - 1: "CAPTCHA", - 2: "QUESTION", - 4: "NOTIFICATION", - } - - _NAMES_TO_VALUES = { - "CAPTCHA": 1, - "QUESTION": 2, - "NOTIFICATION": 4, - } - - -class DownloadInfo(TBase): - """ - Attributes: - - fid - - name - - speed - - eta - - format_eta - - bleft - - size - - format_size - - percent - - status - - statusmsg - - format_wait - - wait_until - - packageID - - packageName - - plugin - """ - - __slots__ = [ - 'fid', - 'name', - 'speed', - 'eta', - 'format_eta', - 'bleft', - 'size', - 'format_size', - 'percent', - 'status', - 'statusmsg', - 'format_wait', - 'wait_until', - 'packageID', - 'packageName', - 'plugin', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - (2, TType.STRING, 'name', None, None, ), # 2 - (3, TType.I64, 'speed', None, None, ), # 3 - (4, TType.I32, 'eta', None, None, ), # 4 - (5, TType.STRING, 'format_eta', None, None, ), # 5 - (6, TType.I64, 'bleft', None, None, ), # 6 - (7, TType.I64, 'size', None, None, ), # 7 - (8, TType.STRING, 'format_size', None, None, ), # 8 - (9, TType.BYTE, 'percent', None, None, ), # 9 - (10, TType.I32, 'status', None, None, ), # 10 - (11, TType.STRING, 'statusmsg', None, None, ), # 11 - (12, TType.STRING, 'format_wait', None, None, ), # 12 - (13, TType.I64, 'wait_until', None, None, ), # 13 - (14, TType.I32, 'packageID', None, None, ), # 14 - (15, TType.STRING, 'packageName', None, None, ), # 15 - (16, TType.STRING, 'plugin', None, None, ), # 16 - ) - - def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,): - self.fid = fid - self.name = name - self.speed = speed - self.eta = eta - self.format_eta = format_eta - self.bleft = bleft - self.size = size - self.format_size = format_size - self.percent = percent - self.status = status - self.statusmsg = statusmsg - self.format_wait = format_wait - self.wait_until = wait_until - self.packageID = packageID - self.packageName = packageName - self.plugin = plugin - - -class ServerStatus(TBase): - """ - Attributes: - - pause - - active - - queue - - total - - speed - - download - - reconnect - """ - - __slots__ = [ - 'pause', - 'active', - 'queue', - 'total', - 'speed', - 'download', - 'reconnect', - ] - - thrift_spec = ( - None, # 0 - (1, TType.BOOL, 'pause', None, None, ), # 1 - (2, TType.I16, 'active', None, None, ), # 2 - (3, TType.I16, 'queue', None, None, ), # 3 - (4, TType.I16, 'total', None, None, ), # 4 - (5, TType.I64, 'speed', None, None, ), # 5 - (6, TType.BOOL, 'download', None, None, ), # 6 - (7, TType.BOOL, 'reconnect', None, None, ), # 7 - ) - - def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,): - self.pause = pause - self.active = active - self.queue = queue - self.total = total - self.speed = speed - self.download = download - self.reconnect = reconnect - - -class ConfigItem(TBase): - """ - Attributes: - - name - - description - - value - - type - """ - - __slots__ = [ - 'name', - 'description', - 'value', - 'type', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'name', None, None, ), # 1 - (2, TType.STRING, 'description', None, None, ), # 2 - (3, TType.STRING, 'value', None, None, ), # 3 - (4, TType.STRING, 'type', None, None, ), # 4 - ) - - def __init__(self, name=None, description=None, value=None, type=None,): - self.name = name - self.description = description - self.value = value - self.type = type - - -class ConfigSection(TBase): - """ - Attributes: - - name - - description - - items - - outline - """ - - __slots__ = [ - 'name', - 'description', - 'items', - 'outline', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'name', None, None, ), # 1 - (2, TType.STRING, 'description', None, None, ), # 2 - (3, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 3 - (4, TType.STRING, 'outline', None, None, ), # 4 - ) - - def __init__(self, name=None, description=None, items=None, outline=None,): - self.name = name - self.description = description - self.items = items - self.outline = outline - - -class FileData(TBase): - """ - Attributes: - - fid - - url - - name - - plugin - - size - - format_size - - status - - statusmsg - - packageID - - error - - order - """ - - __slots__ = [ - 'fid', - 'url', - 'name', - 'plugin', - 'size', - 'format_size', - 'status', - 'statusmsg', - 'packageID', - 'error', - 'order', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - (2, TType.STRING, 'url', None, None, ), # 2 - (3, TType.STRING, 'name', None, None, ), # 3 - (4, TType.STRING, 'plugin', None, None, ), # 4 - (5, TType.I64, 'size', None, None, ), # 5 - (6, TType.STRING, 'format_size', None, None, ), # 6 - (7, TType.I32, 'status', None, None, ), # 7 - (8, TType.STRING, 'statusmsg', None, None, ), # 8 - (9, TType.I32, 'packageID', None, None, ), # 9 - (10, TType.STRING, 'error', None, None, ), # 10 - (11, TType.I16, 'order', None, None, ), # 11 - ) - - def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,): - self.fid = fid - self.url = url - self.name = name - self.plugin = plugin - self.size = size - self.format_size = format_size - self.status = status - self.statusmsg = statusmsg - self.packageID = packageID - self.error = error - self.order = order - - -class PackageData(TBase): - """ - Attributes: - - pid - - name - - folder - - site - - password - - dest - - order - - linksdone - - sizedone - - sizetotal - - linkstotal - - links - - fids - """ - - __slots__ = [ - 'pid', - 'name', - 'folder', - 'site', - 'password', - 'dest', - 'order', - 'linksdone', - 'sizedone', - 'sizetotal', - 'linkstotal', - 'links', - 'fids', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - (2, TType.STRING, 'name', None, None, ), # 2 - (3, TType.STRING, 'folder', None, None, ), # 3 - (4, TType.STRING, 'site', None, None, ), # 4 - (5, TType.STRING, 'password', None, None, ), # 5 - (6, TType.I32, 'dest', None, None, ), # 6 - (7, TType.I16, 'order', None, None, ), # 7 - (8, TType.I16, 'linksdone', None, None, ), # 8 - (9, TType.I64, 'sizedone', None, None, ), # 9 - (10, TType.I64, 'sizetotal', None, None, ), # 10 - (11, TType.I16, 'linkstotal', None, None, ), # 11 - (12, TType.LIST, 'links', (TType.STRUCT,(FileData, FileData.thrift_spec)), None, ), # 12 - (13, TType.LIST, 'fids', (TType.I32,None), None, ), # 13 - ) - - def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,): - self.pid = pid - self.name = name - self.folder = folder - self.site = site - self.password = password - self.dest = dest - self.order = order - self.linksdone = linksdone - self.sizedone = sizedone - self.sizetotal = sizetotal - self.linkstotal = linkstotal - self.links = links - self.fids = fids - - -class InteractionTask(TBase): - """ - Attributes: - - iid - - input - - structure - - preset - - output - - data - - title - - description - - plugin - """ - - __slots__ = [ - 'iid', - 'input', - 'structure', - 'preset', - 'output', - 'data', - 'title', - 'description', - 'plugin', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'iid', None, None, ), # 1 - (2, TType.I32, 'input', None, None, ), # 2 - (3, TType.LIST, 'structure', (TType.STRING,None), None, ), # 3 - (4, TType.LIST, 'preset', (TType.STRING,None), None, ), # 4 - (5, TType.I32, 'output', None, None, ), # 5 - (6, TType.LIST, 'data', (TType.STRING,None), None, ), # 6 - (7, TType.STRING, 'title', None, None, ), # 7 - (8, TType.STRING, 'description', None, None, ), # 8 - (9, TType.STRING, 'plugin', None, None, ), # 9 - ) - - def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,): - self.iid = iid - self.input = input - self.structure = structure - self.preset = preset - self.output = output - self.data = data - self.title = title - self.description = description - self.plugin = plugin - - -class CaptchaTask(TBase): - """ - Attributes: - - tid - - data - - type - - resultType - """ - - __slots__ = [ - 'tid', - 'data', - 'type', - 'resultType', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I16, 'tid', None, None, ), # 1 - (2, TType.STRING, 'data', None, None, ), # 2 - (3, TType.STRING, 'type', None, None, ), # 3 - (4, TType.STRING, 'resultType', None, None, ), # 4 - ) - - def __init__(self, tid=None, data=None, type=None, resultType=None,): - self.tid = tid - self.data = data - self.type = type - self.resultType = resultType - - -class EventInfo(TBase): - """ - Attributes: - - eventname - - id - - type - - destination - """ - - __slots__ = [ - 'eventname', - 'id', - 'type', - 'destination', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'eventname', None, None, ), # 1 - (2, TType.I32, 'id', None, None, ), # 2 - (3, TType.I32, 'type', None, None, ), # 3 - (4, TType.I32, 'destination', None, None, ), # 4 - ) - - def __init__(self, eventname=None, id=None, type=None, destination=None,): - self.eventname = eventname - self.id = id - self.type = type - self.destination = destination - - -class UserData(TBase): - """ - Attributes: - - name - - email - - role - - permission - - templateName - """ - - __slots__ = [ - 'name', - 'email', - 'role', - 'permission', - 'templateName', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'name', None, None, ), # 1 - (2, TType.STRING, 'email', None, None, ), # 2 - (3, TType.I32, 'role', None, None, ), # 3 - (4, TType.I32, 'permission', None, None, ), # 4 - (5, TType.STRING, 'templateName', None, None, ), # 5 - ) - - def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,): - self.name = name - self.email = email - self.role = role - self.permission = permission - self.templateName = templateName - - -class AccountInfo(TBase): - """ - Attributes: - - validuntil - - login - - options - - valid - - trafficleft - - maxtraffic - - premium - - type - """ - - __slots__ = [ - 'validuntil', - 'login', - 'options', - 'valid', - 'trafficleft', - 'maxtraffic', - 'premium', - 'type', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I64, 'validuntil', None, None, ), # 1 - (2, TType.STRING, 'login', None, None, ), # 2 - (3, TType.MAP, 'options', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 3 - (4, TType.BOOL, 'valid', None, None, ), # 4 - (5, TType.I64, 'trafficleft', None, None, ), # 5 - (6, TType.I64, 'maxtraffic', None, None, ), # 6 - (7, TType.BOOL, 'premium', None, None, ), # 7 - (8, TType.STRING, 'type', None, None, ), # 8 - ) - - def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,): - self.validuntil = validuntil - self.login = login - self.options = options - self.valid = valid - self.trafficleft = trafficleft - self.maxtraffic = maxtraffic - self.premium = premium - self.type = type - - -class ServiceCall(TBase): - """ - Attributes: - - plugin - - func - - arguments - - parseArguments - """ - - __slots__ = [ - 'plugin', - 'func', - 'arguments', - 'parseArguments', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - (2, TType.STRING, 'func', None, None, ), # 2 - (3, TType.LIST, 'arguments', (TType.STRING,None), None, ), # 3 - (4, TType.BOOL, 'parseArguments', None, None, ), # 4 - ) - - def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,): - self.plugin = plugin - self.func = func - self.arguments = arguments - self.parseArguments = parseArguments - - -class OnlineStatus(TBase): - """ - Attributes: - - name - - plugin - - packagename - - status - - size - """ - - __slots__ = [ - 'name', - 'plugin', - 'packagename', - 'status', - 'size', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'name', None, None, ), # 1 - (2, TType.STRING, 'plugin', None, None, ), # 2 - (3, TType.STRING, 'packagename', None, None, ), # 3 - (4, TType.I32, 'status', None, None, ), # 4 - (5, TType.I64, 'size', None, None, ), # 5 - ) - - def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,): - self.name = name - self.plugin = plugin - self.packagename = packagename - self.status = status - self.size = size - - -class OnlineCheck(TBase): - """ - Attributes: - - rid - - data - """ - - __slots__ = [ - 'rid', - 'data', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'rid', None, None, ), # 1 - (2, TType.MAP, 'data', (TType.STRING,None,TType.STRUCT,(OnlineStatus, OnlineStatus.thrift_spec)), None, ), # 2 - ) - - def __init__(self, rid=None, data=None,): - self.rid = rid - self.data = data - - -class PackageDoesNotExists(TExceptionBase): - """ - Attributes: - - pid - """ - - __slots__ = [ - 'pid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'pid', None, None, ), # 1 - ) - - def __init__(self, pid=None,): - self.pid = pid - - def __str__(self): - return repr(self) - - -class FileDoesNotExists(TExceptionBase): - """ - Attributes: - - fid - """ - - __slots__ = [ - 'fid', - ] - - thrift_spec = ( - None, # 0 - (1, TType.I32, 'fid', None, None, ), # 1 - ) - - def __init__(self, fid=None,): - self.fid = fid - - def __str__(self): - return repr(self) - - -class ServiceDoesNotExists(TExceptionBase): - """ - Attributes: - - plugin - - func - """ - - __slots__ = [ - 'plugin', - 'func', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'plugin', None, None, ), # 1 - (2, TType.STRING, 'func', None, None, ), # 2 - ) - - def __init__(self, plugin=None, func=None,): - self.plugin = plugin - self.func = func - - def __str__(self): - return repr(self) - - -class ServiceException(TExceptionBase): - """ - Attributes: - - msg - """ - - __slots__ = [ - 'msg', - ] - - thrift_spec = ( - None, # 0 - (1, TType.STRING, 'msg', None, None, ), # 1 - ) - - def __init__(self, msg=None,): - self.msg = msg - - def __str__(self): - return repr(self) - diff --git a/module/setup.py b/module/setup.py deleted file mode 100644 index 42b24859f..000000000 --- a/module/setup.py +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" -from getpass import getpass -import module.common.pylgettext as gettext -import os -from os import makedirs -from os.path import abspath -from os.path import dirname -from os.path import exists -from os.path import join -from subprocess import PIPE -from subprocess import call -import sys -from sys import exit -from module.utils import get_console_encoding - -class Setup(): - """ - pyLoads initial setup configuration assistent - """ - - def __init__(self, path, config): - self.path = path - self.config = config - self.stdin_encoding = get_console_encoding(sys.stdin.encoding) - - def start(self): - langs = self.config.getMetaData("general", "language")["type"].split(";") - lang = self.ask(u"Choose your Language / Wähle deine Sprache", "en", langs) - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("setup", join(self.path, "locale"), languages=[lang, "en"], fallback=True) - translation.install(True) - - #Input shorthand for yes - self.yes = _("y") - #Input shorthand for no - self.no = _("n") - - # print "" - # print _("Would you like to configure pyLoad via Webinterface?") - # print _("You need a Browser and a connection to this PC for it.") - # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True) - # if viaweb: - # try: - # from module.web import ServerThread - # ServerThread.setup = self - # from module.web import webinterface - # webinterface.run_simple() - # return False - # except Exception, e: - # print "Setup failed with this error: ", e - # print "Falling back to commandline setup." - - - print "" - print _("Welcome to the pyLoad Configuration Assistent.") - print _("It will check your system and make a basic setup in order to run pyLoad.") - print "" - print _("The value in brackets [] always is the default value,") - print _("in case you don't want to change it or you are unsure what to choose, just hit enter.") - print _( - "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore.") - print _("If you have any problems with this assistent hit STRG-C,") - print _("to abort and don't let him start with pyLoadCore automatically anymore.") - print "" - print _("When you are ready for system check, hit enter.") - raw_input() - - basic, ssl, captcha, gui, web, js = self.system_check() - print "" - - if not basic: - print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.") - print _("Please correct this and re-run pyLoad.") - print _("Setup will now close.") - raw_input() - return False - - raw_input(_("System check finished, hit enter to see your status report.")) - print "" - print _("## Status ##") - print "" - - avail = [] - if self.check_module("Crypto"): avail.append(_("container decrypting")) - if ssl: avail.append(_("ssl connection")) - if captcha: avail.append(_("automatic captcha decryption")) - if gui: avail.append(_("GUI")) - if web: avail.append(_("Webinterface")) - if js: avail.append(_("extended Click'N'Load")) - - string = "" - - for av in avail: - string += ", " + av - - print _("Features available:") + string[1:] - print "" - - if len(avail) < 5: - print _("Featues missing: ") - print - - if not self.check_module("Crypto"): - print _("no py-crypto available") - print _("You need this if you want to decrypt container files.") - print "" - - if not ssl: - print _("no SSL available") - print _("This is needed if you want to establish a secure connection to core or webinterface.") - print _("If you only want to access locally to pyLoad ssl is not usefull.") - print "" - - if not captcha: - print _("no Captcha Recognition available") - print _("Only needed for some hosters and as freeuser.") - print "" - - if not gui: - print _("Gui not available") - print _("The Graphical User Interface.") - print "" - - if not js: - print _("no JavaScript engine found") - print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino") - - print _("You can abort the setup now and fix some dependicies if you want.") - - con = self.ask(_("Continue with setup?"), self.yes, bool=True) - - if not con: - return False - - print "" - print _("Do you want to change the config path? Current is %s") % abspath("") - print _( - "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.") - path = self.ask(_("Change config path?"), self.no, bool=True) - if path: - self.conf_path() - #calls exit when changed - - print "" - print _("Do you want to configure login data and basic settings?") - print _("This is recommend for first run.") - con = self.ask(_("Make basic setup?"), self.yes, bool=True) - - if con: - self.conf_basic() - - if ssl: - print "" - print _("Do you want to configure ssl?") - ssl = self.ask(_("Configure ssl?"), self.no, bool=True) - if ssl: - self.conf_ssl() - - if web: - print "" - print _("Do you want to configure webinterface?") - web = self.ask(_("Configure webinterface?"), self.yes, bool=True) - if web: - self.conf_web() - - print "" - print _("Setup finished successfully.") - print _("Hit enter to exit and restart pyLoad") - raw_input() - return True - - def system_check(self): - """ make a systemcheck and return the results""" - print _("## System Check ##") - - if sys.version_info[:2] > (2, 7): - print _("Your python version is to new, Please use Python 2.6/2.7") - python = False - elif sys.version_info[:2] < (2, 5): - print _("Your python version is to old, Please use at least Python 2.5") - python = False - else: - print _("Python Version: OK") - python = True - - curl = self.check_module("pycurl") - self.print_dep("pycurl", curl) - - sqlite = self.check_module("sqlite3") - self.print_dep("sqlite3", sqlite) - - basic = python and curl and sqlite - - print "" - - crypto = self.check_module("Crypto") - self.print_dep("pycrypto", crypto) - - ssl = self.check_module("OpenSSL") - self.print_dep("py-OpenSSL", ssl) - - print "" - - pil = self.check_module("Image") - self.print_dep("py-imaging", pil) - - if os.name == "nt": - tesser = self.check_prog([join(pypath, "tesseract", "tesseract.exe"), "-v"]) - else: - tesser = self.check_prog(["tesseract", "-v"]) - - self.print_dep("tesseract", tesser) - - captcha = pil and tesser - - print "" - - gui = self.check_module("PyQt4") - self.print_dep("PyQt4", gui) - - print "" - jinja = True - - try: - import jinja2 - - v = jinja2.__version__ - if v and "unknown" not in v: - if not v.startswith("2.5") and not v.startswith("2.6"): - print _("Your installed jinja2 version %s seems too old.") % jinja2.__version__ - print _("You can safely continue but if the webinterface is not working,") - print _("please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary.") - print - jinja = False - except: - pass - - self.print_dep("jinja2", jinja) - beaker = self.check_module("beaker") - self.print_dep("beaker", beaker) - - web = sqlite and beaker - - from module.common import JsEngine - - js = True if JsEngine.ENGINE else False - self.print_dep(_("JS engine"), js) - - return basic, ssl, captcha, gui, web, js - - def conf_basic(self): - print "" - print _("## Basic Setup ##") - - print "" - print _("The following logindata is valid for CLI, GUI and webinterface.") - - from module.database import DatabaseBackend - - db = DatabaseBackend(None) - db.setup() - username = self.ask(_("Username"), "User") - password = self.ask("", "", password=True) - db.addUser(username, password) - db.shutdown() - - print "" - print _("External clients (GUI, CLI or other) need remote access to work over the network.") - print _("However, if you only want to use the webinterface you may disable it to save ram.") - self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.yes, bool=True) - - print "" - langs = self.config.getMetaData("general", "language") - self.config["general"]["language"] = self.ask(_("Language"), "en", langs["type"].split(";")) - - self.config["general"]["download_folder"] = self.ask(_("Downloadfolder"), "Downloads") - self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3") - #print _("You should disable checksum proofing, if you have low hardware requirements.") - #self.config["general"]["checksum"] = self.ask(_("Proof checksum?"), "y", bool=True) - - reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True) - self.config["reconnect"]["activated"] = reconnect - if reconnect: - self.config["reconnect"]["method"] = self.ask(_("Reconnect script location"), "./reconnect.sh") - - - def conf_web(self): - print "" - print _("## Webinterface Setup ##") - - print "" - self.config["webinterface"]["activated"] = self.ask(_("Activate webinterface?"), self.yes, bool=True) - print "" - print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.") - self.config["webinterface"]["host"] = self.ask(_("Address"), "0.0.0.0") - self.config["webinterface"]["port"] = self.ask(_("Port"), "8000") - print "" - print _("pyLoad offers several server backends, now following a short explanation.") - print "builtin:", _("Default server, best choice if you dont know which one to choose.") - print "threaded:", _("This server offers SSL and is a good alternative to builtin.") - print "fastcgi:", _( - "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job.") - print "lightweight:", _("Very fast alternative written in C, requires libev and linux knowlegde.") - print "\t", _("Get it from here: https://github.com/jonashaag/bjoern, compile it") - print "\t", _("and copy bjoern.so to module/lib") - - print - print _( - "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface") - print _("come back here and change the builtin server to the threaded one here.") - - self.config["webinterface"]["server"] = self.ask(_("Server"), "builtin", - ["builtin", "threaded", "fastcgi", "lightweight"]) - - def conf_ssl(self): - print "" - print _("## SSL Setup ##") - print "" - print _("Execute these commands from pyLoad config folder to make ssl certificates:") - print "" - print "openssl genrsa -out ssl.key 1024" - print "openssl req -new -key ssl.key -out ssl.csr" - print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt " - print "" - print _("If you're done and everything went fine, you can activate ssl now.") - - self.config["ssl"]["activated"] = self.ask(_("Activate SSL?"), self.yes, bool=True) - - def set_user(self): - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("setup", join(self.path, "locale"), - languages=[self.config["general"]["language"], "en"], fallback=True) - translation.install(True) - - from module.database import DatabaseBackend - - db = DatabaseBackend(None) - db.setup() - - noaction = True - try: - while True: - print _("Select action") - print _("1 - Create/Edit user") - print _("2 - List users") - print _("3 - Remove user") - print _("4 - Quit") - action = raw_input("[1]/2/3/4: ") - if not action in ("1", "2", "3", "4"): - continue - elif action == "1": - print "" - username = self.ask(_("Username"), "User") - password = self.ask("", "", password=True) - db.addUser(username, password) - noaction = False - elif action == "2": - print "" - print _("Users") - print "-----" - users = db.listUsers() - noaction = False - for user in users: - print user - print "-----" - print "" - elif action == "3": - print "" - username = self.ask(_("Username"), "") - if username: - db.removeUser(username) - noaction = False - elif action == "4": - break - finally: - if not noaction: - db.shutdown() - - def conf_path(self, trans=False): - if trans: - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("setup", join(self.path, "locale"), - languages=[self.config["general"]["language"], "en"], fallback=True) - translation.install(True) - - print _("Setting new configpath, current configuration will not be transfered!") - path = self.ask(_("Configpath"), abspath("")) - try: - path = join(pypath, path) - if not exists(path): - makedirs(path) - f = open(join(pypath, "module", "config", "configdir"), "wb") - f.write(path) - f.close() - print _("Configpath changed, setup will now close, please restart to go on.") - print _("Press Enter to exit.") - raw_input() - exit() - except Exception, e: - print _("Setting config path failed: %s") % str(e) - - def print_dep(self, name, value): - """Print Status of dependency""" - if value: - print _("%s: OK") % name - else: - print _("%s: missing") % name - - - def check_module(self, module): - try: - __import__(module) - return True - except: - return False - - def check_prog(self, command): - pipe = PIPE - try: - call(command, stdout=pipe, stderr=pipe) - return True - except: - return False - - def ask(self, qst, default, answers=[], bool=False, password=False): - """produce one line to asking for input""" - if answers: - info = "(" - - for i, answer in enumerate(answers): - info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer) - - info += ")" - elif bool: - if default == self.yes: - info = "([%s]/%s)" % (self.yes, self.no) - else: - info = "(%s/[%s])" % (self.yes, self.no) - else: - info = "[%s]" % default - - if password: - p1 = True - p2 = False - while p1 != p2: - # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS) - sys.stdout.write(_("Password: ")) - p1 = getpass("") - - if len(p1) < 4: - print _("Password too short. Use at least 4 symbols.") - continue - - sys.stdout.write(_("Password (again): ")) - p2 = getpass("") - - if p1 == p2: - return p1 - else: - print _("Passwords did not match.") - - while True: - try: - input = raw_input(qst + " %s: " % info) - except KeyboardInterrupt: - print "\nSetup interrupted" - exit() - - input = input.decode(self.stdin_encoding) - - if input.strip() == "": - input = default - - if bool: - # yes, true,t are inputs for booleans with value true - if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]: - return True - # no, false,f are inputs for booleans with value false - elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]: - return False - else: - print _("Invalid Input") - continue - - if not answers: - return input - - else: - if input in answers: - return input - else: - print _("Invalid Input") - - -if __name__ == "__main__": - test = Setup(join(abspath(dirname(__file__)), ".."), None) - test.start() diff --git a/module/unescape.py b/module/unescape.py deleted file mode 100644 index d8999e077..000000000 --- a/module/unescape.py +++ /dev/null @@ -1,3 +0,0 @@ -from module.utils import html_unescape -#deprecated -unescape = html_unescape
\ No newline at end of file diff --git a/module/utils.py b/module/utils.py deleted file mode 100644 index 8748b7693..000000000 --- a/module/utils.py +++ /dev/null @@ -1,201 +0,0 @@ -# -*- coding: utf-8 -*- - -""" Store all usefull functions here """ - -import os -import sys -import time -import re -from os.path import join -from string import maketrans -from htmlentitydefs import name2codepoint - -def chmod(*args): - try: - os.chmod(*args) - except: - pass - - -def decode(string): - """ decode string with utf if possible """ - try: - return string.decode("utf8", "replace") - except: - return string - - -def remove_chars(string, repl): - """ removes all chars in repl from string""" - if type(string) == str: - return string.translate(maketrans("", ""), repl) - elif type(string) == unicode: - return string.translate(dict([(ord(s), None) for s in repl])) - - -def save_path(name): - #remove some chars - if os.name == 'nt': - return remove_chars(name, '/\\?%*:|"<>') - else: - return remove_chars(name, '/\\"') - - -def save_join(*args): - """ joins a path, encoding aware """ - return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args])) - - -# File System Encoding functions: -# Use fs_encode before accesing files on disk, it will encode the string properly - -if sys.getfilesystemencoding().startswith('ANSI'): - def fs_encode(string): - try: - string = string.encode('utf-8') - finally: - return string - - fs_decode = decode #decode utf8 - -else: - fs_encode = fs_decode = lambda x: x # do nothing - -def get_console_encoding(enc): - if os.name == "nt": - if enc == "cp65001": # aka UTF-8 - print "WARNING: Windows codepage 65001 is not supported." - enc = "cp850" - else: - enc = "utf8" - - return enc - -def compare_time(start, end): - start = map(int, start) - end = map(int, end) - - if start == end: return True - - now = list(time.localtime()[3:5]) - if start < now < end: return True - elif start > end and (now > start or now < end): return True - elif start < now > end < start: return True - else: return False - - -def formatSize(size): - """formats size of bytes""" - size = int(size) - steps = 0 - sizes = ["B", "KiB", "MiB", "GiB", "TiB"] - while size > 1000: - size /= 1024.0 - steps += 1 - return "%.2f %s" % (size, sizes[steps]) - - -def formatSpeed(speed): - return formatSize(speed) + "/s" - - -def freeSpace(folder): - if os.name == "nt": - import ctypes - - free_bytes = ctypes.c_ulonglong(0) - ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes)) - return free_bytes.value - else: - from os import statvfs - - s = statvfs(folder) - return s.f_bsize * s.f_bavail - - -def uniqify(seq, idfun=None): -# order preserving - if idfun is None: - def idfun(x): return x - seen = {} - result = [] - for item in seq: - marker = idfun(item) - # in old Python versions: - # if seen.has_key(marker) - # but in new ones: - if marker in seen: continue - seen[marker] = 1 - result.append(item) - return result - - -def parseFileSize(string, unit=None): #returns bytes - if not unit: - m = re.match(r"(\d*[\.,]?\d+)(.*)", string.strip().lower()) - if m: - traffic = float(m.group(1).replace(",", ".")) - unit = m.group(2) - else: - return 0 - else: - if isinstance(string, basestring): - traffic = float(string.replace(",", ".")) - else: - traffic = string - - #ignore case - unit = unit.lower().strip() - - if unit in ("gb", "gig", "gbyte", "gigabyte", "gib", "g"): - traffic *= 1 << 30 - elif unit in ("mb", "mbyte", "megabyte", "mib", "m"): - traffic *= 1 << 20 - elif unit in ("kb", "kib", "kilobyte", "kbyte", "k"): - traffic *= 1 << 10 - - return traffic - - -def lock(func): - def new(*args): - #print "Handler: %s args: %s" % (func,args[1:]) - args[0].lock.acquire() - try: - return func(*args) - finally: - args[0].lock.release() - - return new - - -def fixup(m): - text = m.group(0) - if text[:2] == "&#": - # character reference - try: - if text[:3] == "&#x": - return unichr(int(text[3:-1], 16)) - else: - return unichr(int(text[2:-1])) - except ValueError: - pass - else: - # named entity - try: - name = text[1:-1] - text = unichr(name2codepoint[name]) - except KeyError: - pass - - return text # leave as is - - -def html_unescape(text): - """Removes HTML or XML character references and entities from a text string""" - return re.sub("&#?\w+;", fixup, text) - -if __name__ == "__main__": - print freeSpace(".") - - print remove_chars("ab'cdgdsf''ds'", "'ghd") diff --git a/module/web/ServerThread.py b/module/web/ServerThread.py deleted file mode 100644 index 84667e5f6..000000000 --- a/module/web/ServerThread.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -from __future__ import with_statement -from os.path import exists - -import os -import threading -import logging - -core = None -setup = None -log = logging.getLogger("log") - -class WebServer(threading.Thread): - def __init__(self, pycore): - global core - threading.Thread.__init__(self) - self.core = pycore - core = pycore - self.running = True - self.server = pycore.config['webinterface']['server'] - self.https = pycore.config['webinterface']['https'] - self.cert = pycore.config["ssl"]["cert"] - self.key = pycore.config["ssl"]["key"] - self.host = pycore.config['webinterface']['host'] - self.port = pycore.config['webinterface']['port'] - - self.setDaemon(True) - - def run(self): - import webinterface - global webinterface - - if self.https: - if not exists(self.cert) or not exists(self.key): - log.warning(_("SSL certificates not found.")) - self.https = False - - if self.server in ("lighttpd", "nginx"): - log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server) - log.warning(_("You can use the threaded server which offers good performance and ssl,")) - log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server) - log.warning(_("sample configs are located in the module/web/servers directory")) - self.server = "builtin" - - if self.server == "fastcgi": - try: - import flup - except: - log.warning(_("Can't use %(server)s, python-flup is not installed!") % { - "server": self.server}) - self.server = "builtin" - elif self.server == "lightweight": - try: - import bjoern - except Exception, e: - log.error(_("Error importing lightweight server: %s") % e) - log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern")) - log.warning(_("Copy the boern.so to module/lib folder or use setup.py install")) - log.warning(_("Of course you need to be familiar with linux and know how to compile software")) - self.server = "builtin" - - if os.name == "nt": - self.core.log.info(_("Server set to threaded, due to known performance problems on windows.")) - self.core.config['webinterface']['server'] = "threaded" - self.server = "threaded" - - - if self.server == "fastcgi": - self.start_fcgi() - elif self.server == "threaded": - self.start_threaded() - elif self.server == "lightweight": - self.start_lightweight() - else: - self.start_builtin() - - def start_builtin(self): - - if self.https: - log.warning(_("This server offers no SSL, please consider using threaded instead")) - - self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) - webinterface.run_simple(host=self.host, port=self.port) - - def start_threaded(self): - if self.https: - self.core.log.info(_("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) - else: - self.cert = "" - self.key = "" - self.core.log.info(_("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) - - webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key) - - def start_fcgi(self): - - self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) - webinterface.run_fcgi(host=self.host, port=self.port) - - - def start_lightweight(self): - if self.https: - log.warning(_("This server offers no SSL, please consider using threaded instead")) - - self.core.log.info(_("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port}) - webinterface.run_lightweight(host=self.host, port=self.port) - - def quit(self): - self.running = False diff --git a/module/web/api_app.py b/module/web/api_app.py deleted file mode 100644 index 1629c1677..000000000 --- a/module/web/api_app.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from urllib import unquote -from itertools import chain -from traceback import format_exc, print_exc - -from bottle import route, request, response, HTTPError - -from utils import toDict, set_session -from webinterface import PYLOAD - -from module.common.json_layer import json -from module.lib.SafeEval import const_eval as literal_eval -from module.Api import BaseObject - -# json encoder that accepts TBase objects -class TBaseEncoder(json.JSONEncoder): - - def default(self, o): - if isinstance(o, BaseObject): - return toDict(o) - return json.JSONEncoder.default(self, o) - - -# accepting positional arguments, as well as kwargs via post and get - -@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#") -@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#", method="POST") -def call_api(func, args=""): - response.headers.replace("Content-type", "application/json") - response.headers.append("Cache-Control", "no-cache, must-revalidate") - - s = request.environ.get('beaker.session') - if 'session' in request.POST: - s = s.get_by_id(request.POST['session']) - - if not s or not s.get("authenticated", False): - return HTTPError(403, json.dumps("Forbidden")) - - if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}): - return HTTPError(401, json.dumps("Unauthorized")) - - args = args.split("/")[1:] - kwargs = {} - - for x, y in chain(request.GET.iteritems(), request.POST.iteritems()): - if x == "session": continue - kwargs[x] = unquote(y) - - try: - return callApi(func, *args, **kwargs) - except Exception, e: - print_exc() - return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()})) - - -def callApi(func, *args, **kwargs): - if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"): - print "Invalid API call", func - return HTTPError(404, json.dumps("Not Found")) - - result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args], - **dict([(x, literal_eval(y)) for x, y in kwargs.iteritems()])) - - # null is invalid json response - if result is None: result = True - - return json.dumps(result, cls=TBaseEncoder) - - -#post -> username, password -@route("/api/login", method="POST") -def login(): - response.headers.replace("Content-type", "application/json") - response.headers.append("Cache-Control", "no-cache, must-revalidate") - - user = request.forms.get("username") - password = request.forms.get("password") - - info = PYLOAD.checkAuth(user, password) - - if not info: - return json.dumps(False) - - s = set_session(request, info) - - # get the session id by dirty way, documentations seems wrong - try: - sid = s._headers["cookie_out"].split("=")[1].split(";")[0] - return json.dumps(sid) - except: - return json.dumps(True) - - -@route("/api/logout") -def logout(): - response.headers.replace("Content-type", "application/json") - response.headers.append("Cache-Control", "no-cache, must-revalidate") - - s = request.environ.get('beaker.session') - s.delete() diff --git a/module/web/cnl_app.py b/module/web/cnl_app.py deleted file mode 100644 index d8f7c1180..000000000 --- a/module/web/cnl_app.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from os.path import join -import re -from urllib import unquote -from base64 import standard_b64decode -from binascii import unhexlify - -from bottle import route, request, HTTPError -from webinterface import PYLOAD, DL_ROOT, JS - -try: - from Crypto.Cipher import AES -except: - pass - - -def local_check(function): - def _view(*args, **kwargs): - if request.environ.get('REMOTE_ADDR', "0") in ('127.0.0.1', 'localhost') \ - or request.environ.get('HTTP_HOST','0') == '127.0.0.1:9666': - return function(*args, **kwargs) - else: - return HTTPError(403, "Forbidden") - - return _view - - -@route("/flash") -@route("/flash/:id") -@route("/flash", method="POST") -@local_check -def flash(id="0"): - return "JDownloader\r\n" - -@route("/flash/add", method="POST") -@local_check -def add(request): - package = request.POST.get('referer', None) - urls = filter(lambda x: x != "", request.POST['urls'].split("\n")) - - if package: - PYLOAD.addPackage(package, urls, 0) - else: - PYLOAD.generateAndAddPackages(urls, 0) - - return "" - -@route("/flash/addcrypted", method="POST") -@local_check -def addcrypted(): - - package = request.forms.get('referer', 'ClickAndLoad Package') - dlc = request.forms['crypted'].replace(" ", "+") - - dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc") - dlc_file = open(dlc_path, "wb") - dlc_file.write(dlc) - dlc_file.close() - - try: - PYLOAD.addPackage(package, [dlc_path], 0) - except: - return HTTPError() - else: - return "success\r\n" - -@route("/flash/addcrypted2", method="POST") -@local_check -def addcrypted2(): - - package = request.forms.get("source", None) - crypted = request.forms["crypted"] - jk = request.forms["jk"] - - crypted = standard_b64decode(unquote(crypted.replace(" ", "+"))) - if JS: - jk = "%s f()" % jk - jk = JS.eval(jk) - - else: - try: - jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1] - except: - ## Test for some known js functions to decode - if jk.find("dec") > -1 and jk.find("org") > -1: - org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1] - jk = list(org) - jk.reverse() - jk = "".join(jk) - else: - print "Could not decrypt key, please install py-spidermonkey or ossp-js" - - try: - Key = unhexlify(jk) - except: - print "Could not decrypt key, please install py-spidermonkey or ossp-js" - return "failed" - - IV = Key - - obj = AES.new(Key, AES.MODE_CBC, IV) - result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n") - - result = filter(lambda x: x != "", result) - - try: - if package: - PYLOAD.addPackage(package, result, 0) - else: - PYLOAD.generateAndAddPackages(result, 0) - except: - return "failed can't add" - else: - return "success\r\n" - -@route("/flashgot_pyload") -@route("/flashgot_pyload", method="POST") -@route("/flashgot") -@route("/flashgot", method="POST") -@local_check -def flashgot(): - if request.environ['HTTP_REFERER'] != "http://localhost:9666/flashgot" and request.environ['HTTP_REFERER'] != "http://127.0.0.1:9666/flashgot": - return HTTPError() - - autostart = int(request.forms.get('autostart', 0)) - package = request.forms.get('package', None) - urls = filter(lambda x: x != "", request.forms['urls'].split("\n")) - folder = request.forms.get('dir', None) - - if package: - PYLOAD.addPackage(package, urls, autostart) - else: - PYLOAD.generateAndAddPackages(urls, autostart) - - return "" - -@route("/crossdomain.xml") -@local_check -def crossdomain(): - rep = "<?xml version=\"1.0\"?>\n" - rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" - rep += "<cross-domain-policy>\n" - rep += "<allow-access-from domain=\"*\" />\n" - rep += "</cross-domain-policy>" - return rep - - -@route("/flash/checkSupportForUrl") -@local_check -def checksupport(): - - url = request.GET.get("url") - res = PYLOAD.checkURLs([url]) - supported = (not res[0][1] is None) - - return str(supported).lower() - -@route("/jdcheck.js") -@local_check -def jdcheck(): - rep = "jdownloader=true;\n" - rep += "var version='9.581;'" - return rep diff --git a/module/web/filters.py b/module/web/filters.py deleted file mode 100644 index 13b8345fc..000000000 --- a/module/web/filters.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import os -from os.path import abspath, commonprefix, join - -quotechar = "::/" - -try: - from os.path import relpath -except: - from posixpath import curdir, sep, pardir - def relpath(path, start=curdir): - """Return a relative version of a path""" - if not path: - raise ValueError("no path specified") - start_list = abspath(start).split(sep) - path_list = abspath(path).split(sep) - # Work out how much of the filepath is shared by start and path. - i = len(commonprefix([start_list, path_list])) - rel_list = [pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return curdir - return join(*rel_list) - - -def quotepath(path): - try: - return path.replace("../", quotechar) - except AttributeError: - return path - except: - return "" - -def unquotepath(path): - try: - return path.replace(quotechar, "../") - except AttributeError: - return path - except: - return "" - -def path_make_absolute(path): - p = os.path.abspath(path) - if p[-1] == os.path.sep: - return p - else: - return p + os.path.sep - -def path_make_relative(path): - p = relpath(path) - if p[-1] == os.path.sep: - return p - else: - return p + os.path.sep - -def truncate(value, n): - if (n - len(value)) < 3: - return value[:n]+"..." - return value - -def date(date, format): - return date
\ No newline at end of file diff --git a/module/web/json_app.py b/module/web/json_app.py deleted file mode 100644 index f3626405c..000000000 --- a/module/web/json_app.py +++ /dev/null @@ -1,312 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from os.path import join -from traceback import print_exc -from shutil import copyfileobj - -from bottle import route, request, HTTPError - -from webinterface import PYLOAD - -from utils import login_required, render_to_response, toDict - -from module.utils import decode, formatSize - - -def format_time(seconds): - seconds = int(seconds) - - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) - - -def get_sort_key(item): - return item["order"] - - -@route("/json/status") -@route("/json/status", method="POST") -@login_required('LIST') -def status(): - try: - status = toDict(PYLOAD.statusServer()) - status['captcha'] = PYLOAD.isCaptchaWaiting() - return status - except: - return HTTPError() - - -@route("/json/links") -@route("/json/links", method="POST") -@login_required('LIST') -def links(): - try: - links = [toDict(x) for x in PYLOAD.statusDownloads()] - ids = [] - for link in links: - ids.append(link['fid']) - - if link['status'] == 12: - link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed'])) - elif link['status'] == 5: - link['percent'] = 0 - link['size'] = 0 - link['bleft'] = 0 - link['info'] = _("waiting %s") % link['format_wait'] - else: - link['info'] = "" - - data = {'links': links, 'ids': ids} - return data - except Exception, e: - print_exc() - return HTTPError() - - -@route("/json/packages") -@login_required('LIST') -def packages(): - print "/json/packages" - try: - data = PYLOAD.getQueue() - - for package in data: - package['links'] = [] - for file in PYLOAD.get_package_files(package['id']): - package['links'].append(PYLOAD.get_file_info(file)) - - return data - - except: - return HTTPError() - - -@route("/json/package/<id:int>") -@login_required('LIST') -def package(id): - try: - data = toDict(PYLOAD.getPackageData(id)) - data["links"] = [toDict(x) for x in data["links"]] - - for pyfile in data["links"]: - if pyfile["status"] == 0: - pyfile["icon"] = "status_finished.png" - elif pyfile["status"] in (2, 3): - pyfile["icon"] = "status_queue.png" - elif pyfile["status"] in (9, 1): - pyfile["icon"] = "status_offline.png" - elif pyfile["status"] == 5: - pyfile["icon"] = "status_waiting.png" - elif pyfile["status"] == 8: - pyfile["icon"] = "status_failed.png" - elif pyfile["status"] == 4: - pyfile["icon"] = "arrow_right.png" - elif pyfile["status"] in (11, 13): - pyfile["icon"] = "status_proc.png" - else: - pyfile["icon"] = "status_downloading.png" - - tmp = data["links"] - tmp.sort(key=get_sort_key) - data["links"] = tmp - return data - - except: - print_exc() - return HTTPError() - - -@route("/json/package_order/:ids") -@login_required('ADD') -def package_order(ids): - try: - pid, pos = ids.split("|") - PYLOAD.orderPackage(int(pid), int(pos)) - return {"response": "success"} - except: - return HTTPError() - - -@route("/json/abort_link/<id:int>") -@login_required('DELETE') -def abort_link(id): - try: - PYLOAD.stopDownloads([id]) - return {"response": "success"} - except: - return HTTPError() - - -@route("/json/link_order/:ids") -@login_required('ADD') -def link_order(ids): - try: - pid, pos = ids.split("|") - PYLOAD.orderFile(int(pid), int(pos)) - return {"response": "success"} - except: - return HTTPError() - - -@route("/json/add_package") -@route("/json/add_package", method="POST") -@login_required('ADD') -def add_package(): - name = request.forms.get("add_name", "New Package").strip() - queue = int(request.forms['add_dest']) - links = decode(request.forms['add_links']) - links = links.split("\n") - pw = request.forms.get("add_password", "").strip("\n\r") - - try: - f = request.files['add_file'] - - if not name or name == "New Package": - name = f.name - - fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename) - destination = open(fpath, 'wb') - copyfileobj(f.file, destination) - destination.close() - links.insert(0, fpath) - except: - pass - - name = name.decode("utf8", "ignore") - - links = map(lambda x: x.strip(), links) - links = filter(lambda x: x != "", links) - - pack = PYLOAD.addPackage(name, links, queue) - if pw: - pw = pw.decode("utf8", "ignore") - data = {"password": pw} - PYLOAD.setPackageData(pack, data) - - -@route("/json/move_package/<dest:int>/<id:int>") -@login_required('MODIFY') -def move_package(dest, id): - try: - PYLOAD.movePackage(dest, id) - return {"response": "success"} - except: - return HTTPError() - - -@route("/json/edit_package", method="POST") -@login_required('MODIFY') -def edit_package(): - try: - id = int(request.forms.get("pack_id")) - data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"), - "folder": request.forms.get("pack_folder").decode("utf8", "ignore"), - "password": request.forms.get("pack_pws").decode("utf8", "ignore")} - - PYLOAD.setPackageData(id, data) - return {"response": "success"} - - except: - return HTTPError() - - -@route("/json/set_captcha") -@route("/json/set_captcha", method="POST") -@login_required('ADD') -def set_captcha(): - if request.environ.get('REQUEST_METHOD', "GET") == "POST": - try: - PYLOAD.setCaptchaResult(request.forms["cap_id"], request.forms["cap_result"]) - except: - pass - - task = PYLOAD.getCaptchaTask() - - if task.tid >= 0: - src = "data:image/%s;base64,%s" % (task.type, task.data) - - return {'captcha': True, 'id': task.tid, 'src': src, 'result_type' : task.resultType} - else: - return {'captcha': False} - - -@route("/json/load_config/:category/:section") -@login_required("SETTINGS") -def load_config(category, section): - conf = None - if category == "general": - conf = PYLOAD.getConfigDict() - elif category == "plugin": - conf = PYLOAD.getPluginConfigDict() - - for key, option in conf[section].iteritems(): - if key in ("desc","outline"): continue - - if ";" in option["type"]: - option["list"] = option["type"].split(";") - - option["value"] = decode(option["value"]) - - return render_to_response("settings_item.html", {"skey": section, "section": conf[section]}) - - -@route("/json/save_config/:category", method="POST") -@login_required("SETTINGS") -def save_config(category): - for key, value in request.POST.iteritems(): - try: - section, option = key.split("|") - except: - continue - - if category == "general": category = "core" - - PYLOAD.setConfigValue(section, option, decode(value), category) - - -@route("/json/add_account", method="POST") -@login_required("ACCOUNTS") -def add_account(): - login = request.POST["account_login"] - password = request.POST["account_password"] - type = request.POST["account_type"] - - PYLOAD.updateAccount(type, login, password) - - -@route("/json/update_accounts", method="POST") -@login_required("ACCOUNTS") -def update_accounts(): - deleted = [] #dont update deleted accs or they will be created again - - for name, value in request.POST.iteritems(): - value = value.strip() - if not value: continue - - tmp, user = name.split(";") - plugin, action = tmp.split("|") - - if (plugin, user) in deleted: continue - - if action == "password": - PYLOAD.updateAccount(plugin, user, value) - elif action == "time" and "-" in value: - PYLOAD.updateAccount(plugin, user, options={"time": [value]}) - elif action == "limitdl" and value.isdigit(): - PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]}) - elif action == "delete": - deleted.append((plugin,user)) - PYLOAD.removeAccount(plugin, user) - -@route("/json/change_password", method="POST") -def change_password(): - - user = request.POST["user_login"] - oldpw = request.POST["login_current_password"] - newpw = request.POST["login_new_password"] - - if not PYLOAD.changePassword(user, oldpw, newpw): - print "Wrong password" - return HTTPError() diff --git a/module/web/media/default/css/MooDialog.css b/module/web/media/default/css/MooDialog.css deleted file mode 100644 index 48c9166ad..000000000 --- a/module/web/media/default/css/MooDialog.css +++ /dev/null @@ -1,92 +0,0 @@ -/* Created by Arian Stolwijk <http://www.aryweb.nl> */ - -.MooDialog { -/* position: fixed;*/ - margin: 0 auto 0 -350px; - width:600px; - padding:14px; - left:50%; - top: 100px; - - position: absolute; - left: 50%; - z-index: 50000; - - background: #eef5f8; - color: black; - border-radius: 7px; - -moz-border-radius: 7px; - -webkit-border-radius: 7px; - border-radius: 7px; - -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); - -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); - box-shadow: 1px 1px 5px rgba(0,0,0,0.8); -} - -.MooDialogTitle { - padding-top: 30px; -} - -.MooDialog .title { - position: absolute; - top: 0; - left: 0; - right: 0; - padding: 3px 20px; - background: #b7c4dc; - border-bottom: 1px solid #a1aec5; - font-weight: bold; - text-shadow: 1px 1px 0 #fff; - color: black; - border-radius: 7px; - -moz-border-radius: 7px; - -webkit-border-radius: 7px; -} - -.MooDialog .close { - background: url(/media/img/dialog-close.png) no-repeat; - width: 16px; - height: 16px; - display: block; - cursor: pointer; - top: -5px; - left: -5px; - position: absolute; -} - -.MooDialog .buttons { - text-align: right; - margin: 0; - padding: 0; - border: 0; - background: none; -} - -.MooDialog .iframe { - width: 100%; - height: 100%; -} - -.MooDialog .textInput { - width: 200px; - float: left; -} - -.MooDialog .MooDialogAlert, -.MooDialog .MooDialogConfirm, -.MooDialog .MooDialogPrompt, -.MooDialog .MooDialogError { - background: url(/media/img/dialog-warning.png) no-repeat; - padding-left: 40px; - min-height: 40px; -} - -.MooDialog .MooDialogConfirm, -.MooDialog .MooDialogPromt { - background: url(/media/img/dialog-question.png) no-repeat; -} - -.MooDialog .MooDialogError { - background: url(/media/img/dialog-error.png) no-repeat; -} - diff --git a/module/web/media/default/css/default.css b/module/web/media/default/css/default.css deleted file mode 100644 index 116f9725a..000000000 --- a/module/web/media/default/css/default.css +++ /dev/null @@ -1,908 +0,0 @@ -.hidden { - display:none; -} -.leftalign { - text-align:left; -} -.centeralign { - text-align:center; -} -.rightalign { - text-align:right; -} - - -.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited { - background-color:#000080; - color:#fff !important; - text-decoration:none; - padding:0 0.2em; - margin:0.1em 0.2em; - border:none !important; -} -.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited { - background-color:#808080; - color:#fff !important; - text-decoration:none; - padding:0 0.2em; - margin:0.1em 0.2em; - border:none !important; -} - -.dokuwiki div.plugin_translation ul li a:hover img { - opacity:1.0; - height:15px; -} - -body { - margin:0; - padding:0; - background-color:white; - color:black; - font-size:12px; - font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif; - font-family:sans-serif; - font-size:99, 96%; - font-size-adjust:none; - font-style:normal; - font-variant:normal; - font-weight:normal; - line-height:normal; -} -hr { - border-width:0; - border-bottom:1px #aaa dotted; -} -img { - border:none; -} -form { - margin:0px; - padding:0px; - border:none; - display:inline; - background:transparent; -} -ul li { - margin:5px; -} -textarea { - font-family:monospace; -} -table { - margin:0.5em 0; - border-collapse:collapse; -} -td { - padding:0.25em; - border:1pt solid #ADB9CC; -} -a { - color:#3465a4; - text-decoration:none; -} -a:hover { - text-decoration:underline; -} - -option { - border:0 none #fff; -} -strong.highlight { - background-color:#fc9; - padding:1pt; -} -#pagebottom { - clear:both; -} -hr { - height:1px; - color:#c0c0c0; - background-color:#c0c0c0; - border:none; - margin:.2em 0 .2em 0; -} - -.invisible { - margin:0px; - border:0px; - padding:0px; - height:0px; - visibility:hidden; -} -.left { - float:left !important; -} -.right { - float:right !important; -} -.center { - text-align:center; -} -div#body-wrapper { - padding:40px 40px 10px 40px; - font-size:127%; -} -div#content { - margin-top:-20px; - padding:0; - font-size:14px; - color:black; - line-height:1.5em; -} -h1, h2, h3, h4, h5, h6 { - background:transparent none repeat scroll 0 0; - border-bottom:1px solid #aaa; - color:black; - font-weight:normal; - margin:0; - padding:0; - padding-bottom:0.17em; - padding-top:0.5em; -} -h1 { - font-size:188%; - line-height:1.2em; - margin-bottom:0.1em; - padding-bottom:0; -} -h2 { - font-size:150%; -} -h3, h4, h5, h6 { - border-bottom:none; - font-weight:bold; -} -h3 { - font-size:132%; -} -h4 { - font-size:116%; -} -h5 { - font-size:100%; -} -h6 { - font-size:80%; -} -ul#page-actions, ul#page-actions-more { - float:right; - margin:10px 10px 0 10px; - padding:6px; - color:black; - background-color:#ececec; - list-style-type:none; - white-space: nowrap; - border-radius:5px; - -moz-border-radius:5px; -} -ul#user-actions { - padding:5px; - margin:0; - display:inline; - color:black; - background-color:#ececec; - list-style-type:none; - -moz-border-radius:3px; - border-radius:3px; -} -ul#page-actions li, ul#user-actions li, ul#page-actions-more li { - display:inline; -} -ul#page-actions a, ul#user-actions a, ul#page-actions-more a { - text-decoration:none; - color:black; - display:inline; - margin:0 3px; - padding:2px 0px 2px 18px; -} -ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus { - /*text-decoration:underline;*/ -} -/***************************/ -ul#page-actions2 { - float:left; - margin:10px 10px 0 10px; - padding:6px; - color:black; - background-color:#ececec; - list-style-type:none; - border-radius:5px; - -moz-border-radius:5px; -} -ul#user-actions2 { - padding:5px; - margin:0; - display:inline; - color:black; - background-color:#ececec; - list-style-type:none; - border-radius:3px; - -moz-border-radius:3px; -} -ul#page-actions2 li, ul#user-actions2 li { - display:inline; -} -ul#page-actions2 a, ul#user-actions2 a { - text-decoration:none; - color:black; - display:inline; - margin:0 3px; - padding:2px 0px 2px 18px; -} -ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, -ul#page-actions-more a:hover, ul#page-actions-more a:focus{ - color: #4e7bb4; -} -/****************************/ -.hidden { - display:none; -} - -a.action.index { - background:transparent url(/media/default/img/wiki-tools-index.png) 0px 1px no-repeat; -} -a.action.recent { - background:transparent url(/media/default/img/wiki-tools-recent.png) 0px 1px no-repeat; -} -a.logout { - background:transparent url(/media/default/img/user-actions-logout.png) 0px 1px no-repeat; -} - -a.info { - background:transparent url(/media/default/img/user-info.png) 0px 1px no-repeat; -} - -a.admin { - background:transparent url(/media/default/img/user-actions-admin.png) 0px 1px no-repeat; -} -a.profile { - background:transparent url(/media/default/img/user-actions-profile.png) 0px 1px no-repeat; -} -a.create, a.edit { - background:transparent url(/media/default/img/page-tools-edit.png) 0px 1px no-repeat; -} -a.source, a.show { - background:transparent url(/media/default/img/page-tools-source.png) 0px 1px no-repeat; -} -a.revisions { - background:transparent url(/media/default/img/page-tools-revisions.png) 0px 1px no-repeat; -} -a.subscribe, a.unsubscribe { - background:transparent url(/media/default/img/page-tools-subscribe.png) 0px 1px no-repeat; -} -a.backlink { - background:transparent url(/media/default/img/page-tools-backlinks.png) 0px 1px no-repeat; -} -a.play { - background:transparent url(/media/default/img/control_play.png) 0px 1px no-repeat; -} -.time { - background:transparent url(/media/default/img/status_None.png) 0px 1px no-repeat; - padding: 2px 0px 2px 18px; - margin: 0px 3px; -} -.reconnect { - background:transparent url(/media/default/img/reconnect.png) 0px 1px no-repeat; - padding: 2px 0px 2px 18px; - margin: 0px 3px; -} -a.play:hover { - background:transparent url(/media/default/img/control_play_blue.png) 0px 1px no-repeat; -} -a.cancel { - background:transparent url(/media/default/img/control_cancel.png) 0px 1px no-repeat; -} -a.cancel:hover { - background:transparent url(/media/default/img/control_cancel_blue.png) 0px 1px no-repeat; -} -a.pause { - background:transparent url(/media/default/img/control_pause.png) 0px 1px no-repeat; -} -a.pause:hover { - background:transparent url(/media/default/img/control_pause_blue.png) 0px 1px no-repeat; - font-weight: bold; -} -a.stop { - background:transparent url(/media/default/img/control_stop.png) 0px 1px no-repeat; -} -a.stop:hover { - background:transparent url(/media/default/img/control_stop_blue.png) 0px 1px no-repeat; -} -a.add { - background:transparent url(/media/default/img/control_add.png) 0px 1px no-repeat; -} -a.add:hover { - background:transparent url(/media/default/img/control_add_blue.png) 0px 1px no-repeat; -} -a.cog { - background:transparent url(/media/default/img/cog.png) 0px 1px no-repeat; -} -#head-panel { - background:#525252 url(/media/default/img/head_bg1.png) bottom left repeat-x; -} -#head-panel h1 { - display:none; - margin:0; - text-decoration:none; - padding-top:0.8em; - padding-left:3.3em; - font-size:2.6em; - color:#eeeeec; -} -#head-panel #head-logo { - float:left; - margin:5px 0 -15px 5px; - padding:0; - overflow:visible; -} -#head-menu { - background:transparent url(/media/default/img/tabs-border-bottom.png) 0 100% repeat-x; - width:100%; - float:left; - margin:0; - padding:0; - padding-top:0.8em; -} -#head-menu ul { - list-style:none; - margin:0 1em 0 2em; -} -#head-menu ul li { - float:left; - margin:0; - margin-left:0.3em; - font-size:14px; - margin-bottom:4px; -} -#head-menu ul li.selected, #head-menu ul li:hover { - margin-bottom:0px; -} -#head-menu ul li a img { - height:22px; - width:22px; - vertical-align:middle; -} -#head-menu ul li a, #head-menu ul li a:link { - float:left; - text-decoration:none; - color:#555; - background:#eaeaea url(/media/default/img/tab-background.png) 0 100% repeat-x; - padding:3px 7px 3px 7px; - border:2px solid #ccc; - border-bottom:0px solid transparent; - padding-bottom:3px; - -moz-border-radius:5px; - border-radius:5px; -} -#head-menu ul li a:hover, #head-menu ul li a:focus { - color:#111; - padding-bottom:7px; - border-bottom:0px none transparent; - outline:none; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - -moz-border-radius-bottomright:0px; - -moz-border-radius-bottomleft:0px; -} -#head-menu ul li a:focus { - margin-bottom:-4px; -} -#head-menu ul li.selected a { - color:#3566A5; - background:#fff; - padding-bottom:7px; - border-bottom:0px none transparent; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - -moz-border-radius-bottomright:0px; - -moz-border-radius-bottomleft:0px; -} -#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus { - color:#111; -} -div#head-search-and-login { - float:right; - margin:0 1em 0 0; - background-color:#222; - padding:7px 7px 5px 5px; - color:white; - white-space: nowrap; - border-bottom-left-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-bottomright:6px; - -moz-border-radius-bottomleft:6px; -} -div#head-search-and-login form { - display:inline; - padding:0 3px; -} -div#head-search-and-login form input { - border:2px solid #888; - background:#eee; - font-size:14px; - padding:2px; - border-radius:3px; - -moz-border-radius:3px; -} -div#head-search-and-login form input:focus { - background:#fff; -} -#head-search { - font-size:14px; -} -#head-username, #head-password { - width:80px; - font-size:14px; -} -#pageinfo { - clear:both; - color:#888; - padding:0.6em 0; - margin:0; -} -#foot { - font-style:normal; - color:#888; - text-align:center; -} -#foot a { - color:#aaf; -} -#foot img { - vertical-align:middle; -} -div.toc { - border:1px dotted #888; - background:#f0f0f0; - margin:1em 0 1em 1em; - float:right; - font-size:95%; -} -div.toc .tocheader { - font-weight:bold; - margin:0.5em 1em; -} -div.toc ol { - margin:1em 0.5em 1em 1em; - padding:0; -} -div.toc ol li { - margin:0; - padding:0; - margin-left:1em; -} -div.toc ol ol { - margin:0.5em 0.5em 0.5em 1em; - padding:0; -} -div.recentchanges table { - clear:both; -} -div#editor-help { - font-size:90%; - border:1px dotted #888; - padding:0ex 1ex 1ex 1ex; - background:#f7f6f2; -} -div#preview { - margin-top:1em; -} -label.block { - display:block; - text-align:right; - font-weight:bold; -} -label.simple { - display:block; - text-align:left; - font-weight:normal; -} -label.block input.edit { - width:50%; -} -/*fieldset { - width:300px; - text-align:center; - padding:0.5em; - margin:auto; -} -*/ -div.editor { - margin:0 0 0 0; -} -table { - margin:0.5em 0; - border-collapse:collapse; -} -td { - padding:0.25em; - border:1pt solid #ADB9CC; -} -td p { - margin:0; - padding:0; -} -.u { - text-decoration:underline; -} -.footnotes ul { - padding:0 2em; - margin:0 0 1em; -} -.footnotes li { - list-style:none; -} -.userpref table, .userpref td { - border:none; -} -#message { - clear:both; - padding:5px 10px; - background-color:#eee; - border-bottom:2px solid #ccc; -} -#message p { - margin:5px 0; - padding:0; - font-weight:bold; -} -#message div.buttons { - font-weight:normal; -} -.diff { - width:99%; -} -.diff-title { - background-color:#C0C0C0; -} -.searchresult dd span { - font-weight:bold; -} -.boxtext { - font-family:tahoma, arial, sans-serif; - font-size:11px; - color:#000; - float:none; - padding:3px 0 0 10px; -} -.statusbutton { - width:32px; - height:32px; - float:left; - margin-left:-32px; - margin-right:5px; - opacity:0; - cursor:pointer -} -.dlsize { - float:left; - padding-right: 8px; -} -.dlspeed { - float:left; - padding-right: 8px; -} -.package { - margin-bottom: 10px; -} -.packagename { - font-weight: bold; -} - -.child { - margin-left: 20px; -} -.child_status { - margin-right: 10px; -} -.child_secrow { - font-size: 10px; -} - -.header, .header th { - text-align: left; - font-weight: normal; - background-color:#ececec; - -moz-border-radius:5px; - border-radius:5px; -} -.progress_bar { - background: #0C0; - height: 5px; - -} - -.queue { - border: none -} - -.queue tr td { - border: none -} - -.header, .header th{ - text-align: left; - font-weight: normal; -} - - -.clearer -{ - clear: both; - height: 1px; -} - -.left -{ - float: left; -} - -.right -{ - float: right; -} - - -.setfield -{ - display: table-cell; -} - -ul.tabs li a -{ - padding: 5px 16px 4px 15px; - border: none; - font-weight: bold; - - border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - -} - - -#tabs span -{ - display: none; -} - -#tabs span.selected -{ - display: inline; -} - -#tabsback -{ - background-color: #525252; - margin: 2px 0 0; - padding: 6px 4px 1px 4px; - - border-top-right-radius: 30px; - border-top-left-radius: 3px; - -moz-border-radius-topright: 30px; - -moz-border-radius-topleft: 3px; -} -ul.tabs -{ - list-style-type: none; - margin:0; - padding: 0 40px 0 0; -} - -ul.tabs li -{ - display: inline; - margin-left: 8px; -} - - -ul.tabs li a -{ - color: #42454a; - background-color: #eaeaea; - border: 1px none #c9c3ba; - margin: 0; - text-decoration: none; - - outline: 0; - - padding: 5px 16px 4px 15px; - font-weight: bold; - - border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - -} - -ul.tabs li a.selected, ul.tabs li a:hover -{ - color: #000; - background-color: white; - - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; -} - -ul.tabs li a:hover -{ - background-color: #f1f4ee; -} - -ul.tabs li a.selected -{ - font-weight: bold; - background-color: #525252; - padding-bottom: 5px; - color: white; -} - - -#tabs-body { - position: relative; - overflow: hidden; -} - - -span.tabContent -{ - border: 2px solid #525252; - margin: 0; - padding: 0; - padding-bottom: 10px; -} - -#tabs-body > span { - display: none; -} - -#tabs-body > span.active { - display: block; -} - -.hide -{ - display: none; -} - -.settable -{ - margin: 20px; - border: none; -} -.settable td -{ - border: none; - margin: 0; - padding: 5px; -} - -.settable th{ - padding-bottom: 8px; -} - -.settable.wide td , .settable.wide th { - padding-left: 15px; - padding-right: 15px; -} - - -/*settings navbar*/ -ul.nav { - margin: -30px 0 0; - padding: 0; - list-style: none; - position: absolute; -} - - -ul.nav li { - position: relative; - float: left; - padding: 5px; -} - -ul.nav > li a { - background: white; - -moz-border-radius: 4px 4px 4px 4px; - border: 1px solid #C9C3BA; - border-bottom: medium none; - color: black; -} - -ul.nav ul { - position: absolute; - top: 26px; - left: 10px; - margin: 0; - padding: 0; - list-style: none; - border: 1px solid #AAA; - background: #f1f1f1; - -webkit-box-shadow: 1px 1px 5px #AAA; - -moz-box-shadow: 1px 1px 5px #AAA; - box-shadow: 1px 1px 5px #AAA; - cursor: pointer; -} - -ul.nav .open { - display: block; -} - -ul.nav .close { - display: none; -} - -ul.nav ul li { - float: none; - padding: 0; -} - -ul.nav ul li a { - width: 130px; - background: #f1f1f1; - padding: 3px; - display: block; - font-weight: normal; -} - -ul.nav ul li a:hover { - background: #CDCDCD; -} - -ul.nav ul ul { - left: 137px; - top: 0; -} - -.purr-wrapper{ - margin:10px; -} - -/*Purr alert styles*/ - -.purr-alert{ - margin-bottom:10px; - padding:10px; - background:#000; - font-size:13px; - font-weight:bold; - color:#FFF; - -moz-border-radius:5px; - -webkit-border-radius:5px; - /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/ - width:300px; -} -.purr-alert.error{ - color:#F55; - padding-left:30px; - background:url(/media/default/img/error.png) no-repeat #000 7px 10px; - width:280px; -} -.purr-alert.success{ - color:#5F5; - padding-left:30px; - background:url(/media/default/img/success.png) no-repeat #000 7px 10px; - width:280px; -} -.purr-alert.notice{ - color:#99F; - padding-left:30px; - background:url(/media/default/img//notice.png) no-repeat #000 7px 10px; - width:280px; -} - -table.system { - border: none; - margin-left: 10px; -} - -table.system td { - border: none -} - -table.system tr > td:first-child { - font-weight: bold; - padding-right: 10px; -}
\ No newline at end of file diff --git a/module/web/media/default/css/log.css b/module/web/media/default/css/log.css deleted file mode 100644 index 73786bfb4..000000000 --- a/module/web/media/default/css/log.css +++ /dev/null @@ -1,72 +0,0 @@ - -html, body, #content -{ - height: 100%; -} -#body-wrapper -{ - height: 70%; -} -.logdiv -{ - height: 90%; - width: 100%; - overflow: auto; - border: 2px solid #CCC; - outline: 1px solid #666; - background-color: #FFE; - margin-right: auto; - margin-left: auto; -} -.logform -{ - display: table; - margin: 0 auto 0 auto; - padding-top: 5px; -} -.logtable -{ - - margin: 0px; -} -.logtable td -{ - border: none; - white-space: nowrap; - - - font-family: monospace; - font-size: 16px; - margin: 0px; - padding: 0px 10px 0px 10px; - line-height: 110%; -} -td.logline -{ - background-color: #EEE; - text-align:right; - padding: 0px 5px 0px 5px; -} -td.loglevel -{ - text-align:right; -} -.logperpage -{ - float: right; - padding-bottom: 8px; -} -.logpaginator -{ - float: left; - padding-top: 5px; -} -.logpaginator a -{ - padding: 0px 8px 0px 8px; -} -.logwarn -{ - text-align: center; - color: red; -}
\ No newline at end of file diff --git a/module/web/media/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 12829868b..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 Binary files differdeleted file mode 100644 index 8acbc411b..000000000 --- a/module/web/media/default/img/add_folder.png +++ /dev/null diff --git a/module/web/media/default/img/ajax-loader.gif b/module/web/media/default/img/ajax-loader.gif Binary files differdeleted file mode 100644 index 2fd8e0737..000000000 --- a/module/web/media/default/img/ajax-loader.gif +++ /dev/null diff --git a/module/web/media/default/img/arrow_refresh.png b/module/web/media/default/img/arrow_refresh.png Binary files differdeleted file mode 100644 index 0de26566d..000000000 --- a/module/web/media/default/img/arrow_refresh.png +++ /dev/null diff --git a/module/web/media/default/img/arrow_right.png b/module/web/media/default/img/arrow_right.png Binary files differdeleted file mode 100644 index b1a181923..000000000 --- a/module/web/media/default/img/arrow_right.png +++ /dev/null diff --git a/module/web/media/default/img/big_button.gif b/module/web/media/default/img/big_button.gif Binary files differdeleted file mode 100644 index 7680490ea..000000000 --- a/module/web/media/default/img/big_button.gif +++ /dev/null diff --git a/module/web/media/default/img/big_button_over.gif b/module/web/media/default/img/big_button_over.gif Binary files differdeleted file mode 100644 index 2e3ee10d2..000000000 --- a/module/web/media/default/img/big_button_over.gif +++ /dev/null diff --git a/module/web/media/default/img/body.png b/module/web/media/default/img/body.png Binary files differdeleted file mode 100644 index 7ff1043e0..000000000 --- a/module/web/media/default/img/body.png +++ /dev/null diff --git a/module/web/media/default/img/button.png b/module/web/media/default/img/button.png Binary files differdeleted file mode 100644 index 890160614..000000000 --- a/module/web/media/default/img/button.png +++ /dev/null diff --git a/module/web/media/default/img/closebtn.gif b/module/web/media/default/img/closebtn.gif Binary files differdeleted file mode 100644 index 3e27e6030..000000000 --- a/module/web/media/default/img/closebtn.gif +++ /dev/null diff --git a/module/web/media/default/img/cog.png b/module/web/media/default/img/cog.png Binary files differdeleted file mode 100644 index 67de2c6cc..000000000 --- a/module/web/media/default/img/cog.png +++ /dev/null diff --git a/module/web/media/default/img/control_add.png b/module/web/media/default/img/control_add.png Binary files differdeleted file mode 100644 index d39886893..000000000 --- a/module/web/media/default/img/control_add.png +++ /dev/null diff --git a/module/web/media/default/img/control_add_blue.png b/module/web/media/default/img/control_add_blue.png Binary files differdeleted file mode 100644 index d11b7f41d..000000000 --- a/module/web/media/default/img/control_add_blue.png +++ /dev/null diff --git a/module/web/media/default/img/control_cancel.png b/module/web/media/default/img/control_cancel.png Binary files differdeleted file mode 100644 index 7b9bc3fba..000000000 --- a/module/web/media/default/img/control_cancel.png +++ /dev/null diff --git a/module/web/media/default/img/control_cancel_blue.png b/module/web/media/default/img/control_cancel_blue.png Binary files differdeleted file mode 100644 index 0c5c96ce3..000000000 --- a/module/web/media/default/img/control_cancel_blue.png +++ /dev/null diff --git a/module/web/media/default/img/control_pause.png b/module/web/media/default/img/control_pause.png Binary files differdeleted file mode 100644 index 2d9ce9c4e..000000000 --- a/module/web/media/default/img/control_pause.png +++ /dev/null diff --git a/module/web/media/default/img/control_pause_blue.png b/module/web/media/default/img/control_pause_blue.png Binary files differdeleted file mode 100644 index ec61099b0..000000000 --- a/module/web/media/default/img/control_pause_blue.png +++ /dev/null diff --git a/module/web/media/default/img/control_play.png b/module/web/media/default/img/control_play.png Binary files differdeleted file mode 100644 index 0846555d0..000000000 --- a/module/web/media/default/img/control_play.png +++ /dev/null diff --git a/module/web/media/default/img/control_play_blue.png b/module/web/media/default/img/control_play_blue.png Binary files differdeleted file mode 100644 index f8c8ec683..000000000 --- a/module/web/media/default/img/control_play_blue.png +++ /dev/null diff --git a/module/web/media/default/img/control_stop.png b/module/web/media/default/img/control_stop.png Binary files differdeleted file mode 100644 index 893bb60e5..000000000 --- a/module/web/media/default/img/control_stop.png +++ /dev/null diff --git a/module/web/media/default/img/control_stop_blue.png b/module/web/media/default/img/control_stop_blue.png Binary files differdeleted file mode 100644 index e6f75d232..000000000 --- a/module/web/media/default/img/control_stop_blue.png +++ /dev/null diff --git a/module/web/media/default/img/delete.png b/module/web/media/default/img/delete.png Binary files differdeleted file mode 100644 index 08f249365..000000000 --- a/module/web/media/default/img/delete.png +++ /dev/null diff --git a/module/web/media/default/img/drag_corner.gif b/module/web/media/default/img/drag_corner.gif Binary files differdeleted file mode 100644 index befb1adf1..000000000 --- a/module/web/media/default/img/drag_corner.gif +++ /dev/null diff --git a/module/web/media/default/img/error.png b/module/web/media/default/img/error.png Binary files differdeleted file mode 100644 index c37bd062e..000000000 --- a/module/web/media/default/img/error.png +++ /dev/null diff --git a/module/web/media/default/img/folder.png b/module/web/media/default/img/folder.png Binary files differdeleted file mode 100644 index 784e8fa48..000000000 --- a/module/web/media/default/img/folder.png +++ /dev/null diff --git a/module/web/media/default/img/full.png b/module/web/media/default/img/full.png Binary files differdeleted file mode 100644 index fea52af76..000000000 --- a/module/web/media/default/img/full.png +++ /dev/null diff --git a/module/web/media/default/img/head-login.png b/module/web/media/default/img/head-login.png Binary files differdeleted file mode 100644 index b59b7cbbf..000000000 --- a/module/web/media/default/img/head-login.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-collector.png b/module/web/media/default/img/head-menu-collector.png Binary files differdeleted file mode 100644 index 861be40bc..000000000 --- a/module/web/media/default/img/head-menu-collector.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-config.png b/module/web/media/default/img/head-menu-config.png Binary files differdeleted file mode 100644 index bbf43d4f3..000000000 --- a/module/web/media/default/img/head-menu-config.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-development.png b/module/web/media/default/img/head-menu-development.png Binary files differdeleted file mode 100644 index fad150fe1..000000000 --- a/module/web/media/default/img/head-menu-development.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-download.png b/module/web/media/default/img/head-menu-download.png Binary files differdeleted file mode 100644 index 98c5da9db..000000000 --- a/module/web/media/default/img/head-menu-download.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-home.png b/module/web/media/default/img/head-menu-home.png Binary files differdeleted file mode 100644 index 9d62109aa..000000000 --- a/module/web/media/default/img/head-menu-home.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-index.png b/module/web/media/default/img/head-menu-index.png Binary files differdeleted file mode 100644 index 44d631064..000000000 --- a/module/web/media/default/img/head-menu-index.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-news.png b/module/web/media/default/img/head-menu-news.png Binary files differdeleted file mode 100644 index 43950ebc9..000000000 --- a/module/web/media/default/img/head-menu-news.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-queue.png b/module/web/media/default/img/head-menu-queue.png Binary files differdeleted file mode 100644 index be98793ce..000000000 --- a/module/web/media/default/img/head-menu-queue.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-recent.png b/module/web/media/default/img/head-menu-recent.png Binary files differdeleted file mode 100644 index fc9b0497f..000000000 --- a/module/web/media/default/img/head-menu-recent.png +++ /dev/null diff --git a/module/web/media/default/img/head-menu-wiki.png b/module/web/media/default/img/head-menu-wiki.png Binary files differdeleted file mode 100644 index 07cf0102d..000000000 --- a/module/web/media/default/img/head-menu-wiki.png +++ /dev/null diff --git a/module/web/media/default/img/head-search-noshadow.png b/module/web/media/default/img/head-search-noshadow.png Binary files differdeleted file mode 100644 index aafdae015..000000000 --- a/module/web/media/default/img/head-search-noshadow.png +++ /dev/null diff --git a/module/web/media/default/img/head_bg1.png b/module/web/media/default/img/head_bg1.png Binary files differdeleted file mode 100644 index f2848c3cc..000000000 --- a/module/web/media/default/img/head_bg1.png +++ /dev/null diff --git a/module/web/media/default/img/images.png b/module/web/media/default/img/images.png Binary files differdeleted file mode 100644 index 184860d1e..000000000 --- a/module/web/media/default/img/images.png +++ /dev/null diff --git a/module/web/media/default/img/notice.png b/module/web/media/default/img/notice.png Binary files differdeleted file mode 100644 index 12cd1aef9..000000000 --- a/module/web/media/default/img/notice.png +++ /dev/null diff --git a/module/web/media/default/img/package_go.png b/module/web/media/default/img/package_go.png Binary files differdeleted file mode 100644 index aace63ad6..000000000 --- a/module/web/media/default/img/package_go.png +++ /dev/null diff --git a/module/web/media/default/img/page-tools-backlinks.png b/module/web/media/default/img/page-tools-backlinks.png Binary files differdeleted file mode 100644 index 3eb6a9ce3..000000000 --- a/module/web/media/default/img/page-tools-backlinks.png +++ /dev/null diff --git a/module/web/media/default/img/page-tools-edit.png b/module/web/media/default/img/page-tools-edit.png Binary files differdeleted file mode 100644 index 188e1c12b..000000000 --- a/module/web/media/default/img/page-tools-edit.png +++ /dev/null diff --git a/module/web/media/default/img/page-tools-revisions.png b/module/web/media/default/img/page-tools-revisions.png Binary files differdeleted file mode 100644 index 5c3b8587f..000000000 --- a/module/web/media/default/img/page-tools-revisions.png +++ /dev/null diff --git a/module/web/media/default/img/parseUri.png b/module/web/media/default/img/parseUri.png Binary files differdeleted file mode 100644 index 937bded9d..000000000 --- a/module/web/media/default/img/parseUri.png +++ /dev/null diff --git a/module/web/media/default/img/pencil.png b/module/web/media/default/img/pencil.png Binary files differdeleted file mode 100644 index 0bfecd50e..000000000 --- a/module/web/media/default/img/pencil.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 2443cd8b1..000000000 --- a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png +++ /dev/null diff --git a/module/web/media/default/img/reconnect.png b/module/web/media/default/img/reconnect.png Binary files differdeleted file mode 100644 index 49b269145..000000000 --- a/module/web/media/default/img/reconnect.png +++ /dev/null diff --git a/module/web/media/default/img/status_None.png b/module/web/media/default/img/status_None.png Binary files differdeleted file mode 100644 index 293b13f77..000000000 --- a/module/web/media/default/img/status_None.png +++ /dev/null diff --git a/module/web/media/default/img/status_downloading.png b/module/web/media/default/img/status_downloading.png Binary files differdeleted file mode 100644 index fb4ebc850..000000000 --- a/module/web/media/default/img/status_downloading.png +++ /dev/null diff --git a/module/web/media/default/img/status_failed.png b/module/web/media/default/img/status_failed.png Binary files differdeleted file mode 100644 index c37bd062e..000000000 --- a/module/web/media/default/img/status_failed.png +++ /dev/null diff --git a/module/web/media/default/img/status_finished.png b/module/web/media/default/img/status_finished.png Binary files differdeleted file mode 100644 index 89c8129a4..000000000 --- a/module/web/media/default/img/status_finished.png +++ /dev/null diff --git a/module/web/media/default/img/status_offline.png b/module/web/media/default/img/status_offline.png Binary files differdeleted file mode 100644 index 0cfd58596..000000000 --- a/module/web/media/default/img/status_offline.png +++ /dev/null diff --git a/module/web/media/default/img/status_proc.png b/module/web/media/default/img/status_proc.png Binary files differdeleted file mode 100644 index 67de2c6cc..000000000 --- a/module/web/media/default/img/status_proc.png +++ /dev/null diff --git a/module/web/media/default/img/status_queue.png b/module/web/media/default/img/status_queue.png Binary files differdeleted file mode 100644 index 293b13f77..000000000 --- a/module/web/media/default/img/status_queue.png +++ /dev/null diff --git a/module/web/media/default/img/status_waiting.png b/module/web/media/default/img/status_waiting.png Binary files differdeleted file mode 100644 index 2842cc338..000000000 --- a/module/web/media/default/img/status_waiting.png +++ /dev/null diff --git a/module/web/media/default/img/success.png b/module/web/media/default/img/success.png Binary files differdeleted file mode 100644 index 89c8129a4..000000000 --- a/module/web/media/default/img/success.png +++ /dev/null diff --git a/module/web/media/default/img/tab-background.png b/module/web/media/default/img/tab-background.png Binary files differdeleted file mode 100644 index 29a5d1991..000000000 --- a/module/web/media/default/img/tab-background.png +++ /dev/null diff --git a/module/web/media/default/img/tabs-border-bottom.png b/module/web/media/default/img/tabs-border-bottom.png Binary files differdeleted file mode 100644 index 02440f428..000000000 --- a/module/web/media/default/img/tabs-border-bottom.png +++ /dev/null diff --git a/module/web/media/default/img/user-actions-logout.png b/module/web/media/default/img/user-actions-logout.png Binary files differdeleted file mode 100644 index 0010931e2..000000000 --- a/module/web/media/default/img/user-actions-logout.png +++ /dev/null diff --git a/module/web/media/default/img/user-actions-profile.png b/module/web/media/default/img/user-actions-profile.png Binary files differdeleted file mode 100644 index 46573fff6..000000000 --- a/module/web/media/default/img/user-actions-profile.png +++ /dev/null diff --git a/module/web/media/default/img/user-info.png b/module/web/media/default/img/user-info.png Binary files differdeleted file mode 100644 index 6e643100f..000000000 --- a/module/web/media/default/img/user-info.png +++ /dev/null diff --git a/module/web/media/img/dialog-close.png b/module/web/media/img/dialog-close.png Binary files differdeleted file mode 100644 index 81ebb88b2..000000000 --- a/module/web/media/img/dialog-close.png +++ /dev/null diff --git a/module/web/media/img/dialog-question.png b/module/web/media/img/dialog-question.png Binary files differdeleted file mode 100644 index b0af3db5b..000000000 --- a/module/web/media/img/dialog-question.png +++ /dev/null diff --git a/module/web/media/img/favicon.ico b/module/web/media/img/favicon.ico Binary files differdeleted file mode 100644 index 58b1f4b89..000000000 --- a/module/web/media/img/favicon.ico +++ /dev/null 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,"&").replace(/"/g,""");};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} ".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> ".substitute({"plugin": link.plugin}); - html += "<img title='{{_("Delete Link")}}' style='cursor: pointer;' width='10px' height='10px' src='/media/default/img/delete.png' /> "; - html += "<img title='{{_("Restart Link")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='/media/default/img/arrow_refresh.png' /></div>"; - - var div = new Element("div", { - "id": "file_" + link.id, - "class": "child", - "html": html - }); - - li.store("order", link.order); - li.store("lid", link.id); - - li.adopt(div); - ul.adopt(li); - }); - this.sorts = new Sortables(ul, { - constrain: false, - clone: true, - revert: true, - opacity: 0.4, - handle: ".sorthandle", - onComplete: this.saveSort.bind(this) - }); - this.registerLinkEvents(); - this.linksLoaded = true; - indicateFinish(); - this.toggle(); - }, - - registerLinkEvents: function() { - this.ele.getElements('.child').each(function(child) { - var lid = child.get('id').match(/[0-9]+/); - var imgs = child.getElements('.child_secrow img'); - imgs[0].addEvent('click', function(e) { - new Request({ - method: 'get', - url: '/api/deleteFiles/[' + this + "]", - onSuccess: function() { - $('file_' + this).nix() - }.bind(this), - onFailure: indicateFail - }).send(); - }.bind(lid)); - - imgs[1].addEvent('click', function(e) { - new Request({ - method: 'get', - url: '/api/restartFile/' + this, - onSuccess: function() { - var ele = $('file_' + this); - var imgs = ele.getElements("img"); - imgs[0].set("src", "/media/default/img/status_queue.png"); - var spans = ele.getElements(".child_status"); - spans[1].set("html", "queued"); - indicateSuccess(); - }.bind(this), - onFailure: indicateFail - }).send(); - }.bind(lid)); - }); - }, - - toggle: function() { - var child = this.ele.getElement('.children'); - if (child.getStyle('display') == "block") { - child.dissolve(); - } else { - if (!this.linksLoaded) { - this.loadLinks(); - } else { - child.reveal(); - } - } - }, - - - deletePackage: function(event) { - indicateLoad(); - new Request({ - method: 'get', - url: '/api/deletePackages/[' + this.id + "]", - onSuccess: function() { - this.ele.nix(); - indicateFinish(); - }.bind(this), - onFailure: indicateFail - }).send(); - //hide_pack(); - event.stop(); - }, - - restartPackage: function(event) { - indicateLoad(); - new Request({ - method: 'get', - url: '/api/restartPackage/' + this.id, - onSuccess: function() { - this.close(); - indicateSuccess(); - }.bind(this), - onFailure: indicateFail - }).send(); - event.stop(); - }, - - close: function() { - var child = this.ele.getElement('.children'); - if (child.getStyle('display') == "block") { - child.dissolve(); - } - var ul = $("sort_children_{id}".substitute({"id": this.id})); - ul.erase("html"); - this.linksLoaded = false; - }, - - movePackage: function(event) { - indicateLoad(); - new Request({ - method: 'get', - url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id, - onSuccess: function() { - this.ele.nix(); - indicateFinish(); - }.bind(this), - onFailure: indicateFail - }).send(); - event.stop(); - }, - - editPackage: function(event) { - $("pack_form").removeEvents("submit"); - $("pack_form").addEvent("submit", this.savePackage.bind(this)); - - $("pack_id").set("value", this.id); - $("pack_name").set("value", this.name.get("text")); - $("pack_folder").set("value", this.folder.get("text")); - $("pack_pws").set("value", this.password.get("text")); - - root.packageBox.open(); - event.stop(); - }, - - savePackage: function(event) { - $("pack_form").send(); - this.name.set("text", $("pack_name").get("value")); - this.folder.set("text", $("pack_folder").get("value")); - this.password.set("text", $("pack_pws").get("value")); - root.packageBox.close(); - event.stop(); - }, - - saveSort: function(ele, copy) { - var order = []; - this.sorts.serialize(function(li, pos) { - if (li == ele && ele.retrieve("order") != pos) { - order.push(ele.retrieve("lid") + "|" + pos) - } - li.store("order", pos) - }); - if (order.length > 0) { - indicateLoad(); - new Request.JSON({ - method: 'get', - url: '/json/link_order/' + order[0], - onSuccess: indicateFinish, - onFailure: indicateFail - }).send(); - } - } - -}); - diff --git a/module/web/media/js/purr_static.js b/module/web/media/js/purr_static.js deleted file mode 100644 index 7e0aee949..000000000 --- a/module/web/media/js/purr_static.js +++ /dev/null @@ -1,308 +0,0 @@ -/* ---- -script: purr.js - -description: Class to create growl-style popup notifications. - -license: MIT-style - -authors: [atom smith] - -requires: -- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph] - -provides: [Purr, Element.alert] -... -*/ - - -var Purr = new Class({ - - 'options': { - 'mode': 'top', - 'position': 'left', - 'elementAlertClass': 'purr-element-alert', - 'elements': { - 'wrapper': 'div', - 'alert': 'div', - 'buttonWrapper': 'div', - 'button': 'button' - }, - 'elementOptions': { - 'wrapper': { - 'styles': { - 'position': 'fixed', - 'z-index': '9999' - }, - 'class': 'purr-wrapper' - }, - 'alert': { - 'class': 'purr-alert', - 'styles': { - 'opacity': '.85' - } - }, - 'buttonWrapper': { - 'class': 'purr-button-wrapper' - }, - 'button': { - 'class': 'purr-button' - } - }, - 'alert': { - 'buttons': [], - 'clickDismiss': true, - 'hoverWait': true, - 'hideAfter': 5000, - 'fx': { - 'duration': 500 - }, - 'highlightRepeat': false, - 'highlight': { // false to disable highlighting - 'start': '#FF0', - 'end': false - } - } - }, - - 'Implements': [Options, Events, Chain], - - 'initialize': function(options){ - this.setOptions(options); - this.createWrapper(); - return this; - }, - - 'bindAlert': function(){ - return this.alert.bind(this); - }, - - 'createWrapper': function(){ - this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper); - if(this.options.mode == 'top') - { - this.wrapper.setStyle('top', 0); - } - else - { - this.wrapper.setStyle('bottom', 0); - } - document.id(document.body).grab(this.wrapper); - this.positionWrapper(this.options.position); - }, - - 'positionWrapper': function(position){ - if(typeOf(position) == 'object') - { - - var wrapperCoords = this.getWrapperCoords(); - - this.wrapper.setStyles({ - 'bottom': '', - 'left': position.x, - 'top': position.y - wrapperCoords.height, - 'position': 'absolute' - }); - } - else if(position == 'left') - { - this.wrapper.setStyle('left', 0); - } - else if(position == 'right') - { - this.wrapper.setStyle('right', 0); - } - else - { - this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2)); - } - return this; - }, - - 'getWrapperCoords': function(){ - this.wrapper.setStyle('visibility', 'hidden'); - var measurer = this.alert('need something in here to measure'); - var coords = this.wrapper.getCoordinates(); - measurer.destroy(); - this.wrapper.setStyle('visibility',''); - return coords; - }, - - 'alert': function(msg, options){ - - options = Object.merge({}, this.options.alert, options || {}); - - var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert); - - if(typeOf(msg) == 'string') - { - alert.set('html', msg); - } - else if(typeOf(msg) == 'element') - { - alert.grab(msg); - } - else if(typeOf(msg) == 'array') - { - var alerts = []; - msg.each(function(m){ - alerts.push(this.alert(m, options)); - }, this); - return alerts; - } - - alert.store('options', options); - - if(options.buttons.length > 0) - { - options.clickDismiss = false; - options.hideAfter = false; - options.hoverWait = false; - var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper); - alert.grab(buttonWrapper); - options.buttons.each(function(button){ - if(button.text !== undefined) - { - var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button); - callbackButton.set('html', button.text); - if(button.callback !== undefined) - { - callbackButton.addEvent('click', button.callback.pass(alert)); - } - if(button.dismiss !== undefined && button.dismiss) - { - callbackButton.addEvent('click', this.dismiss.pass(alert, this)); - } - buttonWrapper.grab(callbackButton); - } - }, this); - } - if(options.className !== undefined) - { - alert.addClass(options.className); - } - - this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top'); - - var fx = Object.merge(this.options.alert.fx, options.fx); - var alertFx = new Fx.Morph(alert, fx); - alert.store('fx', alertFx); - this.fadeIn(alert); - - if(options.highlight) - { - alertFx.addEvent('complete', function(){ - alert.highlight(options.highlight.start, options.highlight.end); - if(options.highlightRepeat) - { - alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]); - } - }); - } - if(options.hideAfter) - { - this.dismiss(alert); - } - - if(options.clickDismiss) - { - alert.addEvent('click', function(){ - this.holdUp = false; - this.dismiss(alert, true); - }.bind(this)); - } - - if(options.hoverWait) - { - alert.addEvents({ - 'mouseenter': function(){ - this.holdUp = true; - }.bind(this), - 'mouseleave': function(){ - this.holdUp = false; - }.bind(this) - }); - } - - return alert; - }, - - 'fadeIn': function(alert){ - var alertFx = alert.retrieve('fx'); - alertFx.set({ - 'opacity': 0 - }); - alertFx.start({ - 'opacity': [this.options.elementOptions.alert.styles.opacity, '.9'].pick() - }); - }, - - 'dismiss': function(alert, now){ - now = now || false; - var options = alert.retrieve('options'); - if(now) - { - this.fadeOut(alert); - } - else - { - this.fadeOut.delay(options.hideAfter, this, alert); - } - }, - - 'fadeOut': function(alert){ - if(this.holdUp) - { - this.dismiss.delay(100, this, [alert, true]); - return null; - } - var alertFx = alert.retrieve('fx'); - if(!alertFx) - { - return null; - } - var to = { - 'opacity': 0 - }; - if(this.options.mode == 'top') - { - to['margin-top'] = '-'+alert.offsetHeight+'px'; - } - else - { - to['margin-bottom'] = '-'+alert.offsetHeight+'px'; - } - alertFx.start(to); - alertFx.addEvent('complete', function(){ - alert.destroy(); - }); - } -}); - -Element.implement({ - - 'alert': function(msg, options){ - var alert = this.retrieve('alert'); - if(!alert) - { - options = options || { - 'mode':'top' - }; - alert = new Purr(options); - this.store('alert', alert); - } - - var coords = this.getCoordinates(); - - alert.alert(msg, options); - - alert.wrapper.setStyles({ - 'bottom': '', - 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2), - 'top': coords.top - (alert.wrapper.getHeight()), - 'position': 'absolute' - }); - - } - -});
\ No newline at end of file diff --git a/module/web/media/js/settings.coffee b/module/web/media/js/settings.coffee deleted file mode 100644 index 9205233e3..000000000 --- a/module/web/media/js/settings.coffee +++ /dev/null @@ -1,107 +0,0 @@ -root = this - -window.addEvent 'domready', -> - root.accountDialog = new MooDialog {destroyOnHide: false} - root.accountDialog.setContent $ 'account_box' - - new TinyTab $$('#toptabs li a'), $$('#tabs-body > span') - - $$('ul.nav').each (nav) -> - new MooDropMenu nav, { - onOpen: (el) -> el.fade 'in' - onClose: (el) -> el.fade 'out' - onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500} - } - - new SettingsUI() - - -class SettingsUI - constructor: -> - @menu = $$ "#general-menu li" - @menu.append $$ "#plugin-menu li" - - @name = $ "tabsback" - @general = $ "general_form_content" - @plugin = $ "plugin_form_content" - - el.addEvent 'click', @menuClick.bind(this) for el in @menu - - $("general|submit").addEvent "click", @configSubmit.bind(this) - $("plugin|submit").addEvent "click", @configSubmit.bind(this) - - $("account_add").addEvent "click", (e) -> - root.accountDialog.open() - e.stop() - - $("account_reset").addEvent "click", (e) -> - root.accountDialog.close() - - $("account_add_button").addEvent "click", @addAccount.bind(this) - $("account_submit").addEvent "click", @submitAccounts.bind(this) - - - menuClick: (e) -> - [category, section] = e.target.get("id").split("|") - name = e.target.get "text" - - - target = if category is "general" then @general else @plugin - target.dissolve() - - new Request({ - "method" : "get" - "url" : "/json/load_config/#{category}/#{section}" - "onSuccess": (data) => - target.set "html", data - target.reveal() - this.name.set "text", name - }).send() - - - configSubmit: (e) -> - category = e.target.get("id").split("|")[0]; - form = $("#{category}_form"); - - form.set "send", { - "method": "post" - "url": "/json/save_config/#{category}" - "onSuccess" : -> - root.notify.alert '{{ _("Settings saved.")}}', { - 'className': 'success' - } - "onFailure": -> - root.notify.alert '{{ _("Error occured.")}}', { - 'className': 'error' - } - } - form.send() - e.stop() - - addAccount: (e) -> - form = $ "add_account_form" - form.set "send", { - "method": "post" - "onSuccess" : -> window.location.reload() - "onFailure": -> - root.notify.alert '{{_("Error occured.")}}', { - 'className': 'error' - } - } - - form.send() - e.stop() - - submitAccounts: (e) -> - form = $ "account_form" - form.set "send", { - "method": "post", - "onSuccess" : -> window.location.reload() - "onFailure": -> - root.notify.alert('{{ _("Error occured.") }}', { - 'className': 'error' - }); - } - - form.send() - e.stop()
\ No newline at end of file diff --git a/module/web/media/js/settings.js b/module/web/media/js/settings.js deleted file mode 100644 index 9191fac72..000000000 --- a/module/web/media/js/settings.js +++ /dev/null @@ -1,3 +0,0 @@ -{% autoescape true %} -var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})(); -{% endautoescape %}
\ No newline at end of file diff --git a/module/web/media/js/tinytab_static.js b/module/web/media/js/tinytab_static.js deleted file mode 100644 index 6c38292f5..000000000 --- a/module/web/media/js/tinytab_static.js +++ /dev/null @@ -1,50 +0,0 @@ -/* ---- -description: TinyTab - Tiny and simple tab handler for Mootools. - -license: MIT-style - -authors: -- Danillo César de O. Melo - -requires: -- core/1.2.4: '*' - -provides: TinyTab - -... -*/ -(function($) { - this.TinyTab = new Class({ - Implements: Events, - initialize: function(tabs, contents, opt) { - this.tabs = tabs; - this.contents = contents; - this.header = $("tabsback"); - this.headers = []; - for(var i =0; i < this.tabs.length; i++){ - this.headers.push(""); - } - if(!opt) opt = {}; - this.css = opt.selectedClass || 'selected'; - this.select(this.tabs[0]); - tabs.each(function(el){ - el.addEvent('click',function(e){ - this.select(el); - e.stop(); - }.bind(this)); - }.bind(this)); - }, - - select: function(el) { - this.tabs.removeClass(this.css); - el.addClass(this.css); - this.contents.setStyle('display','none'); - var index = this.tabs.indexOf(el); - this.header.set("text", this.headers[index]); - var content = this.contents[index]; - content.setStyle('display','block'); - this.fireEvent('change',[content,el]); - } - }); -})(document.id);
\ No newline at end of file diff --git a/module/web/middlewares.py b/module/web/middlewares.py deleted file mode 100644 index e0e6c3102..000000000 --- a/module/web/middlewares.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import gzip - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - -class StripPathMiddleware(object): - def __init__(self, app): - self.app = app - - def __call__(self, e, h): - e['PATH_INFO'] = e['PATH_INFO'].rstrip('/') - return self.app(e, h) - - -class PrefixMiddleware(object): - def __init__(self, app, prefix="/pyload"): - self.app = app - self.prefix = prefix - - def __call__(self, e, h): - path = e["PATH_INFO"] - if path.startswith(self.prefix): - e['PATH_INFO'] = path.replace(self.prefix, "", 1) - return self.app(e, h) - -# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) -# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php - -# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) -# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php - -# WSGI middleware -# Gzip-encodes the response. - -class GZipMiddleWare(object): - - def __init__(self, application, compress_level=6): - self.application = application - self.compress_level = int(compress_level) - - def __call__(self, environ, start_response): - if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''): - # nothing for us to do, so this middleware will - # be a no-op: - return self.application(environ, start_response) - response = GzipResponse(start_response, self.compress_level) - app_iter = self.application(environ, - response.gzip_start_response) - if app_iter is not None: - response.finish_response(app_iter) - - return response.write() - -def header_value(headers, key): - for header, value in headers: - if key.lower() == header.lower(): - return value - -def update_header(headers, key, value): - remove_header(headers, key) - headers.append((key, value)) - -def remove_header(headers, key): - for header, value in headers: - if key.lower() == header.lower(): - headers.remove((header, value)) - break - -class GzipResponse(object): - - def __init__(self, start_response, compress_level): - self.start_response = start_response - self.compress_level = compress_level - self.buffer = StringIO() - self.compressible = False - self.content_length = None - self.headers = () - - def gzip_start_response(self, status, headers, exc_info=None): - self.headers = headers - ct = header_value(headers,'content-type') - ce = header_value(headers,'content-encoding') - cl = header_value(headers, 'content-length') - if cl: - cl = int(cl) - else: - cl = 201 - self.compressible = False - if ct and (ct.startswith('text/') or ct.startswith('application/')) \ - and 'zip' not in ct and cl > 200: - self.compressible = True - if ce: - self.compressible = False - if self.compressible: - headers.append(('content-encoding', 'gzip')) - remove_header(headers, 'content-length') - self.headers = headers - self.status = status - return self.buffer.write - - def write(self): - out = self.buffer - out.seek(0) - s = out.getvalue() - out.close() - return [s] - - def finish_response(self, app_iter): - if self.compressible: - output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, - fileobj=self.buffer) - else: - output = self.buffer - try: - for s in app_iter: - output.write(s) - if self.compressible: - output.close() - finally: - if hasattr(app_iter, 'close'): - try: - app_iter.close() - except : - pass - - content_length = self.buffer.tell() - update_header(self.headers, "Content-Length" , str(content_length)) - self.start_response(self.status, self.headers)
\ No newline at end of file diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py deleted file mode 100644 index df4a4b3d4..000000000 --- a/module/web/pyload_app.py +++ /dev/null @@ -1,533 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" -from datetime import datetime -from operator import itemgetter, attrgetter - -import time -import os -import sys -from os import listdir -from os.path import isdir, isfile, join, abspath -from sys import getfilesystemencoding -from urllib import unquote - -from bottle import route, static_file, request, response, redirect, HTTPError, error - -from webinterface import PYLOAD, PYLOAD_DIR, PROJECT_DIR, SETUP, env - -from utils import render_to_response, parse_permissions, parse_userdata, \ - login_required, get_permission, set_permission, permlist, toDict, set_session - -from filters import relpath, unquotepath - -from module.utils import formatSize, save_join, fs_encode, fs_decode - -# Helper - -def pre_processor(): - s = request.environ.get('beaker.session') - user = parse_userdata(s) - perms = parse_permissions(s) - status = {} - captcha = False - update = False - plugins = False - if user["is_authenticated"]: - status = PYLOAD.statusServer() - info = PYLOAD.getInfoByPlugin("UpdateManager") - captcha = PYLOAD.isCaptchaWaiting() - - # check if update check is available - if info: - if info["pyload"] == "True": update = True - if info["plugins"] == "True": plugins = True - - - return {"user": user, - 'status': status, - 'captcha': captcha, - 'perms': perms, - 'url': request.url, - 'update': update, - 'plugins': plugins} - - -def base(messages): - return render_to_response('base.html', {'messages': messages}, [pre_processor]) - - -## Views -@error(500) -def error500(error): - print "An error occured while processing the request." - if error.traceback: - print error.traceback - - return base(["An Error occured, please enable debug mode to get more details.", error, - error.traceback.replace("\n", "<br>") if error.traceback else "No Traceback"]) - -# render js -@route("/media/js/<path:re:.+\.js>") -def js_dynamic(path): - response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", - time.gmtime(time.time() + 60 * 60 * 24 * 2)) - response.headers['Cache-control'] = "public" - response.headers['Content-Type'] = "text/javascript; charset=UTF-8" - - try: - # static files are not rendered - if "static" not in path and "mootools" not in path: - t = env.get_template("js/%s" % path) - return t.render() - else: - return static_file(path, root=join(PROJECT_DIR, "media", "js")) - except: - return HTTPError(404, "Not Found") - -@route('/media/<path:path>') -def server_static(path): - response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", - time.gmtime(time.time() + 60 * 60 * 24 * 7)) - response.headers['Cache-control'] = "public" - return static_file(path, root=join(PROJECT_DIR, "media")) - -@route('/favicon.ico') -def favicon(): - return static_file("favicon.ico", root=join(PROJECT_DIR, "media", "img")) - - -@route('/login', method="GET") -def login(): - if not PYLOAD and SETUP: - redirect("/setup") - else: - return render_to_response("login.html", proc=[pre_processor]) - - -@route('/nopermission') -def nopermission(): - return base([_("You dont have permission to access this page.")]) - - -@route("/login", method="POST") -def login_post(): - user = request.forms.get("username") - password = request.forms.get("password") - - info = PYLOAD.checkAuth(user, password) - - if not info: - return render_to_response("login.html", {"errors": True}, [pre_processor]) - - set_session(request, info) - return redirect("/") - - -@route("/logout") -def logout(): - s = request.environ.get('beaker.session') - s.delete() - return render_to_response("logout.html", proc=[pre_processor]) - - -@route("/") -@route("/home") -@login_required("LIST") -def home(): - try: - res = [toDict(x) for x in PYLOAD.statusDownloads()] - except: - s = request.environ.get('beaker.session') - s.delete() - return redirect("/login") - - for link in res: - if link["status"] == 12: - link["information"] = "%s kB @ %s kB/s" % (link["size"] - link["bleft"], link["speed"]) - - return render_to_response("home.html", {"res": res}, [pre_processor]) - - -@route("/queue") -@login_required("LIST") -def queue(): - queue = PYLOAD.getQueue() - - queue.sort(key=attrgetter("order")) - - return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor]) - - -@route("/collector") -@login_required('LIST') -def collector(): - queue = PYLOAD.getCollector() - - queue.sort(key=attrgetter("order")) - - return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor]) - - -@route("/downloads") -@login_required('DOWNLOAD') -def downloads(): - root = PYLOAD.getConfigValue("general", "download_folder") - - if not isdir(root): - return base([_('Download directory not found.')]) - data = { - 'folder': [], - 'files': [] - } - - items = listdir(fs_encode(root)) - - for item in sorted([fs_decode(x) for x in items]): - if isdir(save_join(root, item)): - folder = { - 'name': item, - 'path': item, - 'files': [] - } - files = listdir(save_join(root, item)) - for file in sorted([fs_decode(x) for x in files]): - try: - if isfile(save_join(root, item, file)): - folder['files'].append(file) - except: - pass - - data['folder'].append(folder) - elif isfile(join(root, item)): - data['files'].append(item) - - return render_to_response('downloads.html', {'files': data}, [pre_processor]) - - -@route("/downloads/get/<path:re:.+>") -@login_required("DOWNLOAD") -def get_download(path): - path = unquote(path).decode("utf8") - #@TODO some files can not be downloaded - - root = PYLOAD.getConfigValue("general", "download_folder") - - path = path.replace("..", "") - try: - return static_file(fs_encode(path), fs_encode(root)) - - except Exception, e: - print e - return HTTPError(404, "File not Found.") - - - -@route("/settings") -@login_required('SETTINGS') -def config(): - conf = PYLOAD.getConfig() - plugin = PYLOAD.getPluginConfig() - - conf_menu = [] - plugin_menu = [] - - for entry in sorted(conf.keys()): - conf_menu.append((entry, conf[entry].description)) - - for entry in sorted(plugin.keys()): - plugin_menu.append((entry, plugin[entry].description)) - - accs = PYLOAD.getAccounts(False) - - for data in accs: - if data.trafficleft == -1: - data.trafficleft = _("unlimited") - elif not data.trafficleft: - data.trafficleft = _("not available") - else: - data.trafficleft = formatSize(data.trafficleft * 1024) - - if data.validuntil == -1: - data.validuntil = _("unlimited") - elif not data.validuntil : - data.validuntil = _("not available") - else: - t = time.localtime(data.validuntil) - data.validuntil = time.strftime("%d.%m.%Y", t) - - if "time" in data.options: - try: - data.options["time"] = data.options["time"][0] - except: - data.options["time"] = "0:00-0:00" - - if "limitDL" in data.options: - data.options["limitdl"] = data.options["limitDL"][0] - else: - data.options["limitdl"] = "0" - - return render_to_response('settings.html', - {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, 'types': PYLOAD.getAccountTypes()}, - [pre_processor]) - - -@route("/filechooser") -@route("/pathchooser") -@route("/filechooser/:file#.+#") -@route("/pathchooser/:path#.+#") -@login_required('STATUS') -def path(file="", path=""): - if file: - type = "file" - else: - type = "folder" - - path = os.path.normpath(unquotepath(path)) - - if os.path.isfile(path): - oldfile = path - path = os.path.dirname(path) - else: - oldfile = '' - - abs = False - - if os.path.isdir(path): - if os.path.isabs(path): - cwd = os.path.abspath(path) - abs = True - else: - cwd = relpath(path) - else: - cwd = os.getcwd() - - try: - cwd = cwd.encode("utf8") - except: - pass - - cwd = os.path.normpath(os.path.abspath(cwd)) - parentdir = os.path.dirname(cwd) - if not abs: - if os.path.abspath(cwd) == "/": - cwd = relpath(cwd) - else: - cwd = relpath(cwd) + os.path.sep - parentdir = relpath(parentdir) + os.path.sep - - if os.path.abspath(cwd) == "/": - parentdir = "" - - try: - folders = os.listdir(cwd) - except: - folders = [] - - files = [] - - for f in folders: - try: - f = f.decode(getfilesystemencoding()) - data = {'name': f, 'fullpath': join(cwd, f)} - data['sort'] = data['fullpath'].lower() - data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f)))) - data['ext'] = os.path.splitext(f)[1] - except: - continue - - if os.path.isdir(join(cwd, f)): - data['type'] = 'dir' - else: - data['type'] = 'file' - - if os.path.isfile(join(cwd, f)): - data['size'] = os.path.getsize(join(cwd, f)) - - power = 0 - while (data['size'] / 1024) > 0.3: - power += 1 - data['size'] /= 1024. - units = ('', 'K', 'M', 'G', 'T') - data['unit'] = units[power] + 'Byte' - else: - data['size'] = '' - - files.append(data) - - files = sorted(files, key=itemgetter('type', 'sort')) - - return render_to_response('pathchooser.html', - {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile, - 'absolute': abs}, []) - - -@route("/logs") -@route("/logs", method="POST") -@route("/logs/:item") -@route("/logs/:item", method="POST") -@login_required('LOGS') -def logs(item=-1): - s = request.environ.get('beaker.session') - - perpage = s.get('perpage', 34) - reversed = s.get('reversed', False) - - warning = "" - conf = PYLOAD.getConfigValue("log","file_log") - if not conf: - warning = "Warning: File log is disabled, see settings page." - - perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all')) - fro = None - - if request.environ.get('REQUEST_METHOD', "GET") == "POST": - try: - fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S') - except: - pass - try: - perpage = int(request.forms['perpage']) - s['perpage'] = perpage - - reversed = bool(request.forms.get('reversed', False)) - s['reversed'] = reversed - except: - pass - - s.save() - - try: - item = int(item) - except: - pass - - log = PYLOAD.getLog() - if not perpage: - item = 0 - - if item < 1 or type(item) is not int: - item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1 - - if type(fro) is datetime: # we will search for datetime - item = -1 - - data = [] - counter = 0 - perpagecheck = 0 - for l in log: - counter += 1 - - if counter >= item: - try: - date, time, level, message = l.decode("utf8", "ignore").split(" ", 3) - dtime = datetime.strptime(date + ' ' + time, '%d.%m.%Y %H:%M:%S') - except: - dtime = None - date = '?' - time = ' ' - level = '?' - message = l - if item == -1 and dtime is not None and fro <= dtime: - item = counter #found our datetime - if item >= 0: - data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message}) - perpagecheck += 1 - if fro is None and dtime is not None: #if fro not set set it to first showed line - fro = dtime - if perpagecheck >= perpage > 0: - break - - if fro is None: #still not set, empty log? - fro = datetime.now() - if reversed: - data.reverse() - return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'), - 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p), - 'iprev': 1 if item - perpage < 1 else item - perpage, - 'inext': (item + perpage) if item + perpage < len(log) else item}, - [pre_processor]) - - -@route("/admin") -@route("/admin", method="POST") -@login_required("ADMIN") -def admin(): - # convert to dict - user = dict([(name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()]) - perms = permlist() - - for data in user.itervalues(): - data["perms"] = {} - get_permission(data["perms"], data["permission"]) - data["perms"]["admin"] = True if data["role"] is 0 else False - - - s = request.environ.get('beaker.session') - if request.environ.get('REQUEST_METHOD', "GET") == "POST": - for name in user: - if request.POST.get("%s|admin" % name, False): - user[name]["role"] = 0 - user[name]["perms"]["admin"] = True - elif name != s["name"]: - user[name]["role"] = 1 - user[name]["perms"]["admin"] = False - - # set all perms to false - for perm in perms: - user[name]["perms"][perm] = False - - - for perm in request.POST.getall("%s|perms" % name): - user[name]["perms"][perm] = True - - user[name]["permission"] = set_permission(user[name]["perms"]) - - PYLOAD.setUserPermission(name, user[name]["permission"], user[name]["role"]) - - return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor]) - - -@route("/setup") -def setup(): - if PYLOAD or not SETUP: - return base([_("Run pyLoadCore.py -s to access the setup.")]) - - return render_to_response('setup.html', {"user": False, "perms": False}) - - -@route("/info") -def info(): - conf = PYLOAD.getConfigDict() - - if hasattr(os, "uname"): - extra = os.uname() - else: - extra = tuple() - - data = {"python": sys.version, - "os": " ".join((os.name, sys.platform) + extra), - "version": PYLOAD.getServerVersion(), - "folder": abspath(PYLOAD_DIR), "config": abspath(""), - "download": abspath(conf["general"]["download_folder"]["value"]), - "freespace": formatSize(PYLOAD.freeSpace()), - "remote": conf["remote"]["port"]["value"], - "webif": conf["webinterface"]["port"]["value"], - "language": conf["general"]["language"]["value"]} - - return render_to_response("info.html", data, [pre_processor]) diff --git a/module/web/servers/lighttpd_default.conf b/module/web/servers/lighttpd_default.conf deleted file mode 100644 index e56dda35f..000000000 --- a/module/web/servers/lighttpd_default.conf +++ /dev/null @@ -1,153 +0,0 @@ -# lighttpd configuration file -# -# use it as a base for lighttpd 1.0.0 and above -# -# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $ - -############ Options you really have to take care of #################### - -## modules to load -# at least mod_access and mod_accesslog should be loaded -# all other module should only be loaded if really neccesary -# - saves some time -# - saves memory -server.modules = ( - "mod_rewrite", - "mod_redirect", - "mod_alias", - "mod_access", -# "mod_trigger_b4_dl", -# "mod_auth", -# "mod_status", -# "mod_setenv", - "mod_fastcgi", -# "mod_proxy", -# "mod_simple_vhost", -# "mod_evhost", -# "mod_userdir", -# "mod_cgi", -# "mod_compress", -# "mod_ssi", -# "mod_usertrack", -# "mod_expire", -# "mod_secdownload", -# "mod_rrdtool", -# "mod_accesslog" - ) - -## A static document-root. For virtual hosting take a look at the -## mod_simple_vhost module. -server.document-root = "%(path)" - -## where to send error-messages to -server.errorlog = "%(path)/error.log" - -# files to check for if .../ is requested -index-file.names = ( "index.php", "index.html", - "index.htm", "default.htm" ) - -## set the event-handler (read the performance section in the manual) -# server.event-handler = "freebsd-kqueue" # needed on OS X - -# mimetype mapping -mimetype.assign = ( - ".pdf" => "application/pdf", - ".sig" => "application/pgp-signature", - ".spl" => "application/futuresplash", - ".class" => "application/octet-stream", - ".ps" => "application/postscript", - ".torrent" => "application/x-bittorrent", - ".dvi" => "application/x-dvi", - ".gz" => "application/x-gzip", - ".pac" => "application/x-ns-proxy-autoconfig", - ".swf" => "application/x-shockwave-flash", - ".tar.gz" => "application/x-tgz", - ".tgz" => "application/x-tgz", - ".tar" => "application/x-tar", - ".zip" => "application/zip", - ".mp3" => "audio/mpeg", - ".m3u" => "audio/x-mpegurl", - ".wma" => "audio/x-ms-wma", - ".wax" => "audio/x-ms-wax", - ".ogg" => "application/ogg", - ".wav" => "audio/x-wav", - ".gif" => "image/gif", - ".jar" => "application/x-java-archive", - ".jpg" => "image/jpeg", - ".jpeg" => "image/jpeg", - ".png" => "image/png", - ".xbm" => "image/x-xbitmap", - ".xpm" => "image/x-xpixmap", - ".xwd" => "image/x-xwindowdump", - ".css" => "text/css", - ".html" => "text/html", - ".htm" => "text/html", - ".js" => "text/javascript", - ".asc" => "text/plain", - ".c" => "text/plain", - ".cpp" => "text/plain", - ".log" => "text/plain", - ".conf" => "text/plain", - ".text" => "text/plain", - ".txt" => "text/plain", - ".dtd" => "text/xml", - ".xml" => "text/xml", - ".mpeg" => "video/mpeg", - ".mpg" => "video/mpeg", - ".mov" => "video/quicktime", - ".qt" => "video/quicktime", - ".avi" => "video/x-msvideo", - ".asf" => "video/x-ms-asf", - ".asx" => "video/x-ms-asf", - ".wmv" => "video/x-ms-wmv", - ".bz2" => "application/x-bzip", - ".tbz" => "application/x-bzip-compressed-tar", - ".tar.bz2" => "application/x-bzip-compressed-tar", - # default mime type - "" => "application/octet-stream", - ) - -# Use the "Content-Type" extended attribute to obtain mime type if possible -#mimetype.use-xattr = "enable" - -#### accesslog module -accesslog.filename = "%(path)/access.log" - -url.access-deny = ( "~", ".inc" ) - -$HTTP["url"] =~ "\.pdf$" { - server.range-requests = "disable" -} -static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" ) - -server.pid-file = "%(path)/lighttpd.pid" - -server.bind = "%(host)" -server.port = %(port) - -#server.document-root = "/home/user/public_html" -fastcgi.server = ( - "/pyload.fcgi" => ( - "main" => ( - "host" => "127.0.0.1", - "port" => 9295, - "check-local" => "disable", - "docroot" => "/", - ) - ), -) - -alias.url = ( - "/media/" => "%(media)/", - "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/", -) - -url.rewrite-once = ( - "^(/media.*)$" => "$1", - "^(/admin/media.*)$" => "$1", - "^/favicon\.ico$" => "/media/img/favicon.ico", - "^(/pyload.fcgi.*)$" => "$1", - "^(/.*)$" => "/pyload.fcgi$1", -) - -%(ssl)
\ No newline at end of file diff --git a/module/web/servers/nginx_default.conf b/module/web/servers/nginx_default.conf deleted file mode 100644 index b4ebd1e02..000000000 --- a/module/web/servers/nginx_default.conf +++ /dev/null @@ -1,87 +0,0 @@ -daemon off; -pid %(path)/nginx.pid; -worker_processes 2; - -error_log %(path)/error.log info; - -events { - worker_connections 1024; - use epoll; -} - -http { - include /etc/nginx/conf/mime.types; - default_type application/octet-stream; - - %(ssl) - - log_format main - '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $bytes_sent ' - '"$http_referer" "$http_user_agent" ' - '"$gzip_ratio"'; - - error_log %(path)/error.log info; - - client_header_timeout 10m; - client_body_timeout 10m; - send_timeout 10m; - - client_body_temp_path %(path)/client_body_temp; - proxy_temp_path %(path)/proxy_temp; - fastcgi_temp_path %(path)/fastcgi_temp; - - - connection_pool_size 256; - client_header_buffer_size 1k; - large_client_header_buffers 4 2k; - request_pool_size 4k; - - gzip on; - gzip_min_length 1100; - gzip_buffers 4 8k; - gzip_types text/plain; - - output_buffers 1 32k; - postpone_output 1460; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - - keepalive_timeout 75 20; - - ignore_invalid_headers on; - - server { - listen %(port); - server_name %(host); - # site_media - folder in uri for static files - location ^~ /media { - root %(media)/..; - } - location ^~ /admin/media { - root /usr/lib/python%(version)/site-packages/django/contrib; - } -location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) { - access_log off; - expires 30d; -} - location / { - # host and port to fastcgi server - fastcgi_pass 127.0.0.1:9295; - fastcgi_param PATH_INFO $fastcgi_script_name; - fastcgi_param REQUEST_METHOD $request_method; - fastcgi_param QUERY_STRING $query_string; - fastcgi_param CONTENT_TYPE $content_type; - fastcgi_param CONTENT_LENGTH $content_length; - fastcgi_param SERVER_NAME $server_name; - fastcgi_param SERVER_PORT $server_port; - fastcgi_param SERVER_PROTOCOL $server_protocol; - fastcgi_pass_header Authorization; - fastcgi_intercept_errors off; - } - access_log %(path)/access.log main; - error_log %(path)/error.log; - } - } diff --git a/module/web/templates/500.html b/module/web/templates/500.html deleted file mode 100644 index e15090b66..000000000 --- a/module/web/templates/500.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" - "http://www.w3.org/TR/html4/loose.dtd"> -<html> -<head> - <title>Server Error</title> -</head> -<body> -<h1>Server Error occured. Please enable debug mode to get a more detailed report.</h1> -</body> -</html> diff --git a/module/web/templates/default/admin.html b/module/web/templates/default/admin.html deleted file mode 100644 index b049411fd..000000000 --- a/module/web/templates/default/admin.html +++ /dev/null @@ -1,98 +0,0 @@ -{% extends 'default/base.html' %} - -{% block head %} - <script type="text/javascript" src="media/js/admin.js"></script> -{% endblock %} - - -{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %} -{% block subtitle %}{{ _("Administrate") }}{% endblock %} - -{% block content %} - - <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> | - <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a> - <br> - <br> - - {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br> - {{ _("Important: Admin user have always all permissions!") }} - - <form action="" method="POST"> - <table class="settable wide"> - <thead style="font-size: 11px"> - <th> - {{ _("Name") }} - </th> - <th> - {{ _("Change Password") }} - </th> - <th> - {{ _("Admin") }} - </th> - <th> - {{ _("Permissions") }} - </th> - </thead> - - {% for name, data in users.iteritems() %} - <tr> - <td>{{ name }}</td> - <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td> - <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} - checked="True" {% endif %}"></td> - <td> - <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms"> - {% for perm in permlist %} - {% if data.perms|getitem(perm) %} - <option selected="selected">{{ perm }}</option> - {% else %} - <option>{{ perm }}</option> - {% endif %} - {% endfor %} - </select> - </td> - </tr> - {% endfor %} - - - </table> - - <button class="styled_button" type="submit">{{ _("Submit") }}</button> - </form> -{% endblock %} -{% block hidden %} - <div id="password_box" class="window_box" style="z-index: 2"> - <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data"> - <h1>{{ _("Change Password") }}</h1> - - <p>{{ _("Enter your current and desired Password.") }}</p> - <label for="user_login">{{ _("User") }} - <span class="small">{{ _("Your username.") }}</span> - </label> - <input id="user_login" name="user_login" type="text" size="20"/> - - <label for="login_current_password">{{ _("Current password") }} - <span class="small">{{ _("The password for this account.") }}</span> - </label> - <input id="login_current_password" name="login_current_password" type="password" size="20"/> - - <label for="login_new_password">{{ _("New password") }} - <span class="small">{{ _("The new password.") }}</span> - </label> - <input id="login_new_password" name="login_new_password" type="password" size="20"/> - - <label for="login_new_password2">{{ _("New password (repeat)") }} - <span class="small">{{ _("Please repeat the new password.") }}</span> - </label> - <input id="login_new_password2" name="login_new_password2" type="password" size="20"/> - - - <button id="login_password_button" type="submit">{{ _("Submit") }}</button> - <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button> - <div class="spacer"></div> - - </form> - - </div> -{% endblock %} diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html deleted file mode 100644 index 147c08a37..000000000 --- a/module/web/templates/default/base.html +++ /dev/null @@ -1,180 +0,0 @@ -<?xml version="1.0" ?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> - -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -<link rel="stylesheet" type="text/css" href="/media/default/css/default.css"/> -<link rel="stylesheet" type="text/css" href="/media/default/css/window.css"/> -<link rel="stylesheet" type="text/css" href="/media/default/css/MooDialog.css"/> - -<script type="text/javascript" src="/media/js/mootools-core-1.4.1.js"></script> -<script type="text/javascript" src="/media/js/mootools-more-1.4.0.1.js"></script> -<script type="text/javascript" src="/media/js/MooDialog_static.js"></script> -<script type="text/javascript" src="/media/js/purr_static.js"></script> - - -<script type="text/javascript" src="/media/js/base.js"></script> - -<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title> - -{% block head %} -{% endblock %} -</head> -<body> -<a class="anchor" name="top" id="top"></a> - -<div id="head-panel"> - - - <div id="head-search-and-login"> - {% block headpanel %} - - {% if user.is_authenticated %} - - -{% if update %} -<span> -<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span> -</span> -{% endif %} - - -{% if plugins %} -<span> -<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span> -</span> -{% endif %} - -<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}"> -<img src="/media/default/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" /> -<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span> -</span> - - <img src="/media/default/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span> - <ul id="user-actions"> - <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li> - {% if user.is_admin %} - <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li> - {% endif %} - <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li> - - </ul> -{% else %} - <span style="padding-right: 2px;">{{_("Please Login!")}}</span> -{% endif %} - - {% endblock %} - </div> - - <a href="/"><img id="head-logo" src="/media/default/img/pyload-logo-edited3.5-new-font-small.png" alt="pyLoad" /></a> - - <div id="head-menu"> - <ul> - - {% macro selected(name, right=False) -%} - {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %} - {% if not name in url and right -%}class="right"{%- endif %} - {%- endmacro %} - - - {% block menu %} - <li> - <a href="/" title=""><img src="/media/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> - </li> - <li {{ selected('queue') }}> - <a href="/queue/" title=""><img src="/media/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> - </li> - <li {{ selected('collector') }}> - <a href="/collector/" title=""><img src="/media/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> - </li> - <li {{ selected('downloads') }}> - <a href="/downloads/" title=""><img src="/media/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> - </li> -{# <li {{ selected('filemanager') }}>#} -{# <a href="/filemanager/" title=""><img src="/media/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#} -{# </li>#} - <li {{ selected('logs', True) }}> - <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> - </li> - <li {{ selected('settings', True) }}> - <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a> - </li> - {% endblock %} - - </ul> - </div> - - <div style="clear:both;"></div> -</div> - -{% if perms.STATUS %} -<ul id="page-actions2"> - <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li> - <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li> - <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li> - <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li> -</ul> -{% endif %} - -{% if perms.LIST %} -<ul id="page-actions"> - <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> - <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> - <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li> - <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li> - <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li> -</ul> -{% endif %} - -{% block pageactions %} -{% endblock %} -<br/> - -<div id="body-wrapper" class="dokuwiki"> - -<div id="content" lang="en" dir="ltr"> - -<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1> - -{% block statusbar %} -{% endblock %} - - -<br/> - -<div class="level1" style="clear:both"> -</div> -<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript> - -{% for message in messages %} - <b><p>{{message}}</p></b> -{% endfor %} - -<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;"> - <img src="/media/default/img/ajax-loader.gif" alt="" style="padding-right: 5px"/> - {{_("loading")}} -</div> - -{% block content %} -{% endblock content %} - - <hr style="clear: both;" /> - -<div id="foot">© 2008-2011 pyLoad Team -<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br /> -<!--<div class="breadcrumbs"></div>--> - -</div> -</div> -</div> - -<div style="display: none;"> - {% include "default/window.html" %} - {% include "default/captcha.html" %} - {% block hidden %} - {% endblock %} -</div> -</body> -</html> diff --git a/module/web/templates/default/captcha.html b/module/web/templates/default/captcha.html deleted file mode 100644 index 288375b76..000000000 --- a/module/web/templates/default/captcha.html +++ /dev/null @@ -1,42 +0,0 @@ -<!-- Captcha box --> -<div id="cap_box" class="window_box"> - - <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;"> - - <h1>{{_("Captcha reading")}}</h1> - <p id="cap_title">{{_("Please read the text on the captcha.")}}</p> - - <div id="cap_textual"> - - <input id="cap_id" name="cap_id" type="hidden" value="" /> - - <label>{{_("Captcha")}} - <span class="small">{{_("The captcha.")}}</span> - </label> - <span class="cont"> - <img id="cap_textual_img" src=""> - </span> - - <label>{{_("Text")}} - <span class="small">{{_("Input the text on the captcha.")}}</span> - </label> - <input id="cap_result" name="cap_result" type="text" size="20" /> - - </div> - - <div id="cap_positional" style="text-align: center"> - <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer"> - </div> - - <div id="button_bar" style="text-align: center"> - <span> - <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button> - <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button> - </span> - </div> - - <div class="spacer"></div> - - </form> - -</div>
\ No newline at end of file diff --git a/module/web/templates/default/downloads.html b/module/web/templates/default/downloads.html deleted file mode 100644 index 450b8a102..000000000 --- a/module/web/templates/default/downloads.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'default/base.html' %} - -{% block title %}Downloads - {{super()}} {% endblock %} - -{% block subtitle %} -{{_("Downloads")}} -{% endblock %} - -{% block content %} - -<ul> - {% for folder in files.folder %} - <li> - {{ folder.name }} - <ul> - {% for file in folder.files %} - <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li> - {% endfor %} - </ul> - </li> - {% endfor %} - - {% for file in files.files %} - <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li> - {% endfor %} - -</ul> - -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/filemanager.html b/module/web/templates/default/filemanager.html deleted file mode 100644 index 97095c13e..000000000 --- a/module/web/templates/default/filemanager.html +++ /dev/null @@ -1,80 +0,0 @@ -{% extends 'default/base.html' %} - -{% block head %} - -<script type="text/javascript" src="/filemanager_ui.js"></script> - -<script type="text/javascript"> - -document.addEvent("domready", function(){ - var fmUI = new FilemanagerUI("url",1); -}); -</script> -{% endblock %} - -{% block title %}Downloads - {{super()}} {% endblock %} - - -{% block subtitle %} -{{_("FileManager")}} -{% endblock %} - -{% macro display_file(file) %} - <li class="file"> - <input type="hidden" name="path" class="path" value="{{ file.path }}" /> - <input type="hidden" name="name" class="name" value="{{ file.name }}" /> - <span> - <b>{{ file.name }}</b> - <span class="buttons" style="opacity:0"> - <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" /> - - <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" /> - - <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" /> - - <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" /> - - <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" /> - - <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/add_folder.png" /> - </span> - </span> - <div style="display:none">{{ _("Folder is empty") }}</div> -</li>
\ No newline at end of file diff --git a/module/web/templates/default/home.html b/module/web/templates/default/home.html deleted file mode 100644 index 7359e326c..000000000 --- a/module/web/templates/default/home.html +++ /dev/null @@ -1,266 +0,0 @@ -{% extends 'default/base.html' %} -{% block head %} - -<script type="text/javascript"> - -var em; -var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0); - -document.addEvent("domready", function(){ - em = new EntryManager(); -}); - -var EntryManager = new Class({ - initialize: function(){ - this.json = new Request.JSON({ - url: "json/links", - secure: false, - async: true, - onSuccess: this.update.bind(this), - initialDelay: 0, - delay: 2500, - limit: 30000 - }); - - this.ids = [{% for link in content %} - {% if forloop.last %} - {{ link.id }} - {% else %} - {{ link.id }}, - {% endif %} - {% endfor %}]; - - this.entries = []; - this.container = $('LinksAktiv'); - - this.parseFromContent(); - - this.json.startTimer(); - }, - parseFromContent: function(){ - this.ids.each(function(id,index){ - var entry = new LinkEntry(id); - entry.parse(); - this.entries.push(entry) - }, this); - }, - update: function(data){ - - try{ - this.ids = this.entries.map(function(item){ - return item.fid - }); - - this.ids.filter(function(id){ - return !this.ids.contains(id) - },data).each(function(id){ - var index = this.ids.indexOf(id); - this.entries[index].remove(); - this.entries = this.entries.filter(function(item){return item.fid != this},id); - this.ids = this.ids.erase(id) - }, this); - - data.links.each(function(link, i){ - if (this.ids.contains(link.fid)){ - - var index = this.ids.indexOf(link.fid); - this.entries[index].update(link) - - }else{ - var entry = new LinkEntry(link.fid); - entry.insert(link); - this.entries.push(entry); - this.ids.push(link.fid); - this.container.adopt(entry.elements.tr,entry.elements.pgbTr); - entry.fade.start('opacity', 1); - entry.fadeBar.start('opacity', 1); - - } - }, this) - }catch(e){ - //alert(e) - } - } -}); - - -var LinkEntry = new Class({ - initialize: function(id){ - this.fid = id; - this.id = id; - }, - parse: function(){ - this.elements = { - tr: $("link_{id}".substitute({id: this.id})), - name: $("link_{id}_name".substitute({id: this.id})), - status: $("link_{id}_status".substitute({id: this.id})), - info: $("link_{id}_info".substitute({id: this.id})), - bleft: $("link_{id}_bleft".substitute({id: this.id})), - percent: $("link_{id}_percent".substitute({id: this.id})), - remove: $("link_{id}_remove".substitute({id: this.id})), - pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})), - pgb: $("link_{id}_pgb".substitute({id: this.id})) - }; - this.initEffects(); - }, - insert: function(item){ - try{ - - this.elements = { - tr: new Element('tr', { - 'html': '', - 'styles':{ - 'opacity': 0 - } - }), - name: new Element('td', { - 'html': item.name - }), - status: new Element('td', { - 'html': item.statusmsg - }), - info: new Element('td', { - 'html': item.info - }), - bleft: new Element('td', { - 'html': humanFileSize(item.size) - }), - percent: new Element('span', { - 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft) - }), - remove: new Element('img',{ - 'src': 'media/default/img/control_cancel.png', - 'styles':{ - 'vertical-align': 'middle', - 'margin-right': '-20px', - 'margin-left': '5px', - 'margin-top': '-2px', - 'cursor': 'pointer' - } - }), - pgbTr: new Element('tr', { - 'html':'' - }), - pgb: new Element('div', { - 'html': ' ', - '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 }}%;"> </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> - | - <a href="http://forum.pyload.org/" target="_blank">Forum</a> - | - <a href="http://pyload.org/irc/" target="_blank">Chat</a> - </li> - <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li> - <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li> - <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li> - - </ul> - - <h3>{{ _("System") }}</h3> - <table class="system"> - <tr> - <td>{{ _("Python:") }}</td> - <td>{{ python }}</td> - </tr> - <tr> - <td>{{ _("OS:") }}</td> - <td>{{ os }}</td> - </tr> - <tr> - <td>{{ _("pyLoad version:") }}</td> - <td>{{ version }}</td> - </tr> - <tr> - <td>{{ _("Installation Folder:") }}</td> - <td>{{ folder }}</td> - </tr> - <tr> - <td>{{ _("Config Folder:") }}</td> - <td>{{ config }}</td> - </tr> - <tr> - <td>{{ _("Download Folder:") }}</td> - <td>{{ download }}</td> - </tr> - <tr> - <td>{{ _("Free Space:") }}</td> - <td>{{ freespace }}</td> - </tr> - <tr> - <td>{{ _("Language:") }}</td> - <td>{{ language }}</td> - </tr> - <tr> - <td>{{ _("Webinterface Port:") }}</td> - <td>{{ webif }}</td> - </tr> - <tr> - <td>{{ _("Remote Interface Port:") }}</td> - <td>{{ remote }}</td> - </tr> - </table> - -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/login.html b/module/web/templates/default/login.html deleted file mode 100644 index 9e91ad309..000000000 --- a/module/web/templates/default/login.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'default/base.html' %} - -{% block title %}{{_("Login")}} - {{super()}} {% endblock %} - -{% block content %} - -<div class="centeralign"> -<form action="" method="post" accept-charset="utf-8" id="login"> - <div class="no"> - <input type="hidden" name="do" value="login" /> - <fieldset> - <legend>Login</legend> - <label> - <span>{{_("Username")}}</span> - <input type="text" size="20" name="username" /> - </label> - <br /> - <label> - <span>{{_("Password")}}</span> - <input type="password" size="20" name="password" /> - </label> - <br /> - <input type="submit" value="Login" class="button" /> - </fieldset> - </div> -</form> - -{% if errors %} -<p>{{_("Your username and password didn't match. Please try again.")}}</p> - {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b> -{% endif %} - -</div> -<br> - -{% endblock %} diff --git a/module/web/templates/default/logout.html b/module/web/templates/default/logout.html deleted file mode 100644 index d3f07472b..000000000 --- a/module/web/templates/default/logout.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'default/base.html' %} - -{% block head %} -<meta http-equiv="refresh" content="3; url=/"> -{% endblock %} - -{% block content %} -<p><b>{{_("You were successfully logged out.")}}</b></p> -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/logs.html b/module/web/templates/default/logs.html deleted file mode 100644 index d6288df0e..000000000 --- a/module/web/templates/default/logs.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends 'default/base.html' %} - -{% block title %}{{_("Logs")}} - {{super()}} {% endblock %} -{% block subtitle %}{{_("Logs")}}{% endblock %} -{% block head %} -<link rel="stylesheet" type="text/css" href="/media/default/css/log.css"/> -{% endblock %} - -{% block content %} -<div style="clear: both;"></div> - -<div class="logpaginator"><a href="{{ "/logs/1" }}"><< {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">< {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} ></a> <a href="/logs/">{{_("End")}} >></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 %} /> - <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;"> </div> -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/pathchooser.html b/module/web/templates/default/pathchooser.html deleted file mode 100644 index d00637055..000000000 --- a/module/web/templates/default/pathchooser.html +++ /dev/null @@ -1,76 +0,0 @@ -<html> -<head> - <script class="javascript"> - function chosen() - { - opener.ifield.value = document.forms[0].p.value; - close(); - } - function exit() - { - close(); - } - function setInvalid() { - document.forms[0].send.disabled = 'disabled'; - document.forms[0].p.style.color = '#FF0000'; - } - function setValid() { - document.forms[0].send.disabled = ''; - document.forms[0].p.style.color = '#000000'; - } - function setFile(file) - { - document.forms[0].p.value = file; - setValid(); - - } - </script> - <link rel="stylesheet" type="text/css" href="/media/default/css/pathchooser.css"/> -</head> -<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}> -<center> - <div id="paths"> - <form method="get" action="?" onSubmit="chosen();" onReset="exit();"> - <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();"> - <input type="submit" value="Ok" name="send"> - </form> - - {% if type == 'folder' %} - <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> - {% else %} - <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> - {% endif %} - </div> - <table border="0" cellspacing="0" cellpadding="3"> - <tr> - <th>{{_("name")}}</th> - <th>{{_("size")}}</th> - <th>{{_("type")}}</th> - <th>{{_("last modified")}}</th> - </tr> - {% if parentdir %} - <tr> - <td colspan="4"> - <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a> - </td> - </tr> - {% endif %} -{% for file in files %} - <tr> - {% if type == 'folder' %} - <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td> - {% else %} - <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td> - {% endif %} - <td class="size">{{ file.size|float|filesizeformat }}</td> - <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td> - <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td> - <tr> -<!-- <tr> - <td colspan="4">{{_("no content")}}</td> - </tr> --> -{% endfor %} - </table> - </center> -</body> -</html>
\ No newline at end of file diff --git a/module/web/templates/default/queue.html b/module/web/templates/default/queue.html deleted file mode 100644 index 046abbe49..000000000 --- a/module/web/templates/default/queue.html +++ /dev/null @@ -1,104 +0,0 @@ -{% extends 'default/base.html' %} -{% block head %} - -<script type="text/javascript" src="/media/js/package_ui.js"></script> - -<script type="text/javascript"> - -document.addEvent("domready", function(){ - var pUI = new PackageUI("url", {{ target }}); -}); -</script> -{% endblock %} - -{% if target %} - {% set name = _("Queue") %} -{% else %} - {% set name = _("Collector") %} -{% endif %} - -{% block title %}{{name}} - {{super()}} {% endblock %} -{% block subtitle %}{{name}}{% endblock %} - -{% block pageactions %} -<ul id="page-actions-more"> - <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li> - <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li> -</ul> -{% endblock %} - -{% block content %} -{% autoescape true %} - -<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;"> -{% for package in content %} - <li> -<div id="package_{{package.pid}}" class="package"> - <div class="order" style="display: none;">{{ package.order }}</div> - - <div class="packagename" style="cursor: pointer"> - <img class="package_drag" src="/media/default/img/folder.png" style="cursor: move; margin-bottom: -2px"> - <span class="name">{{package.name}}</span> - - <span class="buttons" style="opacity:0"> - <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" /> - - <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/arrow_refresh.png" /> - - <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/pencil.png" /> - - <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/package_go.png" /> - </span> - </div> - {% set progress = (package.linksdone * 100) / package.linkstotal %} - - <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em"> - <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div> - <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px"> - {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label> - <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px"> - {{ package.linksdone }} / {{ package.linkstotal }}</label> - </div> - <div style="clear: both; margin-bottom: -10px"></div> - - <div id="children_{{package.pid}}" style="display: none;" class="children"> - <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span> - <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0"> - </ul> - </div> -</div> - </li> -{% endfor %} -</ul> -{% endautoescape %} -{% endblock %} - -{% block hidden %} -<div id="pack_box" class="window_box" style="z-index: 2"> - <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data"> - <h1>{{_("Edit Package")}}</h1> - <p>{{_("Edit the package detais below.")}}</p> - <input name="pack_id" id="pack_id" type="hidden" value=""/> - <label for="pack_name">{{_("Name")}} - <span class="small">{{_("The name of the package.")}}</span> - </label> - <input id="pack_name" name="pack_name" type="text" size="20" /> - - <label for="pack_folder">{{_("Folder")}} - <span class="small">{{_("Name of subfolder for these downloads.")}}</span> - </label> - <input id="pack_folder" name="pack_folder" type="text" size="20" /> - - <label for="pack_pws">{{_("Password")}} - <span class="small">{{_("List of passwords used for unrar.")}}</span> - </label> - <textarea rows="3" name="pack_pws" id="pack_pws"></textarea> - - <button type="submit">{{_("Submit")}}</button> - <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button> - <div class="spacer"></div> - - </form> - -</div> -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html deleted file mode 100644 index a4443025a..000000000 --- a/module/web/templates/default/settings.html +++ /dev/null @@ -1,204 +0,0 @@ -{% extends 'default/base.html' %} - -{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %} -{% block subtitle %}{{ _("Config") }}{% endblock %} - -{% block head %} - <script type="text/javascript" src="/media/js/tinytab_static.js"></script> - <script type="text/javascript" src="/media/js/MooDropMenu_static.js"></script> - <script type="text/javascript" src="/media/js/settings.js"></script> - -{% endblock %} - -{% block content %} - - <ul id="toptabs" class="tabs"> - <li><a class="selected" href="#">{{ _("General") }}</a></li> - <li><a href="#">{{ _("Plugins") }}</a></li> - <li><a href="#">{{ _("Accounts") }}</a></li> - </ul> - - <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;"> - - </div> - - <span id="tabs-body"> - <!-- General --> - <span id="general" class="active tabContent"> - <ul class="nav tabs"> - <li class> - <a>Menu</a> - <ul id="general-menu"> - {% for entry,name in conf.general %} - <nobr> - <li id="general|{{ entry }}">{{ name }}</li> - </nobr> - <br> - {% endfor %} - </ul> - </li> - </ul> - - <form id="general_form" action="" method="POST" autocomplete="off"> - <span id="general_form_content"> - <br> - <h3> {{ _("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> {{ _("Choose a section from the menu") }}</h3> - <br> - </span> - <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> - </form> - - </span> - - <!-- Accounts --> - <span id="accounts" class="tabContent"> - <form id="account_form" action="/json/update_accounts" method="POST"> - - <table class="settable wide"> - - <thead> - <tr> - <th>{{ _("Plugin") }}</th> - <th>{{ _("Name") }}</th> - <th>{{ _("Password") }}</th> - <th>{{ _("Status") }}</th> - <th>{{ _("Premium") }}</th> - <th>{{ _("Valid until") }}</th> - <th>{{ _("Traffic left") }}</th> - <th>{{ _("Time") }}</th> - <th>{{ _("Max Parallel") }}</th> - <th>{{ _("Delete?") }}</th> - </tr> - </thead> - - - {% for account in conf.accs %} - {% set plugin = account.type %} - <tr> - <td> - <span style="padding:5px">{{ plugin }}</span> - </td> - - <td><label for="{{plugin}}|password;{{account.login}}" - style="color:#424242;">{{ account.login }}</label></td> - <td> - <input id="{{plugin}}|password;{{account.login}}" - name="{{plugin}}|password;{{account.login}}" - type="password" value="{{account.password}}" size="12"/> - </td> - <td> - {% if account.valid %} - <span style="font-weight: bold; color: #006400;"> - {{ _("valid") }} - {% else %} - <span style="font-weight: bold; color: #8b0000;"> - {{ _("not valid") }} - {% endif %} - </span> - </td> - <td> - {% if account.premium %} - <span style="font-weight: bold; color: #006400;"> - {{ _("yes") }} - {% else %} - <span style="font-weight: bold; color: #8b0000;"> - {{ _("no") }} - {% endif %} - </span> - </td> - <td> - <span style="font-weight: bold;"> - {{ account.validuntil }} - </span> - </td> - <td> - <span style="font-weight: bold;"> - {{ account.trafficleft }} - </span> - </td> - <td> - <input id="{{plugin}}|time;{{account.login}}" - name="{{plugin}}|time;{{account.login}}" type="text" - size="7" value="{{account.time}}"/> - </td> - <td> - <input id="{{plugin}}|limitdl;{{account.login}}" - name="{{plugin}}|limitdl;{{account.login}}" type="text" - size="2" value="{{account.limitdl}}"/> - </td> - <td> - <input id="{{plugin}}|delete;{{account.login}}" - name="{{plugin}}|delete;{{account.login}}" type="checkbox" - value="True"/> - </td> - </tr> - {% endfor %} - </table> - - <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button> - <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button> - </form> - </span> - </span> -{% endblock %} -{% block hidden %} -<div id="account_box" class="window_box" style="z-index: 2"> -<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data"> -<h1>{{_("Add Account")}}</h1> -<p>{{_("Enter your account data to use premium features.")}}</p> -<label for="account_login">{{_("Login")}} -<span class="small">{{_("Your username.")}}</span> -</label> -<input id="account_login" name="account_login" type="text" size="20" /> - -<label for="account_password">{{_("Password")}} -<span class="small">{{_("The password for this account.")}}</span> -</label> -<input id="account_password" name="account_password" type="password" size="20" /> - -<label for="account_type">{{_("Type")}} -<span class="small">{{_("Choose the hoster for your account.")}}</span> -</label> - <select name=account_type id="account_type"> - {% for type in types|sort %} - <option value="{{ type }}">{{ type }}</option> - {% endfor %} - </select> - -<button id="account_add_button" type="submit">{{_("Add")}}</button> -<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button> -<div class="spacer"></div> - -</form> - -</div> -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/settings_item.html b/module/web/templates/default/settings_item.html deleted file mode 100644 index 813383343..000000000 --- a/module/web/templates/default/settings_item.html +++ /dev/null @@ -1,48 +0,0 @@ -<table class="settable"> - {% if section.outline %} - <tr><th colspan="2">{{ section.outline }}</th></tr> - {% endif %} - {% for okey, option in section.iteritems() %} - {% if okey not in ("desc","outline") %} - <tr> - <td><label for="{{skey}}|{{okey}}" - style="color:#424242;">{{ option.desc }}:</label></td> - <td> - {% if option.type == "bool" %} - <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> - <option {% if option.value %} selected="selected" - {% endif %}value="True">{{ _("on") }}</option> - <option {% if not option.value %} selected="selected" - {% endif %}value="False">{{ _("off") }}</option> - </select> - {% elif ";" in option.type %} - <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> - {% for entry in option.list %} - <option {% if option.value == entry %} - selected="selected" {% endif %}>{{ entry }}</option> - {% endfor %} - </select> - {% elif option.type == "folder" %} - <input name="{{skey}}|{{okey}}" type="text" - id="{{skey}}|{{okey}}" value="{{option.value}}"/> - <input name="browsebutton" type="button" - onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;" - value="{{_("Browse")}}"/> - {% elif option.type == "file" %} - <input name="{{skey}}|{{okey}}" type="text" - id="{{skey}}|{{okey}}" value="{{option.value}}"/> - <input name="browsebutton" type="button" - onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;" - value="{{_("Browse")}}"/> - {% elif option.type == "password" %} - <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" - type="password" value="{{option.value}}"/> - {% else %} - <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" - type="text" value="{{option.value}}"/> - {% endif %} - </td> - </tr> - {% endif %} - {% endfor %} -</table>
\ No newline at end of file diff --git a/module/web/templates/default/setup.html b/module/web/templates/default/setup.html deleted file mode 100644 index 39ef6f1e8..000000000 --- a/module/web/templates/default/setup.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'default/base.html' %} - -{% block title %}{{ _("Setup") }} - {{ super() }} {% endblock %} -{% block subtitle %}{{ _("Setup") }}{% endblock %} -{% block headpanel %}Welcome to pyLoad{% endblock %} -{% block menu %} - <li style="height: 25px"> <!-- Needed to get enough margin --> - </li> -{% endblock %} - -{% block content %} - Comming Soon. -{% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/window.html b/module/web/templates/default/window.html deleted file mode 100644 index a11323fe0..000000000 --- a/module/web/templates/default/window.html +++ /dev/null @@ -1,46 +0,0 @@ -<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe> - -<div id="add_box" class="window_box"> -<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data"> -<h1>{{_("Add Package")}}</h1> -<p>{{_("Paste your links or upload a container.")}}</p> -<label for="add_name">{{_("Name")}} -<span class="small">{{_("The name of the new package.")}}</span> -</label> -<input id="add_name" name="add_name" type="text" size="20" /> - -<label for="add_links">{{_("Links")}} -<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span> -<span class="small"> {{ _("Filter urls") }} -<img alt="URIParsing" Title="Parse Uri" src="/media/default/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/> -</span> - -</label> -<textarea rows="5" name="add_links" id="add_links"></textarea> - -<label for="add_password">{{_("Password")}} - <span class="small">{{_("Password for RAR-Archive")}}</span> -</label> -<input id="add_password" name="add_password" type="text" size="20"> - -<label>{{_("File")}} -<span class="small">{{_("Upload a container.")}}</span> -</label> -<input type="file" name="add_file" id="add_file"/> - -<label for="add_dest">{{_("Destination")}} -</label> -<span class="cont"> - {{_("Queue")}} - <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/> - {{_("Collector")}} - <input type="radio" name="add_dest" id="add_dest2" value="0"/> -</span> - -<button type="submit">{{_("Add Package")}}</button> -<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button> -<div class="spacer"></div> - -</form> - -</div>
\ No newline at end of file diff --git a/module/web/utils.py b/module/web/utils.py deleted file mode 100644 index a89c87558..000000000 --- a/module/web/utils.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this plrogram; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" -from bottle import request, HTTPError, redirect, ServerAdapter - -from webinterface import env, TEMPLATE - -from module.Api import has_permission, PERMS, ROLE - -def render_to_response(name, args={}, proc=[]): - for p in proc: - args.update(p()) - - t = env.get_template(TEMPLATE + "/" + name) - return t.render(**args) - - -def parse_permissions(session): - perms = dict([(x, False) for x in dir(PERMS) if not x.startswith("_")]) - perms["ADMIN"] = False - perms["is_admin"] = False - - if not session.get("authenticated", False): - return perms - - if session.get("role") == ROLE.ADMIN: - for k in perms.iterkeys(): - perms[k] = True - - elif session.get("perms"): - p = session.get("perms") - get_permission(perms, p) - - return perms - - -def permlist(): - return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"] - - -def get_permission(perms, p): - """Returns a dict with permission key - - :param perms: dictionary - :param p: bits - """ - for name in permlist(): - perms[name] = has_permission(p, getattr(PERMS, name)) - - -def set_permission(perms): - """generates permission bits from dictionary - - :param perms: dict - """ - permission = 0 - for name in dir(PERMS): - if name.startswith("_"): continue - - if name in perms and perms[name]: - permission |= getattr(PERMS, name) - - return permission - - -def set_session(request, info): - s = request.environ.get('beaker.session') - s["authenticated"] = True - s["user_id"] = info["id"] - s["name"] = info["name"] - s["role"] = info["role"] - s["perms"] = info["permission"] - s["template"] = info["template"] - s.save() - - return s - - -def parse_userdata(session): - return {"name": session.get("name", "Anonymous"), - "is_admin": True if session.get("role", 1) == 0 else False, - "is_authenticated": session.get("authenticated", False)} - - -def login_required(perm=None): - def _dec(func): - def _view(*args, **kwargs): - s = request.environ.get('beaker.session') - if s.get("name", None) and s.get("authenticated", False): - if perm: - perms = parse_permissions(s) - if perm not in perms or not perms[perm]: - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return HTTPError(403, "Forbidden") - else: - return redirect("/nopermission") - - return func(*args, **kwargs) - else: - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return HTTPError(403, "Forbidden") - else: - return redirect("/login") - - return _view - - return _dec - - -def toDict(obj): - ret = {} - for att in obj.__slots__: - ret[att] = getattr(obj, att) - return ret - - -class CherryPyWSGI(ServerAdapter): - def run(self, handler): - from wsgiserver import CherryPyWSGIServer - - server = CherryPyWSGIServer((self.host, self.port), handler) - server.start() diff --git a/module/web/webinterface.py b/module/web/webinterface.py deleted file mode 100644 index ec8b2e56c..000000000 --- a/module/web/webinterface.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: RaNaN -""" - -import sys -import module.common.pylgettext as gettext - -import os -from os.path import join, abspath, dirname, exists -from os import makedirs - -PROJECT_DIR = abspath(dirname(__file__)) -PYLOAD_DIR = abspath(join(PROJECT_DIR, "..", "..")) - -sys.path.append(PYLOAD_DIR) - -from module import InitHomeDir -from module.utils import decode, formatSize - -import bottle -from bottle import run, app - -from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache -from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware - -SETUP = None -PYLOAD = None - -from module.web import ServerThread - -if not ServerThread.core: - if ServerThread.setup: - SETUP = ServerThread.setup - config = SETUP.config - else: - raise Exception("Could not access pyLoad Core") -else: - PYLOAD = ServerThread.core.api - config = ServerThread.core.config - -from module.common.JsEngine import JsEngine - -JS = JsEngine() - -TEMPLATE = config.get('webinterface', 'template') -DL_ROOT = config.get('general', 'download_folder') -LOG_ROOT = config.get('log', 'log_folder') -PREFIX = config.get('webinterface', 'prefix') - -if PREFIX: - PREFIX = PREFIX.rstrip("/") - if not PREFIX.startswith("/"): - PREFIX = "/" + PREFIX - -DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv -bottle.debug(DEBUG) - -cache = join("tmp", "jinja_cache") -if not exists(cache): - makedirs(cache) - -bcc = FileSystemBytecodeCache(cache, '%s.cache') -loader = PrefixLoader({ - "default": FileSystemLoader(join(PROJECT_DIR, "templates", "default")), - 'js': FileSystemLoader(join(PROJECT_DIR, 'media', 'js')) -}) - -env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False, - bytecode_cache=bcc) - -from filters import quotepath, path_make_relative, path_make_absolute, truncate, date - -env.filters["quotepath"] = quotepath -env.filters["truncate"] = truncate -env.filters["date"] = date -env.filters["path_make_relative"] = path_make_relative -env.filters["path_make_absolute"] = path_make_absolute -env.filters["decode"] = decode -env.filters["type"] = lambda x: str(type(x)) -env.filters["formatsize"] = formatSize -env.filters["getitem"] = lambda x, y: x.__getitem__(y) -if PREFIX: - env.filters["url"] = lambda x: x -else: - env.filters["url"] = lambda x: PREFIX + x if x.startswith("/") else x - -gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) -translation = gettext.translation("django", join(PYLOAD_DIR, "locale"), - languages=[config.get("general", "language"), "en"],fallback=True) -translation.install(True) -env.install_gettext_translations(translation) - -from beaker.middleware import SessionMiddleware - -session_opts = { - 'session.type': 'file', - 'session.cookie_expires': False, - 'session.data_dir': './tmp', - 'session.auto': False -} - -web = StripPathMiddleware(SessionMiddleware(app(), session_opts)) -web = GZipMiddleWare(web) - -if PREFIX: - web = PrefixMiddleware(web, prefix=PREFIX) - -import pyload_app -import json_app -import cnl_app -import api_app - -def run_simple(host="0.0.0.0", port="8000"): - run(app=web, host=host, port=port, quiet=True) - - -def run_lightweight(host="0.0.0.0", port="8000"): - run(app=web, host=host, port=port, quiet=True, server="bjoern") - - -def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""): - from wsgiserver import CherryPyWSGIServer - - if cert and key: - CherryPyWSGIServer.ssl_certificate = cert - CherryPyWSGIServer.ssl_private_key = key - - CherryPyWSGIServer.numthreads = theads - - from utils import CherryPyWSGI - - run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True) - - -def run_fcgi(host="0.0.0.0", port="8000"): - from bottle import FlupFCGIServer - - run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True) - - -if __name__ == "__main__": - run(app=web, port=8001) diff --git a/pavement.py b/pavement.py index ac9a6fa1a..ea6775b1a 100644 --- a/pavement.py +++ b/pavement.py @@ -1,84 +1,44 @@ # -*- coding: utf-8 -*- - from paver.easy import * -from paver.setuputils import setup from paver.doctools import cog +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 os import sys +import shutil import re -from urllib import urlretrieve -from subprocess import call, Popen, PIPE -from zipfile import ZipFile +from glob import glob +from tempfile import mkdtemp +from subprocess import call, Popen PROJECT_DIR = path(__file__).dirname() sys.path.append(PROJECT_DIR) -options = environment.options -path('pyload').mkdir() - -extradeps = [] -if sys.version_info <= (2, 5): - extradeps += 'simplejson' - -setup( - name="pyload", - version="0.4.9", - description='Fast, lightweight and full featured download manager.', - long_description=open(PROJECT_DIR / "README").read(), - keywords = ('pyload', 'download-manager', 'one-click-hoster', 'download'), - url="http://pyload.org", - download_url='http://pyload.org/download', - license='GPL v3', - author="pyLoad Team", - author_email="support@pyload.org", - platforms = ('Any',), - #package_dir={'pyload': 'src'}, - packages=['pyload'], - #package_data=find_package_data(), - #data_files=[], - include_package_data=True, - exclude_package_data={'pyload': ['docs*', 'scripts*', 'tests*']}, #exluced from build but not from sdist - # 'bottle >= 0.10.0' not in list, because its small and contain little modifications - install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker', 'BeautifulSoup>=3.2, <3.3'] + extradeps, - extras_require={ - 'SSL': ["pyOpenSSL"], - 'DLC': ['pycrypto'], - 'lightweight webserver': ['bjoern'], - 'RSS plugins': ['feedparser'], - }, - #setup_requires=["setuptools_hg"], - entry_points={ - 'console_scripts': [ - 'pyLoadCore = pyLoadCore:main', - 'pyLoadCli = pyLoadCli:main' - ]}, - zip_safe=False, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Topic :: Internet :: WWW/HTTP", - "Environment :: Console", - "Environment :: Web Environment", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: GNU General Public License (GPL)", - "Operating System :: OS Independent", - "Programming Language :: Python :: 2" - ] -) +from pyload import __version__ +options = environment.options options( sphinx=Bunch( builddir="_build", sourcedir="" ), - get_source=Bunch( - src="https://bitbucket.org/spoob/pyload/get/tip.zip", - rev=None, - clean=False - ), - thrift=Bunch( - path="../thrift/trunk/compiler/cpp/thrift", - gen="" + apitypes=Bunch( + path="thrift", ), virtualenv=Bunch( dir="env", @@ -86,165 +46,212 @@ options( virtual="virtualenv2", ), cog=Bunch( - pattern="*.py", + pattern=["*.py", "*.rst"], ) ) # xgettext args -xargs = ["--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyLoad", - "--package-version=%s" % options.version, "--msgid-bugs-address='bugs@pyload.org'"] +xargs = ["--language=Python", "--add-comments=L10N", + "--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyload", + "--package-version=%s" % __version__, "--msgid-bugs-address='bugs@pyload.org'"] + +# Modules replace rules +module_replace = [ + ('from module.plugins.Hoster import Hoster', 'from pyload.plugins.Hoster import Hoster'), + ('from module.plugins.Hook import threaded, Expose, Hook', + 'from pyload.plugins.Addon import threaded, Expose, Addon'), + ('from module.plugins.Hook import Hook', 'from pyload.plugins.Addon import Addon'), + ('from module.common.json_layer import json_loads, json_dumps', 'from pyload.utils import json_loads, json_dumps'), + ('from module.common.json_layer import json_loads', 'from pyload.utils import json_loads'), + ('from module.common.json_layer import json_dumps', 'from pyload.utils import json_dumps'), + ('from module.utils import parseFileSize', 'from pyload.utils import parseFileSize'), + ('from module.utils import save_join, save_path', + 'from pyload.utils.fs import save_join, save_filename as save_path'), + ('from module.utils import save_join, fs_encode', 'from pyload.utils.fs import save_join, fs_encode'), + ('from module.utils import save_join', 'from pyload.utils.fs import save_join'), + ('from module.utils import fs_encode', 'from pyload.utils.fs import fs_encode'), + ('from module.unescape import unescape', 'from pyload.utils import html_unescape as unescape'), + ('from module.lib.BeautifulSoup import BeautifulSoup', 'from BeautifulSoup import BeautifulSoup'), + ('from module.lib import feedparser', 'import feedparser'), + ('self.account.getAccountInfo(self.user, ', 'self.account.getAccountData('), + ('self.account.getAccountInfo(self.user)', 'self.account.getAccountData()'), + ('self.account.accounts[self.user]["password"]', 'self.account.password'), + ("self.account.accounts[self.user]['password']", 'self.account.password'), + ('from module.', 'from pyload.') # This should be always the last one +] + @task @needs('cog') def html(): """Build html documentation""" - module = path("docs") / "module" + module = path("docs") / "pyload" module.rmtree() call_task('paver.doctools.html') @task @cmdopts([ - ('src=', 's', 'Url to source'), - ('rev=', 'r', "HG revision"), - ("clean", 'c', 'Delete old source folder') + ('path=', 'p', 'Thrift path'), ]) -def get_source(options): - """ Downloads pyload source from bitbucket tip or given rev""" - if options.rev: options.url = "https://bitbucket.org/spoob/pyload/get/%s.zip" % options.rev +def apitypes(options): + """ Generate data types stubs """ - pyload = path("pyload") + outdir = PROJECT_DIR / "pyload" / "remote" - if len(pyload.listdir()) and not options.clean: - return - elif pyload.exists(): - pyload.rmtree() + if (outdir / "gen-py").exists(): + (outdir / "gen-py").rmtree() - urlretrieve(options.src, "pyload_src.zip") - zip = ZipFile("pyload_src.zip") - zip.extractall() - path("pyload_src.zip").remove() + cmd = [options.apitypes.path, "-strict", "-o", outdir, "--gen", "py:slots,dynamic", outdir / "pyload.thrift"] - folder = [x for x in path(".").dirs() if x.name.startswith("spoob-pyload-")][0] - folder.move(pyload) + print "running", cmd - change_mode(pyload, 0644) - change_mode(pyload, 0755, folder=True) + p = Popen(cmd) + p.communicate() - for file in pyload.files(): - if file.name.endswith(".py"): - file.chmod(0755) + (outdir / "thriftgen").rmtree() + (outdir / "gen-py").move(outdir / "thriftgen") - (pyload / ".hgtags").remove() - (pyload / ".hgignore").remove() - #(pyload / "docs").rmtree() + #create light ttypes + from pyload.remote.create_apitypes import main - f = open(pyload / "__init__.py", "wb") - f.close() + main() + from pyload.remote.create_jstypes import main - #options.setup.packages = find_packages() - #options.setup.package_data = find_package_data() + main() @task -@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist') -def sdist(): - """ Build source code package with distutils """ +def webapp(): + """ Builds the pyload web app. Nodejs and npm must be installed """ + + os.chdir(PROJECT_DIR / "pyload" / "web") + + # Preserve exit codes + ret = call(["npm", "install", "--no-color"]) + if ret: + exit(ret) + ret = call(["bower", "install", "--no-color"]) + if ret: + exit(ret) + ret = call(["bower", "update", "--no-color"]) + if ret: + exit(ret) + ret = call(["grunt", "--no-color"]) + if ret: + exit(ret) @task -@cmdopts([ - ('path=', 'p', 'Thrift path'), - ('gen=', 'g', "Extra --gen option") -]) -def thrift(options): - """ Generate Thrift stubs """ - - print "add import for TApplicationException manually as long it is not fixed" - - outdir = path("module") / "remote" / "thriftbackend" - (outdir / "gen-py").rmtree() +def generate_locale(): + """ Generates localisation files """ - cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots,dynamic", outdir / "pyload.thrift"] + EXCLUDE = ["pyload/lib", "pyload/cli", "pyload/setup", "pyload/plugins", "Setup.py"] - if options.gen: - cmd.insert(len(cmd) - 1, "--gen") - cmd.insert(len(cmd) - 1, options.gen) + makepot("core", path("pyload"), EXCLUDE) + makepot("plugins", path("pyload") / "plugins") + makepot("setup", "", [], includes="./pyload/setup/Setup.py\n") + makepot("cli", path("pyload") / "cli", []) + makepot("webUI", path("pyload") / "web" / "app", ["components", "vendor", "gettext"], endings=[".js", ".html"], + xxargs="--language=Python --force-po".split(" ")) - print "running", cmd + makehtml("webUI", path("pyload") / "web" / "app" / "templates") - p = Popen(cmd) - p.communicate() + path("includes.txt").remove() - (outdir / "thriftgen").rmtree() - (outdir / "gen-py").move(outdir / "thriftgen") + print "Locale generated" - #create light ttypes - from module.remote.socketbackend.create_ttypes import main - main() @task -def compile_js(): - """ Compile .coffee files to javascript""" - - root = path("module") / "web" / "media" / "js" - for f in root.glob("*.coffee"): - print "generate", f - coffee = Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE) - yui = Popen(["yuicompressor", "--type", "js"], stdin=coffee.stdout, stdout=PIPE) - coffee.stdout.close() - content = yui.communicate()[0] - with open(root / f.name.replace(".coffee", ".js"), "wb") as js: - js.write("{% autoescape true %}\n") - js.write(content) - js.write("\n{% endautoescape %}") +@cmdopts([ + ('key=', 'k', 'api key') +]) +def upload_translations(options): + """ Uploads the locale files to translation server """ + tmp = path(mkdtemp()) + + shutil.copy('locale/crowdin.yaml', tmp) + os.mkdir(tmp / 'pyLoad') + for f in glob('locale/*.pot'): + if os.path.isfile(f): + shutil.copy(f, tmp / 'pyLoad') + + config = tmp / 'crowdin.yaml' + content = open(config, 'rb').read() + content = content.format(key=options.key, tmp=tmp) + f = open(config, 'wb') + f.write(content) + f.close() + call(['crowdin-cli', '-c', config, 'upload', 'source']) -@task -def generate_locale(): - """ Generates localisation files """ + shutil.rmtree(tmp) - EXCLUDE = ["BeautifulSoup.py", "module/gui", "module/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload", - "setup.py"] - makepot("core", path("module"), EXCLUDE, "./pyLoadCore.py\n") + print "Translations uploaded" - makepot("gui", path("module") / "gui", [], includes="./pyLoadGui.py\n") - makepot("cli", path("module") / "cli", [], includes="./pyLoadCli.py\n") - makepot("setup", "", [], includes="./module/setup.py\n") - EXCLUDE = ["ServerThread.py", "web/media/default"] - - # strings from js files - strings = set() +@task +@cmdopts([ + ('key=', 'k', 'api key') +]) +def download_translations(options): + """ Downloads the translated files from translation server """ + tmp = path(mkdtemp()) + + shutil.copy('locale/crowdin.yaml', tmp) + os.mkdir(tmp / 'pyLoad') + for f in glob('locale/*.pot'): + if os.path.isfile(f): + shutil.copy(f, tmp / 'pyLoad') + + config = tmp / 'crowdin.yaml' + content = open(config, 'rb').read() + content = content.format(key=options.key, tmp=tmp) + f = open(config, 'wb') + f.write(content) + f.close() - for fi in path("module/web").walkfiles(): - if not fi.name.endswith(".js") and not fi.endswith(".coffee"): continue - with open(fi, "rb") as c: - content = c.read() + call(['crowdin-cli', '-c', config, 'download']) - strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content)) - strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content)) + for language in (tmp / 'pyLoad').listdir(): + if not language.isdir(): + continue - trans = path("module") / "web" / "translations.js" + target = path('locale') / language.basename() + print "Copy language %s" % target + if target.exists(): + shutil.rmtree(target) - with open(trans, "wb") as js: - for s in strings: - js.write('_("%s")\n' % s) + shutil.copytree(language, target) - makepot("django", path("module/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"]) + shutil.rmtree(tmp) - trans.remove() - path("includes.txt").remove() +@task +def compile_translations(): + """ Compile PO files to MO """ + for language in path('locale').listdir(): + if not language.isdir(): + continue - print "Locale generated" + for f in glob(language / 'LC_MESSAGES' / '*.po'): + print "Compiling %s" % f + call(['msgfmt', '-o', f.replace('.po', '.mo'), f]) @task def tests(): - call(["nosetests2"]) + """ Run complete test suite """ + call(["tests/run_pyload.sh"]) + call(["tests/nosetests.sh"]) + call(["tests/quit_pyload.sh"]) + @task +@cmdopts([ + ('virtual=', 'v', 'virtualenv path'), + ('python=', 'p', 'python path') +]) def virtualenv(options): """Setup virtual environment""" if path(options.dir).exists(): @@ -263,28 +270,41 @@ def clean_env(): @task -@needs('generate_setup', 'minilib', 'get_source', 'virtualenv') -def env_install(): - """Install pyLoad into the virtualenv""" - venv = options.virtualenv - call([path(venv.dir) / "bin" / "easy_install", "."]) - - -@task def clean(): """Cleans build directories""" path("build").rmtree() path("dist").rmtree() +@task +def replace_module_imports(): + """Replace imports from stable syntax to master""" + for root, dirnames, filenames in os.walk('pyload/plugins'): + for filename in fnmatch.filter(filenames, '*.py'): + path = os.path.join(root, filename) + f = open(path, 'r') + content = f.read() + f.close() + for rule in module_replace: + content = content.replace(rule[0], rule[1]) + if '/addons/' in path: + content = content.replace('(Hook):', '(Addon):') + elif '/accounts/' in path: + content = content.replace('self.accounts[user]["password"]', 'self.password') + content = content.replace("self.accounts[user]['password']", 'self.password') + f = open(path, 'w') + f.write(content) + f.close() + + #helper functions -def walk_trans(path, EXCLUDE, endings=[".py"]): +def walk_trans(path, excludes, endings=[".py"]): result = "" for f in path.walkfiles(): - if [True for x in EXCLUDE if x in f.dirname().relpath()]: continue - if f.name in EXCLUDE: continue + if [True for x in excludes if x in f.dirname().relpath()]: continue + if f.name in excludes: continue for e in endings: if f.name.endswith(e): @@ -318,15 +338,62 @@ def makepot(domain, p, excludes=[], includes="", endings=[".py"], xxargs=[]): with open("locale/%s.pot" % domain, "wb") as f: f.write(content) +def makehtml(domain, p): + """ Parses entries from html and append them to existing pot file""" + + pot = path("locale") / "%s.pot" % domain + + with open(pot, 'rb') as f: + content = f.readlines() + + msgids = {} + # parse existing ids and line + for i, line in enumerate(content): + if line.startswith("msgid "): + msgid = line[6:-1].strip('"') + msgids[msgid] = i + + # TODO: parses only n=2 plural + single = re.compile(r'\{\{ ?(?:gettext|_) "((?:\\.|[^"\\])*)" ?\}\}') + plural = re.compile(r'\{\{ ?(?:ngettext) *"((?:\\.|[^"\\])*)" *"((?:\\.|[^"\\])*)"') + + for f in p.walkfiles(): + if not f.endswith("html"): continue + with open(f, "rb") as html: + for i, line in enumerate(html.readlines()): + key = None + nmessage = plural.search(line) + message = single.search(line) + if nmessage: + key = nmessage.group(1) + keyp = nmessage.group(2) + + if key not in msgids: + content.append("\n") + content.append('msgid "%s"\n' % key) + content.append('msgid_plural "%s"\n' % keyp) + content.append('msgstr[0] ""\n') + content.append('msgstr[1] ""\n') + msgids[key] = len(content) - 4 + + + elif message: + key = message.group(1) + + if key not in msgids: + content.append("\n") + content.append('msgid "%s"\n' % key) + content.append('msgstr ""\n') + msgids[key] = len(content) - 2 + + if key: + content.insert(msgids[key], "#: %s:%d\n" % (f, i)) + msgids[key] += 1 + + + with open(pot, 'wb') as f: + f.writelines(content) -def change_owner(dir, uid, gid): - for p in dir.walk(): - p.chown(uid, gid) + print "Parsed html files" -def change_mode(dir, mode, folder=False): - for p in dir.walk(): - if folder and p.isdir(): - p.chmod(mode) - elif p.isfile() and not folder: - p.chmod(mode) diff --git a/pyLoadCli.py b/pyLoadCli.py deleted file mode 100755 index 079cee19c..000000000 --- a/pyLoadCli.py +++ /dev/null @@ -1,590 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -#Copyright (C) 2011 RaNaN -# -#This program is free software; you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation; either version 3 of the License, -#or (at your option) any later version. -# -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -#See the GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. -# -### -from __future__ import with_statement -from getopt import GetoptError, getopt - -import module.common.pylgettext as gettext -import os -from os import _exit -from os.path import join, exists, abspath, basename -import sys -from sys import exit -from threading import Thread, Lock -from time import sleep -from traceback import print_exc - -import ConfigParser - -from codecs import getwriter - -if os.name == "nt": - enc = "cp850" -else: - enc = "utf8" - -sys.stdout = getwriter(enc)(sys.stdout, errors="replace") - -from module import InitHomeDir -from module.cli.printer import * -from module.cli import AddPackage, ManageFiles - -from module.Api import Destination -from module.utils import formatSize, decode -from module.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed -from module.lib.Getch import Getch -from module.lib.rename_process import renameProcess - -class Cli: - def __init__(self, client, command): - self.client = client - self.command = command - - if not self.command: - renameProcess('pyLoadCli') - self.getch = Getch() - self.input = "" - self.inputline = 0 - self.lastLowestLine = 0 - self.menuline = 0 - - self.lock = Lock() - - #processor funcions, these will be changed dynamically depending on control flow - self.headerHandler = self #the download status - self.bodyHandler = self #the menu section - self.inputHandler = self - - os.system("clear") - println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - println(2, "") - - self.thread = RefreshThread(self) - self.thread.start() - - self.start() - else: - self.processCommand() - - def reset(self): - """ reset to initial main menu """ - self.input = "" - self.headerHandler = self.bodyHandler = self.inputHandler = self - - def start(self): - """ main loop. handle input """ - while True: - #inp = raw_input() - inp = self.getch.impl() - if ord(inp) == 3: - os.system("clear") - sys.exit() # ctrl + c - elif ord(inp) == 13: #enter - try: - self.lock.acquire() - self.inputHandler.onEnter(self.input) - - except Exception, e: - println(2, red(e)) - finally: - self.lock.release() - - elif ord(inp) == 127: - self.input = self.input[:-1] #backspace - try: - self.lock.acquire() - self.inputHandler.onBackSpace() - finally: - self.lock.release() - - elif ord(inp) == 27: #ugly symbol - pass - else: - self.input += inp - try: - self.lock.acquire() - self.inputHandler.onChar(inp) - finally: - self.lock.release() - - self.inputline = self.bodyHandler.renderBody(self.menuline) - self.renderFooter(self.inputline) - - - def refresh(self): - """refresh screen""" - - println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - println(2, "") - - self.lock.acquire() - - self.menuline = self.headerHandler.renderHeader(3) + 1 - println(self.menuline - 1, "") - self.inputline = self.bodyHandler.renderBody(self.menuline) - self.renderFooter(self.inputline) - - self.lock.release() - - - def setInput(self, string=""): - self.input = string - - def setHandler(self, klass): - #create new handler with reference to cli - self.bodyHandler = self.inputHandler = klass(self) - self.input = "" - - def renderHeader(self, line): - """ prints download status """ - #print updated information - # print "\033[J" #clear screen - # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - # self.println(2, "") - # self.println(3, white(_("%s Downloads:") % (len(data)))) - - data = self.client.statusDownloads() - speed = 0 - - println(line, white(_("%s Downloads:") % (len(data)))) - line += 1 - - for download in data: - if download.status == 12: # downloading - percent = download.percent - z = percent / 4 - speed += download.speed - println(line, cyan(download.name)) - line += 1 - println(line, - blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _( - " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green( - download.format_size) + _(" Finished in: ") + green(download.format_eta) + _( - " ID: ") + green(download.fid)) - line += 1 - if download.status == 5: - println(line, cyan(download.name)) - line += 1 - println(line, _("waiting: ") + green(download.format_wait)) - line += 1 - - println(line, "") - line += 1 - status = self.client.statusServer() - if status.pause: - paused = _("Status:") + " " + red(_("paused")) - else: - paused = _("Status:") + " " + red(_("running")) - - println(line,"%s %s: %s %s: %s %s: %s" % ( - paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red( - status.queue), _("Total"), red(status.total))) - - return line + 1 - - def renderBody(self, line): - """ prints initial menu """ - println(line, white(_("Menu:"))) - println(line + 1, "") - println(line + 2, mag("1.") + _(" Add Links")) - println(line + 3, mag("2.") + _(" Manage Queue")) - println(line + 4, mag("3.") + _(" Manage Collector")) - println(line + 5, mag("4.") + _(" (Un)Pause Server")) - println(line + 6, mag("5.") + _(" Kill Server")) - println(line + 7, mag("6.") + _(" Quit")) - - return line + 8 - - def renderFooter(self, line): - """ prints out the input line with input """ - println(line, "") - line += 1 - - println(line, white(" Input: ") + decode(self.input)) - - #clear old output - if line < self.lastLowestLine: - for i in range(line + 1, self.lastLowestLine + 1): - println(i, "") - - self.lastLowestLine = line - - #set cursor to position - print "\033[" + str(self.inputline) + ";0H" - - def onChar(self, char): - """ default no special handling for single chars """ - if char == "1": - self.setHandler(AddPackage) - elif char == "2": - self.setHandler(ManageFiles) - elif char == "3": - self.setHandler(ManageFiles) - self.bodyHandler.target = Destination.Collector - elif char == "4": - self.client.togglePause() - self.setInput() - elif char == "5": - self.client.kill() - self.client.close() - sys.exit() - elif char == "6": - os.system('clear') - sys.exit() - - def onEnter(self, inp): - pass - - def onBackSpace(self): - pass - - def processCommand(self): - command = self.command[0] - args = [] - if len(self.command) > 1: - args = self.command[1:] - - if command == "status": - files = self.client.statusDownloads() - - if not files: - print "No downloads running." - - for download in files: - if download.status == 12: # downloading - print print_status(download) - print "\tDownloading: %s @ %s/s\t %s (%s%%)" % ( - download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft), - download.percent) - elif download.status == 5: - print print_status(download) - print "\tWaiting: %s" % download.format_wait - else: - print print_status(download) - - elif command == "queue": - print_packages(self.client.getQueueData()) - - elif command == "collector": - print_packages(self.client.getCollectorData()) - - elif command == "add": - if len(args) < 2: - print _("Please use this syntax: add <Package name> <link> <link2> ...") - return - - self.client.addPackage(args[0], args[1:], Destination.Queue) - - elif command == "add_coll": - if len(args) < 2: - print _("Please use this syntax: add <Package name> <link> <link2> ...") - return - - self.client.addPackage(args[0], args[1:], Destination.Collector) - - elif command == "del_file": - self.client.deleteFiles([int(x) for x in args]) - print "Files deleted." - - elif command == "del_package": - self.client.deletePackages([int(x) for x in args]) - print "Packages deleted." - - elif command == "move": - for pid in args: - pack = self.client.getPackageInfo(int(pid)) - self.client.movePackage((pack.dest + 1) % 2, pack.pid) - - elif command == "check": - print _("Checking %d links:") % len(args) - print - rid = self.client.checkOnlineStatus(args).rid - self.printOnlineCheck(self.client, rid) - - - elif command == "check_container": - path = args[0] - if not exists(join(owd, path)): - print _("File does not exists.") - return - - f = open(join(owd, path), "rb") - content = f.read() - f.close() - - rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid - self.printOnlineCheck(self.client, rid) - - - elif command == "pause": - self.client.pause() - - elif command == "unpause": - self.client.unpause() - - elif command == "toggle": - self.client.togglePause() - - elif command == "kill": - self.client.kill() - elif command == "restart_file": - for x in args: - self.client.restartFile(int(x)) - print "Files restarted." - elif command == "restart_package": - for pid in args: - self.client.restartPackage(int(pid)) - print "Packages restarted." - - else: - print_commands() - - def printOnlineCheck(self, client, rid): - while True: - sleep(1) - result = client.pollResults(rid) - for url, status in result.data.iteritems(): - if status.status == 2: check = "Online" - elif status.status == 1: check = "Offline" - else: check = "Unknown" - - print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check) - - if result.rid == -1: break - - -class RefreshThread(Thread): - def __init__(self, cli): - Thread.__init__(self) - self.setDaemon(True) - self.cli = cli - - def run(self): - while True: - sleep(1) - try: - self.cli.refresh() - except ConnectionClosed: - os.system("clear") - print _("pyLoad was terminated") - _exit(0) - except Exception, e: - println(2, red(str(e))) - self.cli.reset() - print_exc() - - -def print_help(config): - print - print "pyLoadCli Copyright (c) 2008-2011 the pyLoad Team" - print - print "Usage: [python] pyLoadCli.py [options] [command]" - print - print "<Commands>" - print "See pyLoadCli.py -c for a complete listing." - print - print "<Options>" - print " -i, --interactive", " Start in interactive mode" - print - print " -u, --username=", " " * 2, "Specify Username" - print " --pw=<password>", " " * 2, "Password" - print " -a, --address=", " " * 3, "Specify address (current=%s)" % config["addr"] - print " -p, --port", " " * 7, "Specify port (current=%s)" % config["port"] - print - print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"] - print " -h, --help", " " * 7, "Display this help screen" - print " -c, --commands", " " * 3, "List all available commands" - print - - -def print_packages(data): - for pack in data: - print "Package %s (#%s):" % (pack.name, pack.pid) - for download in pack.links: - print "\t" + print_file(download) - print - - -def print_file(download): - return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % { - "id": download.fid, - "name": download.name, - "statusmsg": download.statusmsg, - "plugin": download.plugin - } - - -def print_status(download): - return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % { - "id": download.fid, - "name": download.name, - "statusmsg": download.statusmsg, - "size": download.format_size - } - - -def print_commands(): - commands = [("status", _("Prints server status")), - ("queue", _("Prints downloads in queue")), - ("collector", _("Prints downloads in collector")), - ("add <name> <link1> <link2>...", _("Adds package to queue")), - ("add_coll <name> <link1> <link2>...", _("Adds package to collector")), - ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")), - ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")), - ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")), - ("restart_file <fid> <fid2>...", _("Restart files")), - ("restart_package <pid> <pid2>...", _("Restart packages")), - ("check <container|url> ...", _("Check online status, works with local container")), - ("check_container path", _("Checks online status of a container file")), - ("pause", _("Pause the server")), - ("unpause", _("continue downloads")), - ("toggle", _("Toggle pause/unpause")), - ("kill", _("kill server")), ] - - print _("List of commands:") - print - for c in commands: - print "%-35s %s" % c - - -def writeConfig(opts): - try: - with open(join(homedir, ".pyloadcli"), "w") as cfgfile: - cfgfile.write("[cli]") - for opt in opts: - cfgfile.write("%s=%s\n" % (opt, opts[opt])) - except: - print _("Couldn't write user config file") - - -def main(): - config = {"addr": "127.0.0.1", "port": "7227", "language": "en"} - try: - config["language"] = os.environ["LANG"][0:2] - except: - pass - - if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "": - config["language"] = "en" - - configFile = ConfigParser.ConfigParser() - configFile.read(join(homedir, ".pyloadcli")) - - if configFile.has_section("cli"): - for opt in configFile.items("cli"): - config[opt[0]] = opt[1] - - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoadCli", join(pypath, "locale"), - languages=[config["language"],"en"],fallback=True) - translation.install(unicode=True) - - interactive = False - command = None - username = "" - password = "" - - shortOptions = 'iu:p:a:hcl:' - longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="] - - try: - opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions) - for option, params in opts: - if option in ("-i", "--interactive"): - interactive = True - elif option in ("-u", "--username"): - username = params - elif option in ("-a", "--address"): - config["addr"] = params - elif option in ("-p", "--port"): - config["port"] = params - elif option in ("-l", "--language"): - config["language"] = params - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoadCli", join(pypath, "locale"), - languages=[config["language"],"en"],fallback=True) - translation.install(unicode=True) - elif option in ("-h", "--help"): - print_help(config) - exit() - elif option in ("--pw"): - password = params - elif option in ("-c", "--comands"): - print_commands() - exit() - - except GetoptError: - print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:]) - print_help(config) - exit() - - if len(extraparams) >= 1: - command = extraparams - - client = False - - if interactive: - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - pass - except NoSSL: - print _("You need py-openssl to connect to this pyLoad Core.") - exit() - except NoConnection: - config["addr"] = False - config["port"] = False - - if not client: - if not config["addr"]: config["addr"] = raw_input(_("Address: ")) - if not config["port"]: config["port"] = raw_input(_("Port: ")) - if not username: username = raw_input(_("Username: ")) - if not password: - from getpass import getpass - - password = getpass(_("Password: ")) - - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - print _("Login data is wrong.") - except NoConnection: - print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], - "port": config["port"]}) - - else: - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - print _("Login data is wrong.") - except NoConnection: - print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], - "port": config["port"]}) - except NoSSL: - print _("You need py-openssl to connect to this pyLoad core.") - - if interactive and command: print _("Interactive mode ignored since you passed some commands.") - - if client: - writeConfig(config) - cli = Cli(client, command) - - -if __name__ == "__main__": - main() diff --git a/pyLoadCore.py b/pyLoadCore.py deleted file mode 100755 index 35cac4682..000000000 --- a/pyLoadCore.py +++ /dev/null @@ -1,668 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: spoob - @author: sebnapi - @author: RaNaN - @author: mkaay - @version: v0.4.9 -""" -CURRENT_VERSION = '0.4.9' - -import __builtin__ - -from getopt import getopt, GetoptError -import module.common.pylgettext as gettext -from imp import find_module -import logging -import logging.handlers -import os -from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close -from os.path import exists, join -import signal -import subprocess -import sys -from sys import argv, executable, exit -from time import time, sleep -from traceback import print_exc - -from module import InitHomeDir -from module.plugins.AccountManager import AccountManager -from module.CaptchaManager import CaptchaManager -from module.ConfigParser import ConfigParser -from module.plugins.PluginManager import PluginManager -from module.PullEvents import PullManager -from module.network.RequestFactory import RequestFactory -from module.web.ServerThread import WebServer -from module.Scheduler import Scheduler -from module.common.JsEngine import JsEngine -from module import remote -from module.remote.RemoteManager import RemoteManager -from module.database import DatabaseBackend, FileHandler - -from module.utils import freeSpace, formatSize, get_console_encoding - -from codecs import getwriter - -enc = get_console_encoding(sys.stdout.encoding) -sys.stdout = getwriter(enc)(sys.stdout, errors="replace") - -# TODO List -# - configurable auth system ldap/mysql -# - cron job like sheduler - -class Core(object): - """pyLoad Core, one tool to rule them all... (the filehosters) :D""" - - def __init__(self): - self.doDebug = False - self.startedInGui = False - self.running = False - self.daemon = False - self.remote = True - self.arg_links = [] - self.pidfile = "pyload.pid" - self.deleteLinks = False # will delete links on startup - - if len(argv) > 1: - try: - options, args = getopt(argv[1:], 'vchdusqp:', - ["version", "clear", "clean", "help", "debug", "user", - "setup", "configdir=", "changedir", "daemon", - "quit", "status", "no-remote","pidfile="]) - - for option, argument in options: - if option in ("-v", "--version"): - print "pyLoad", CURRENT_VERSION - exit() - elif option in ("-p", "--pidfile"): - self.pidfile = argument - elif option == "--daemon": - self.daemon = True - elif option in ("-c", "--clear"): - self.deleteLinks = True - elif option in ("-h", "--help"): - self.print_help() - exit() - elif option in ("-d", "--debug"): - self.doDebug = True - elif option in ("-u", "--user"): - from module.setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.set_user() - exit() - elif option in ("-s", "--setup"): - from module.setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.start() - exit() - elif option == "--changedir": - from module.setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.conf_path(True) - exit() - elif option in ("-q", "--quit"): - self.quitInstance() - exit() - elif option == "--status": - pid = self.isAlreadyRunning() - if self.isAlreadyRunning(): - print pid - exit(0) - else: - print "false" - exit(1) - elif option == "--clean": - self.cleanTree() - exit() - elif option == "--no-remote": - self.remote = False - - except GetoptError: - print 'Unknown Argument(s) "%s"' % " ".join(argv[1:]) - self.print_help() - exit() - - def print_help(self): - print "" - print "pyLoad v%s 2008-2011 the pyLoad Team" % CURRENT_VERSION - print "" - if sys.argv[0].endswith(".py"): - print "Usage: python pyLoadCore.py [options]" - else: - print "Usage: pyLoadCore [options]" - print "" - print "<Options>" - print " -v, --version", " " * 10, "Print version to terminal" - print " -c, --clear", " " * 12, "Delete all saved packages/links" - #print " -a, --add=<link/list>", " " * 2, "Add the specified links" - print " -u, --user", " " * 13, "Manages users" - print " -d, --debug", " " * 12, "Enable debug mode" - print " -s, --setup", " " * 12, "Run Setup Assistent" - print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory" - print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>" - print " --changedir", " " * 12, "Change config dir permanently" - print " --daemon", " " * 15, "Daemonmize after start" - print " --no-remote", " " * 12, "Disable remote access (saves RAM)" - print " --status", " " * 15, "Display pid if running or False" - print " --clean", " " * 16, "Remove .pyc/.pyo files" - print " -q, --quit", " " * 13, "Quit running pyLoad instance" - print " -h, --help", " " * 13, "Display this help screen" - print "" - - def toggle_pause(self): - if self.threadManager.pause: - self.threadManager.pause = False - return False - elif not self.threadManager.pause: - self.threadManager.pause = True - return True - - def quit(self, a, b): - self.shutdown() - self.log.info(_("Received Quit signal")) - _exit(1) - - def writePidFile(self): - self.deletePidFile() - pid = os.getpid() - f = open(self.pidfile, "wb") - f.write(str(pid)) - f.close() - - def deletePidFile(self): - if self.checkPidFile(): - self.log.debug("Deleting old pidfile %s" % self.pidfile) - os.remove(self.pidfile) - - def checkPidFile(self): - """ return pid as int or 0""" - if os.path.isfile(self.pidfile): - f = open(self.pidfile, "rb") - pid = f.read().strip() - f.close() - if pid: - pid = int(pid) - return pid - - return 0 - - def isAlreadyRunning(self): - pid = self.checkPidFile() - if not pid or os.name == "nt": return False - try: - os.kill(pid, 0) # 0 - default signal (does nothing) - except: - return 0 - - return pid - - def quitInstance(self): - if os.name == "nt": - print "Not supported on windows." - return - - pid = self.isAlreadyRunning() - if not pid: - print "No pyLoad running." - return - - try: - os.kill(pid, 3) #SIGUIT - - t = time() - print "waiting for pyLoad to quit" - - while exists(self.pidfile) and t + 10 > time(): - sleep(0.25) - - if not exists(self.pidfile): - print "pyLoad successfully stopped" - else: - os.kill(pid, 9) #SIGKILL - print "pyLoad did not respond" - print "Kill signal was send to process with id %s" % pid - - except: - print "Error quitting pyLoad" - - - def cleanTree(self): - for path, dirs, files in walk(self.path("")): - for f in files: - if not f.endswith(".pyo") and not f.endswith(".pyc"): - continue - - if "_25" in f or "_26" in f or "_27" in f: - continue - - print join(path, f) - remove(join(path, f)) - - def start(self, rpc=True, web=True): - """ starts the fun :D """ - - self.version = CURRENT_VERSION - - if not exists("pyload.conf"): - from module.setup import Setup - - print "This is your first start, running configuration assistent now." - self.config = ConfigParser() - s = Setup(pypath, self.config) - res = False - try: - res = s.start() - except SystemExit: - pass - except KeyboardInterrupt: - print "\nSetup interrupted" - except: - res = False - print_exc() - print "Setup failed" - if not res: - remove("pyload.conf") - - exit() - - try: signal.signal(signal.SIGQUIT, self.quit) - except: pass - - self.config = ConfigParser() - - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoad", self.path("locale"), - languages=[self.config['general']['language'],"en"],fallback=True) - translation.install(True) - - self.debug = self.doDebug or self.config['general']['debug_mode'] - self.remote &= self.config['remote']['activated'] - - pid = self.isAlreadyRunning() - if pid: - print _("pyLoad already running with pid %s") % pid - exit() - - if os.name != "nt" and self.config["general"]["renice"]: - os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid())) - - if self.config["permission"]["change_group"]: - if os.name != "nt": - try: - from grp import getgrnam - - group = getgrnam(self.config["permission"]["group"]) - os.setgid(group[2]) - except Exception, e: - print _("Failed changing group: %s") % e - - if self.config["permission"]["change_user"]: - if os.name != "nt": - try: - from pwd import getpwnam - - user = getpwnam(self.config["permission"]["user"]) - os.setuid(user[2]) - except Exception, e: - print _("Failed changing user: %s") % e - - self.check_file(self.config['log']['log_folder'], _("folder for logs"), True) - - if self.debug: - self.init_logger(logging.DEBUG) # logging level - else: - self.init_logger(logging.INFO) # logging level - - self.do_kill = False - self.do_restart = False - self.shuttedDown = False - - self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION) - self.log.info(_("Using home directory: %s") % getcwd()) - - self.writePidFile() - - #@TODO refractor - - remote.activated = self.remote - self.log.debug("Remote activated: %s" % self.remote) - - self.check_install("Crypto", _("pycrypto to decode container files")) - #img = self.check_install("Image", _("Python Image Libary (PIL) for captcha reading")) - #self.check_install("pycurl", _("pycurl to download any files"), True, True) - self.check_file("tmp", _("folder for temporary files"), True) - #tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True - - self.captcha = True # checks seems to fail, althoug tesseract is available - - self.check_file(self.config['general']['download_folder'], _("folder for downloads"), True) - - if self.config['ssl']['activated']: - self.check_install("OpenSSL", _("OpenSSL for secure connection")) - - self.setupDB() - if self.config.oldRemoteData: - self.log.info(_("Moving old user config to DB")) - self.db.addUser(self.config.oldRemoteData["username"], self.config.oldRemoteData["password"]) - - self.log.info(_("Please check your logindata with ./pyLoadCore.py -u")) - - if self.deleteLinks: - self.log.info(_("All links removed")) - self.db.purgeLinks() - - self.requestFactory = RequestFactory(self) - __builtin__.pyreq = self.requestFactory - - self.lastClientConnected = 0 - - # later imported because they would trigger api import, and remote value not set correctly - from module import Api - from module.HookManager import HookManager - from module.ThreadManager import ThreadManager - - if Api.activated != self.remote: - self.log.warning("Import error: API remote status not correct.") - - self.api = Api.Api(self) - - self.scheduler = Scheduler(self) - - #hell yeah, so many important managers :D - self.pluginManager = PluginManager(self) - self.pullManager = PullManager(self) - self.accountManager = AccountManager(self) - self.threadManager = ThreadManager(self) - self.captchaManager = CaptchaManager(self) - self.hookManager = HookManager(self) - self.remoteManager = RemoteManager(self) - - self.js = JsEngine() - - self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload()) - - if rpc: - self.remoteManager.startBackends() - - if web: - self.init_webserver() - - spaceLeft = freeSpace(self.config["general"]["download_folder"]) - - self.log.info(_("Free space: %s") % formatSize(spaceLeft)) - - self.config.save() #save so config files gets filled - - link_file = join(pypath, "links.txt") - - if exists(link_file): - f = open(link_file, "rb") - if f.read().strip(): - self.api.addPackage("links.txt", [link_file], 1) - f.close() - - link_file = "links.txt" - if exists(link_file): - f = open(link_file, "rb") - if f.read().strip(): - self.api.addPackage("links.txt", [link_file], 1) - f.close() - - #self.scheduler.addJob(0, self.accountManager.getAccountInfos) - self.log.info(_("Activating Accounts...")) - self.accountManager.getAccountInfos() - - self.threadManager.pause = False - self.running = True - - self.log.info(_("Activating Plugins...")) - self.hookManager.coreReady() - - self.log.info(_("pyLoad is up and running")) - - #test api -# from module.common.APIExerciser import startApiExerciser -# startApiExerciser(self, 3) - - #some memory stats -# from guppy import hpy -# hp=hpy() -# import objgraph -# objgraph.show_most_common_types(limit=20) -# import memdebug -# memdebug.start(8002) - - locals().clear() - - while True: - sleep(2) - if self.do_restart: - self.log.info(_("restarting pyLoad")) - self.restart() - if self.do_kill: - self.shutdown() - self.log.info(_("pyLoad quits")) - self.removeLogger() - _exit(0) #@TODO thrift blocks shutdown - - self.threadManager.work() - self.scheduler.work() - - def setupDB(self): - self.db = DatabaseBackend(self) # the backend - self.db.setup() - - self.files = FileHandler(self) - self.db.manager = self.files #ugly? - - def init_webserver(self): - if self.config['webinterface']['activated']: - self.webserver = WebServer(self) - self.webserver.start() - - def init_logger(self, level): - console = logging.StreamHandler(sys.stdout) - frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S") - console.setFormatter(frm) - self.log = logging.getLogger("log") # settable in config - - if self.config['log']['file_log']: - if self.config['log']['log_rotate']: - file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'), - maxBytes=self.config['log']['log_size'] * 1024, - backupCount=int(self.config['log']['log_count']), - encoding="utf8") - else: - file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8") - - file_handler.setFormatter(frm) - self.log.addHandler(file_handler) - - self.log.addHandler(console) #if console logging - self.log.setLevel(level) - - def removeLogger(self): - for h in list(self.log.handlers): - self.log.removeHandler(h) - h.close() - - def check_install(self, check_name, legend, python=True, essential=False): - """check wether needed tools are installed""" - try: - if python: - find_module(check_name) - else: - pipe = subprocess.PIPE - subprocess.Popen(check_name, stdout=pipe, stderr=pipe) - - return True - except: - if essential: - self.log.info(_("Install %s") % legend) - exit() - - return False - - def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False): - """check wether needed files exists""" - tmp_names = [] - if not type(check_names) == list: - tmp_names.append(check_names) - else: - tmp_names.extend(check_names) - file_created = True - file_exists = True - for tmp_name in tmp_names: - if not exists(tmp_name): - file_exists = False - if empty: - try: - if folder: - tmp_name = tmp_name.replace("/", sep) - makedirs(tmp_name) - else: - open(tmp_name, "w") - except: - file_created = False - else: - file_created = False - - if not file_exists and not quiet: - if file_created: - #self.log.info( _("%s created") % description ) - pass - else: - if not empty: - self.log.warning( - _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}) - else: - print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name} - if essential: - exit() - - def isClientConnected(self): - return (self.lastClientConnected + 30) > time() - - def restart(self): - self.shutdown() - chdir(owd) - # close some open fds - for i in range(3,50): - try: - close(i) - except : - pass - - execl(executable, executable, *sys.argv) - _exit(0) - - def shutdown(self): - self.log.info(_("shutting down...")) - try: - if self.config['webinterface']['activated'] and hasattr(self, "webserver"): - self.webserver.quit() - - for thread in self.threadManager.threads: - thread.put("quit") - pyfiles = self.files.cache.values() - - for pyfile in pyfiles: - pyfile.abortDownload() - - self.hookManager.coreExiting() - - except: - if self.debug: - print_exc() - self.log.info(_("error while shutting down")) - - finally: - self.files.syncSave() - self.shuttedDown = True - - self.deletePidFile() - - - def path(self, *args): - return join(pypath, *args) - - -def deamon(): - try: - pid = os.fork() - if pid > 0: - sys.exit(0) - except OSError, e: - print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # decouple from parent environment - os.setsid() - os.umask(0) - - # do second fork - try: - pid = os.fork() - if pid > 0: - # exit from second parent, print eventual PID before - print "Daemon PID %d" % pid - sys.exit(0) - except OSError, e: - print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # Iterate through and close some file descriptors. - for fd in range(0, 3): - try: - os.close(fd) - except OSError: # ERROR, fd wasn't open to begin with (ignored) - pass - - os.open(os.devnull, os.O_RDWR) # standard input (0) - os.dup2(0, 1) # standard output (1) - os.dup2(0, 2) - - pyload_core = Core() - pyload_core.start() - - -def main(): - #change name to 'pyLoadCore' - #from module.lib.rename_process import renameProcess - #renameProcess('pyLoadCore') - if "--daemon" in sys.argv: - deamon() - else: - pyload_core = Core() - try: - pyload_core.start() - except KeyboardInterrupt: - pyload_core.shutdown() - pyload_core.log.info(_("killed pyLoad from Terminal")) - pyload_core.removeLogger() - _exit(1) - -# And so it begins... -if __name__ == "__main__": - main() - diff --git a/pyLoadGui.py b/pyLoadGui.py deleted file mode 100755 index 5f620e52a..000000000 --- a/pyLoadGui.py +++ /dev/null @@ -1,765 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: mkaay -""" - -import sys - -from uuid import uuid4 as uuid # should be above PyQt imports -from time import sleep, time - -from base64 import b64decode - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -import re -import module.common.pylgettext as gettext -import os -from os.path import abspath -from os.path import join -from os.path import basename -from os.path import commonprefix - -from module import InitHomeDir -from module.gui.ConnectionManager import * -from module.gui.Connector import Connector -from module.gui.MainWindow import * -from module.gui.Queue import * -from module.gui.Overview import * -from module.gui.Collector import * -from module.gui.XMLParser import * -from module.gui.CoreConfigParser import ConfigParser - -from module.lib.rename_process import renameProcess -from module.utils import formatSize, formatSpeed - -try: - import pynotify -except ImportError: - print "pynotify not installed, falling back to qt tray notification" - -class main(QObject): - def __init__(self): - """ - main setup - """ - QObject.__init__(self) - self.app = QApplication(sys.argv) - self.path = pypath - self.homedir = abspath("") - - self.configdir = "" - - self.init(True) - - def init(self, first=False): - """ - set main things up - """ - self.parser = XMLParser(join(self.configdir, "gui.xml"), join(self.path, "module", "config", "gui_default.xml")) - lang = self.parser.xml.elementsByTagName("language").item(0).toElement().text() - if not lang: - parser = XMLParser(join(self.path, "module", "config", "gui_default.xml")) - lang = parser.xml.elementsByTagName("language").item(0).toElement().text() - - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoadGui", join(pypath, "locale"), languages=[str(lang), "en"], fallback=True) - try: - translation.install(unicode=(True if sys.stdout.encoding.lower().startswith("utf") else False)) - except: - translation.install(unicode=False) - - - self.connector = Connector() - self.mainWindow = MainWindow(self.connector) - self.connWindow = ConnectionManager() - self.mainloop = self.Loop(self) - self.connectSignals() - - self.checkClipboard = False - default = self.refreshConnections() - self.connData = None - self.captchaProcessing = False - self.serverStatus = {"freespace":0} - - self.core = None # pyLoadCore if started - self.connectionLost = False - - if True: # when used if first, minimizing not working correctly.. - self.tray = TrayIcon() - self.tray.show() - self.notification = Notification(self.tray) - self.connect(self, SIGNAL("showMessage"), self.notification.showMessage) - self.connect(self.tray.exitAction, SIGNAL("triggered()"), self.slotQuit) - self.connect(self.tray.showAction, SIGNAL("toggled(bool)"), self.mainWindow.setVisible) - self.connect(self.mainWindow, SIGNAL("hidden"), self.tray.mainWindowHidden) - - if not first: - self.connWindow.show() - else: - self.connWindow.edit.setData(default) - data = self.connWindow.edit.getData() - self.slotConnect(data) - - def startMain(self): - """ - start all refresh threads and show main window - """ - if not self.connector.connectProxy(): - self.init() - return - self.connect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost) - self.restoreMainWindow() - self.mainWindow.show() - self.initQueue() - self.initPackageCollector() - self.mainloop.start() - self.clipboard = self.app.clipboard() - self.connect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange) - self.mainWindow.actions["clipboard"].setChecked(self.checkClipboard) - - self.mainWindow.tabs["settings"]["w"].setConnector(self.connector) - self.mainWindow.tabs["settings"]["w"].loadConfig() - self.tray.showAction.setDisabled(False) - - def stopMain(self): - """ - stop all refresh threads and hide main window - """ - self.tray.showAction.setDisabled(True) - self.disconnect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange) - self.disconnect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost) - self.mainloop.stop() - self.mainWindow.saveWindow() - self.mainWindow.hide() - self.queue.stop() - - def connectSignals(self): - """ - signal and slot stuff, yay! - """ - self.connect(self.connector, SIGNAL("errorBox"), self.slotErrorBox) - self.connect(self.connWindow, SIGNAL("saveConnection"), self.slotSaveConnection) - self.connect(self.connWindow, SIGNAL("removeConnection"), self.slotRemoveConnection) - self.connect(self.connWindow, SIGNAL("connect"), self.slotConnect) - self.connect(self.mainWindow, SIGNAL("connector"), self.slotShowConnector) - self.connect(self.mainWindow, SIGNAL("addPackage"), self.slotAddPackage) - self.connect(self.mainWindow, SIGNAL("setDownloadStatus"), self.slotSetDownloadStatus) - self.connect(self.mainWindow, SIGNAL("saveMainWindow"), self.slotSaveMainWindow) - self.connect(self.mainWindow, SIGNAL("pushPackageToQueue"), self.slotPushPackageToQueue) - self.connect(self.mainWindow, SIGNAL("restartDownload"), self.slotRestartDownload) - self.connect(self.mainWindow, SIGNAL("removeDownload"), self.slotRemoveDownload) - self.connect(self.mainWindow, SIGNAL("abortDownload"), self.slotAbortDownload) - self.connect(self.mainWindow, SIGNAL("addContainer"), self.slotAddContainer) - self.connect(self.mainWindow, SIGNAL("stopAllDownloads"), self.slotStopAllDownloads) - self.connect(self.mainWindow, SIGNAL("setClipboardStatus"), self.slotSetClipboardStatus) - self.connect(self.mainWindow, SIGNAL("changePackageName"), self.slotChangePackageName) - self.connect(self.mainWindow, SIGNAL("pullOutPackage"), self.slotPullOutPackage) - self.connect(self.mainWindow, SIGNAL("refreshStatus"), self.slotRefreshStatus) - self.connect(self.mainWindow, SIGNAL("reloadAccounts"), self.slotReloadAccounts) - self.connect(self.mainWindow, SIGNAL("Quit"), self.slotQuit) - - self.connect(self.mainWindow.mactions["exit"], SIGNAL("triggered()"), self.slotQuit) - self.connect(self.mainWindow.captchaDock, SIGNAL("done"), self.slotCaptchaDone) - - def slotShowConnector(self): - """ - emitted from main window (menu) - hide the main window and show connection manager - (to switch to other core) - """ - self.quitInternal() - self.stopMain() - self.init() - - #def quit(self): #not used anymore? - # """ - # quit gui - # """ - # self.app.quit() - - def loop(self): - """ - start application loop - """ - sys.exit(self.app.exec_()) - - def slotErrorBox(self, msg): - """ - display a nice error box - """ - msgb = QMessageBox(QMessageBox.Warning, "Error", msg) - msgb.exec_() - - def initPackageCollector(self): - """ - init the package collector view - * columns - * selection - * refresh thread - * drag'n'drop - """ - view = self.mainWindow.tabs["collector"]["package_view"] - view.setSelectionBehavior(QAbstractItemView.SelectRows) - view.setSelectionMode(QAbstractItemView.ExtendedSelection) - def dropEvent(klass, event): - event.setDropAction(Qt.CopyAction) - event.accept() - view = event.source() - if view == klass: - items = view.selectedItems() - for item in items: - if not hasattr(item.parent(), "getPackData"): - continue - target = view.itemAt(event.pos()) - if not hasattr(target, "getPackData"): - target = target.parent() - klass.emit(SIGNAL("droppedToPack"), target.getPackData()["id"], item.getFileData()["id"]) - event.ignore() - return - items = view.selectedItems() - for item in items: - row = view.indexOfTopLevelItem(item) - view.takeTopLevelItem(row) - def dragEvent(klass, event): - #view = event.source() - #dragOkay = False - #items = view.selectedItems() - #for item in items: - # if hasattr(item, "_data"): - # if item._data["id"] == "fixed" or item.parent()._data["id"] == "fixed": - # dragOkay = True - # else: - # dragOkay = True - #if dragOkay: - event.accept() - #else: - # event.ignore() - view.dropEvent = dropEvent - view.dragEnterEvent = dragEvent - view.setDragEnabled(True) - view.setDragDropMode(QAbstractItemView.DragDrop) - view.setDropIndicatorShown(True) - view.setDragDropOverwriteMode(True) - view.connect(view, SIGNAL("droppedToPack"), self.slotAddFileToPackage) - #self.packageCollector = PackageCollector(view, self.connector) - self.packageCollector = view.model() - - def initQueue(self): - """ - init the queue view - * columns - * progressbar - """ - view = self.mainWindow.tabs["queue"]["view"] - view.setSelectionBehavior(QAbstractItemView.SelectRows) - view.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.queue = view.model() - self.connect(self.queue, SIGNAL("updateCount"), self.slotUpdateCount) - overview = self.mainWindow.tabs["overview"]["view"].model() - overview.queue = self.queue - self.connect(self.queue, SIGNAL("updateCount"), overview.queueChanged) - self.queue.start() - - def slotUpdateCount(self, pc, fc): - self.mainWindow.packageCount.setText("%i" % pc) - self.mainWindow.fileCount.setText("%i" % fc) - - def refreshServerStatus(self): - """ - refresh server status and overall speed in the status bar - """ - s = self.connector.statusServer() - if s.pause: - self.mainWindow.status.setText(_("paused")) - else: - self.mainWindow.status.setText(_("running")) - self.mainWindow.speed.setText(formatSpeed(s.speed)) - self.mainWindow.space.setText(formatSize(self.serverStatus["freespace"])) - self.mainWindow.actions["toggle_status"].setChecked(not s.pause) - - def refreshLog(self): - """ - update log window - """ - offset = self.mainWindow.tabs["log"]["text"].logOffset - lines = self.connector.getLog(offset) - if not lines: - return - self.mainWindow.tabs["log"]["text"].logOffset += len(lines) - for line in lines: - self.mainWindow.tabs["log"]["text"].emit(SIGNAL("append(QString)"), line.strip("\n")) - cursor = self.mainWindow.tabs["log"]["text"].textCursor() - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) - self.mainWindow.tabs["log"]["text"].setTextCursor(cursor) - - def getConnections(self): - """ - parse all connections in the config file - """ - connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) - if connectionsNode.isNull(): - raise Exception("null") - connections = self.parser.parseNode(connectionsNode) - ret = [] - for conn in connections: - data = {} - data["type"] = conn.attribute("type", "remote") - data["default"] = conn.attribute("default", "False") - data["id"] = conn.attribute("id", uuid().hex) - if data["default"] == "True": - data["default"] = True - else: - data["default"] = False - subs = self.parser.parseNode(conn, "dict") - if not subs.has_key("name"): - data["name"] = _("Unnamed") - else: - data["name"] = subs["name"].text() - if data["type"] == "remote": - if not subs.has_key("server"): - continue - else: - data["host"] = subs["server"].text() - data["user"] = subs["server"].attribute("user", "admin") - data["port"] = int(subs["server"].attribute("port", "7227")) - data["password"] = subs["server"].attribute("password", "") - ret.append(data) - return ret - - def slotSaveConnection(self, data): - """ - save connection to config file - """ - connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) - if connectionsNode.isNull(): - raise Exception("null") - connections = self.parser.parseNode(connectionsNode) - connNode = self.parser.xml.createElement("connection") - connNode.setAttribute("default", str(data["default"])) - connNode.setAttribute("type", data["type"]) - connNode.setAttribute("id", data["id"]) - nameNode = self.parser.xml.createElement("name") - nameText = self.parser.xml.createTextNode(data["name"]) - nameNode.appendChild(nameText) - connNode.appendChild(nameNode) - if data["type"] == "remote": - serverNode = self.parser.xml.createElement("server") - serverNode.setAttribute("user", data["user"]) - serverNode.setAttribute("port", data["port"]) - serverNode.setAttribute("password", data["password"]) - hostText = self.parser.xml.createTextNode(data["host"]) - serverNode.appendChild(hostText) - connNode.appendChild(serverNode) - found = False - for c in connections: - cid = c.attribute("id", "None") - if str(cid) == str(data["id"]): - found = c - break - if found: - connectionsNode.replaceChild(connNode, found) - else: - connectionsNode.appendChild(connNode) - self.parser.saveData() - self.refreshConnections() - - def slotRemoveConnection(self, data): - """ - remove connection from config file - """ - connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) - if connectionsNode.isNull(): - raise Exception("null") - connections = self.parser.parseNode(connectionsNode) - found = False - for c in connections: - cid = c.attribute("id", "None") - if str(cid) == str(data["id"]): - found = c - break - if found: - connectionsNode.removeChild(found) - self.parser.saveData() - self.refreshConnections() - - def slotConnect(self, data): - """ - connect to a core - if connection is local, parse the core config file for data - if internal, start pyLoadCore - set up connector, show main window - """ - self.connWindow.hide() - if data["type"] not in ("remote","internal"): - - coreparser = ConfigParser(self.configdir) - if not coreparser.config: - self.connector.setConnectionData("127.0.0.1", 7227, "anonymous", "anonymous", False) - else: - self.connector.setConnectionData("127.0.0.1", coreparser.get("remote","port"), "anonymous", "anonymous") - - elif data["type"] == "remote": - self.connector.setConnectionData(data["host"], data["port"], data["user"], data["password"]) - - elif data["type"] == "internal": - from pyLoadCore import Core - from module.ConfigParser import ConfigParser as CoreConfig - import thread - - if not self.core: - - config = CoreConfig() #create so at least default config exists - self.core = Core() - self.core.startedInGui = True - thread.start_new_thread(self.core.start, (False, False)) - while not self.core.running: - sleep(0.5) - - self.connector.proxy = self.core.api - self.connector.internal = True - - #self.connector.setConnectionData("127.0.0.1", config.get("remote","port"), "anonymous", "anonymous") - - self.startMain() -# try: -# host = data["host"] -# except: -# host = "127.0.0.1" - - def refreshConnections(self): - """ - reload connetions and display them - """ - self.parser.loadData() - conns = self.getConnections() - self.connWindow.emit(SIGNAL("setConnections"), conns) - for conn in conns: - if conn["default"]: - return conn - return None - - def slotSetDownloadStatus(self, status): - """ - toolbar start/pause slot - """ - if status: - self.connector.unpauseServer() - else: - self.connector.pauseServer() - - def slotAddPackage(self, name, links, password=None): - """ - emitted from main window - add package to the collector - """ - pack = self.connector.addPackage(name, links, Destination.Collector) - if password: - data = {"password": password} - self.connector.setPackageData(pack, data) - - def slotAddFileToPackage(self, pid, fid): #TODO deprecated? gets called - """ - emitted from collector view after a drop action - """ - #self.connector.addFileToPackage(fid, pid) - pass - - def slotAddContainer(self, path): - """ - emitted from main window - add container - """ - filename = basename(path) - #type = "".join(filename.split(".")[-1]) - fh = open(path, "r") - content = fh.read() - fh.close() - self.connector.uploadContainer(filename, content) - - def slotSaveMainWindow(self, state, geo): - """ - save the window geometry and toolbar/dock position to config file - """ - mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0) - if mainWindowNode.isNull(): - mainWindowNode = self.parser.xml.createElement("mainWindow") - self.parser.root.appendChild(mainWindowNode) - stateNode = mainWindowNode.toElement().elementsByTagName("state").item(0) - geoNode = mainWindowNode.toElement().elementsByTagName("geometry").item(0) - newStateNode = self.parser.xml.createTextNode(state) - newGeoNode = self.parser.xml.createTextNode(geo) - - stateNode.removeChild(stateNode.firstChild()) - geoNode.removeChild(geoNode.firstChild()) - stateNode.appendChild(newStateNode) - geoNode.appendChild(newGeoNode) - - self.parser.saveData() - - def restoreMainWindow(self): - """ - load and restore main window geometry and toolbar/dock position from config - """ - mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0) - if mainWindowNode.isNull(): - return - nodes = self.parser.parseNode(mainWindowNode, "dict") - - state = str(nodes["state"].text()) - geo = str(nodes["geometry"].text()) - - self.mainWindow.restoreWindow(state, geo) - self.mainWindow.captchaDock.hide() - - def slotPushPackageToQueue(self, id): - """ - emitted from main window - push the collector package to queue - """ - self.connector.pushToQueue(id) - - def slotRestartDownload(self, id, isPack): - """ - emitted from main window - restart download - """ - if isPack: - self.connector.restartPackage(id) - else: - self.connector.restartFile(id) - - def slotRefreshStatus(self, id): - """ - emitted from main window - refresh download status - """ - self.connector.recheckPackage(id) - - def slotRemoveDownload(self, id, isPack): - """ - emitted from main window - remove download - """ - if isPack: - self.connector.deletePackages([id]) - else: - self.connector.deleteFiles([id]) - - def slotAbortDownload(self, id, isPack): - """ - emitted from main window - remove download - """ - if isPack: - data = self.connector.getFileOrder(id) #less data to transmit - self.connector.stopDownloads(data.values()) - else: - self.connector.stopDownloads([id]) - - def slotStopAllDownloads(self): - """ - emitted from main window - stop all running downloads - """ - self.connector.stopAllDownloads() - - def slotClipboardChange(self): - """ - called if clipboard changes - """ - if self.checkClipboard: - text = self.clipboard.text() - pattern = re.compile(r"(http|https|ftp)://[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?/.*)?") - matches = pattern.finditer(text) - - # thanks to: jmansour //#139 - links = [str(match.group(0)) for match in matches] - if len(links) == 0: - return - - filenames = [link.rpartition("/")[2] for link in links] - - packagename = commonprefix(filenames) - if len(packagename) == 0: - packagename = filenames[0] - - self.slotAddPackage(packagename, links) - - def slotSetClipboardStatus(self, status): - """ - set clipboard checking - """ - self.checkClipboard = status - - def slotChangePackageName(self, pid, name): - """ - package name edit finished - """ - self.connector.setPackageName(pid, str(name)) - - def slotPullOutPackage(self, pid): - """ - pull package out of the queue - """ - self.connector.pullFromQueue(pid) - - def checkCaptcha(self): - if self.connector.isCaptchaWaiting() and self.mainWindow.captchaDock.isFree(): - t = self.connector.getCaptchaTask(False) - self.mainWindow.show() - self.mainWindow.raise_() - self.mainWindow.activateWindow() - self.mainWindow.captchaDock.emit(SIGNAL("setTask"), t.tid, b64decode(t.data), t.type) - elif not self.mainWindow.captchaDock.isFree(): - status = self.connector.getCaptchaTaskStatus(self.mainWindow.captchaDock.currentID) - if not (status == "user" or status == "shared-user"): - self.mainWindow.captchaDock.hide() - self.mainWindow.captchaDock.processing = False - self.mainWindow.captchaDock.currentID = None - - def slotCaptchaDone(self, cid, result): - self.connector.setCaptchaResult(cid, str(result)) - - def pullEvents(self): - events = self.connector.getEvents(self.connector.connectionID) - if not events: - return - for event in events: - if event.eventname == "account": - self.mainWindow.emit(SIGNAL("reloadAccounts"), False) - elif event.eventname == "config": - pass - elif event.destination == Destination.Queue: - self.queue.addEvent(event) - try: - if event.eventname == "update" and event.type == ElementType.File: - info = self.connector.getFileData(event.id) - if info.statusmsg == "finished": - self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info.name) - elif info.statusmsg == "failed": - self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info.name) - if event.event == "insert" and event.type == ElementType.File: - info = self.connector.getLinkInfo(event[3]) - self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info.name) - except: - print "can't send notification" - elif event.destination == Destination.Collector: - self.packageCollector.addEvent(event) - - def slotReloadAccounts(self, force=False): - self.mainWindow.tabs["accounts"]["view"].model().reloadData(force) - - def slotQuit(self): - self.tray.hide() - self.quitInternal() - self.app.quit() - - def quitInternal(self): - if self.core: - self.core.api.kill() - for i in range(10): - if self.core.shuttedDown: - break - sleep(0.5) - - def slotConnectionLost(self): - if not self.connectionLost: - self.connectionLost = True - m = QMessageBox(QMessageBox.Critical, _("Connection lost"), _("Lost connection to the core!"), QMessageBox.Ok) - m.exec_() - self.slotQuit() - - class Loop(): - def __init__(self, parent): - self.parent = parent - self.timer = QTimer() - self.timer.connect(self.timer, SIGNAL("timeout()"), self.update) - self.lastSpaceCheck = 0 - - def start(self): - self.update() - self.timer.start(1000) - - def update(self): - """ - methods to call - """ - self.parent.refreshServerStatus() - if self.lastSpaceCheck + 5 < time(): - self.lastSpaceCheck = time() - self.parent.serverStatus["freespace"] = self.parent.connector.freeSpace() - self.parent.refreshLog() - self.parent.checkCaptcha() - self.parent.pullEvents() - - def stop(self): - self.timer.stop() - - -class TrayIcon(QSystemTrayIcon): - def __init__(self): - QSystemTrayIcon.__init__(self, QIcon(join(pypath, "icons", "logo-gui.png"))) - self.contextMenu = QMenu() - self.showAction = QAction(_("Show"), self.contextMenu) - self.showAction.setCheckable(True) - self.showAction.setChecked(True) - self.showAction.setDisabled(True) - self.contextMenu.addAction(self.showAction) - self.exitAction = QAction(QIcon(join(pypath, "icons", "close.png")), _("Exit"), self.contextMenu) - self.contextMenu.addAction(self.exitAction) - self.setContextMenu(self.contextMenu) - - self.connect(self, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.clicked) - - def mainWindowHidden(self): - self.showAction.setChecked(False) - - def clicked(self, reason): - if self.showAction.isEnabled(): - if reason == QSystemTrayIcon.Trigger: - self.showAction.toggle() - -class Notification(QObject): - def __init__(self, tray): - QObject.__init__(self) - self.tray = tray - self.usePynotify = False - - try: - self.usePynotify = pynotify.init("icon-summary-body") - except: - print "init error" - - def showMessage(self, body): - if self.usePynotify: - n = pynotify.Notification("pyload", body, join(pypath, "icons", "logo.png")) - try: - n.set_hint_string("x-canonical-append", "") - except: - pass - n.show() - else: - self.tray.showMessage("pyload", body) - -if __name__ == "__main__": - renameProcess('pyLoadGui') - app = main() - app.loop() - diff --git a/pylintrc b/pylintrc new file mode 100644 index 000000000..3a55a9efd --- /dev/null +++ b/pylintrc @@ -0,0 +1,46 @@ +[MASTER] + +#init-hook=import InitHomeDir + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,lib,thriftgen + +# Pickle collected data for later comparisons. +persistent=yes + +[MESSAGES CONTROL] + +# Currently only interessted in warnings and errors +disable=C,I,R,W0102,W0106,W0142,W0201,W0232,W0312,W0403,W0613,W0614,W0702,W0703 + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=parseable + +include-ids=yes + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins=_,owd,pypath,addonManager + +[MISCELLANEOUS] +# Notes are not processed +notes= diff --git a/pyload-cli.py b/pyload-cli.py new file mode 100755 index 000000000..b91e7d215 --- /dev/null +++ b/pyload-cli.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 +############################################################################### + +from pyload.cli.Cli import main + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/pyload.py b/pyload.py new file mode 100755 index 000000000..340c35e15 --- /dev/null +++ b/pyload.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 +############################################################################### + +from pyload.Core import main + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/pyload/AccountManager.py b/pyload/AccountManager.py new file mode 100644 index 000000000..b9f1536d9 --- /dev/null +++ b/pyload/AccountManager.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 pyload.Api import AccountInfo +from pyload.utils import lock, json + + +class AccountManager: + """manages all accounts""" + + def __init__(self, core): + """Constructor""" + + self.core = core + self.lock = Lock() + + # PluginName mapped to list of account instances + self.accounts = {} + + self.loadAccounts() + + def _createAccount(self, info, password, options): + plugin = info.plugin + loginname = info.loginname + # Owner != None must be enforced + if info.owner is None: + raise ValueError("Owner must not be null") + + klass = self.core.pluginManager.loadClass("accounts", plugin) + if not klass: + self.core.log.warning(_("Account plugin %s not available") % plugin) + raise ValueError("Account plugin %s not available" % plugin) + + if plugin not in self.accounts: + self.accounts[plugin] = [] + + self.core.log.debug("Create account %s:%s" % (plugin, loginname)) + + # New account instance + account = klass.fromInfoData(self, info, password, options) + self.accounts[plugin].append(account) + return account + + def loadAccounts(self): + """loads all accounts available from db""" + for info, password, options in self.core.db.loadAccounts(): + # put into options as used in other context + options = json.loads(options) if options else {} + try: + self._createAccount(info, password, options) + except: + self.core.log.error(_("Could not load account %s") % info) + self.core.print_exc() + + def iterAccounts(self): + """ yields login, account for all accounts""" + for plugin, accounts in self.accounts.iteritems(): + for account in accounts: + yield plugin, account + + def saveAccounts(self): + """save all account information""" + data = [] + for plugin, accounts in self.accounts.iteritems(): + data.extend( + [(plugin, acc.loginname, acc.owner, 1 if acc.activated else 0, 1 if acc.shared else 0, acc.password, + json.dumps(acc.options)) for acc in + accounts]) + self.core.db.saveAccounts(data) + + def getAccount(self, plugin, loginname, user=None): + """ Find a account by specific user (if given) """ + if plugin in self.accounts: + for acc in self.accounts[plugin]: + if acc.loginname == loginname and (not user or acc.owner == user.true_primary): + return acc + + @lock + def updateAccount(self, plugin, loginname, password, user): + """add or update account""" + account = self.getAccount(plugin, loginname, user) + if account: + if account.setPassword(password): + self.saveAccounts() + account.scheduleRefresh(force=True) + else: + info = AccountInfo(plugin, loginname, user.true_primary, activated=True) + account = self._createAccount(info, password, {}) + account.scheduleRefresh() + self.saveAccounts() + + self.core.eventManager.dispatchEvent("account:updated", account.toInfoData()) + return account + + @lock + def removeAccount(self, plugin, loginname, uid): + """remove account""" + if plugin in self.accounts: + for acc in self.accounts[plugin]: + # admins may delete accounts + if acc.loginname == loginname and (not uid or acc.owner == uid): + self.accounts[plugin].remove(acc) + self.core.db.removeAccount(plugin, loginname) + self.core.evm.dispatchEvent("account:deleted", plugin, loginname) + break + + @lock + def selectAccount(self, plugin, user): + """ Determines suitable plugins and select one """ + if plugin in self.accounts: + uid = user.true_primary if user else None + # TODO: temporary allowed None user + accs = [x for x in self.accounts[plugin] if x.isUsable() and (x.shared or uid is None or x.owner == uid)] + if accs: return choice(accs) + + @lock + def getAllAccounts(self, uid): + """ Return account info for every visible account """ + # filter by owner / shared, but admins see all accounts + accounts = [] + for plugin, accs in self.accounts.iteritems(): + accounts.extend([acc for acc in accs if acc.shared or not uid or acc.owner == uid]) + + return accounts + + def refreshAllAccounts(self): + """ Force a refresh of every account """ + for p in self.accounts.itervalues(): + for acc in p: + acc.getAccountInfo(True) diff --git a/pyload/AddonManager.py b/pyload/AddonManager.py new file mode 100644 index 000000000..7935ff112 --- /dev/null +++ b/pyload/AddonManager.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 __builtin__ + +from thread import start_new_thread +from threading import RLock + +from types import MethodType + +from pyload.threads.AddonThread import AddonThread +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 + # TODO: multiuser, addons can store the user itself, probably not needed here + 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() + + # manage addons on config change + self.listenTo("config:changed", self.manageAddon) + + @lock + def callInHooks(self, event, eventName, *args): + """ Calls a method in all addons and catch / log errors""" + for plugin in self.plugins.itervalues(): + self.call(plugin, event, *args) + self.dispatchEvent(eventName, *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) + self.core.print_exc() + + @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}) + self.core.print_exc() + + self.log.info(_("Activated addons: %s") % ", ".join(sorted(active))) + self.log.info(_("Deactivated addons: %s") % ", ".join(sorted(deactive))) + + def manageAddon(self, plugin, name, value): + # TODO: user + + # 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 addons...")) + 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 addons...")) + for plugin in self.plugins.itervalues(): + self.call(plugin, "deactivate") + + def downloadPreparing(self, pyfile): + self.callInHooks("downloadPreparing", "download:preparing", pyfile) + + def downloadFinished(self, pyfile): + self.callInHooks("downloadFinished", "download:finished", pyfile) + + def downloadFailed(self, pyfile): + self.callInHooks("downloadFailed", "download:failed", pyfile) + + def packageFinished(self, package): + self.callInHooks("packageFinished", "package:finished", package) + + @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.listenTo(event, getattr(plugin, func)) + # clean up + del self.events[name] + + def listenTo(self, *args): + self.core.eventManager.listenTo(*args) + + def dispatchEvent(self, *args): + self.core.eventManager.dispatchEvent(*args) + diff --git a/pyload/Api.py b/pyload/Api.py new file mode 100644 index 000000000..afd2bb406 --- /dev/null +++ b/pyload/Api.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 types import MethodType + +from remote.apitypes import * + +# contains function names mapped to their permissions +# unlisted functions are for admins only +perm_map = {} + +# decorator only called on init, never initialized, so has no effect on runtime +def RequirePerm(bits): + class _Dec(object): + def __new__(cls, func, *args, **kwargs): + perm_map[func.__name__] = bits + return func + + return _Dec + +urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE) + +stateMap = { + DownloadState.All: frozenset(getattr(DownloadStatus, x) for x in dir(DownloadStatus) if not x.startswith("_")), + DownloadState.Finished: frozenset((DownloadStatus.Finished, DownloadStatus.Skipped)), + DownloadState.Unfinished: None, # set below + DownloadState.Failed: frozenset((DownloadStatus.Failed, DownloadStatus.TempOffline, DownloadStatus.Aborted, + DownloadStatus.NotPossible)), + DownloadState.Unmanaged: None, #TODO +} + +stateMap[DownloadState.Unfinished] = frozenset(stateMap[DownloadState.All].difference(stateMap[DownloadState.Finished])) + +def state_string(state): + return ",".join(str(x) for x in stateMap[state]) + +from datatypes.User import User + +class Api(Iface): + """ + **pyLoads API** + + This is accessible either internal via core.api, websocket backend or json api. + + see Thrift specification file remote/thriftbackend/pyload.thrift\ + 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 web interface. + Admin user have all permissions, and are the only ones who can access the methods with no specific permission. + """ + + EXTERNAL = Iface # let the json api know which methods are external + EXTEND = False # only extendable when set too true + + def __init__(self, core): + self.core = core + self.user_apis = {} + + @property + def user(self): + return None #TODO return default user? + + @property + def primaryUID(self): + return self.user.primary if self.user else None + + @classmethod + def initComponents(cls): + # Allow extending the api + # This prevents unintentionally registering of the components, + # but will only work once when they are imported + cls.EXTEND = True + # Import all Api modules, they register themselves. + import pyload.api + # they will vanish from the namespace afterwards + + + @classmethod + def extend(cls, api): + """Takes all params from api and extends cls with it. + api class can be removed afterwards + + :param api: Class with methods to extend + """ + if cls.EXTEND: + for name, func in api.__dict__.iteritems(): + if name.startswith("_"): continue + setattr(cls, name, MethodType(func, None, cls)) + + return cls.EXTEND + + def withUserContext(self, uid): + """ Returns a proxy version of the api, to call method in user context + + :param uid: user or userData instance or uid + :return: :class:`UserApi` + """ + if isinstance(uid, User): + uid = uid.uid + + if uid not in self.user_apis: + user = self.core.db.getUserData(uid=uid) + if not user: #TODO: anonymous user? + return None + + self.user_apis[uid] = UserApi(self.core, User.fromUserData(self, user)) + + return self.user_apis[uid] + + + ############################# + # Auth+User Information + ############################# + + @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. + + :param username: + :param password: + :param remoteip: Omit this argument, its only used internal + :return: bool indicating login was successful + """ + return True if self.checkAuth(username, password, remoteip) else False + + def checkAuth(self, username, password, remoteip=None): + """Check authentication and returns details + + :param username: + :param password: + :param remoteip: + :return: dict with info, empty when login is incorrect + """ + self.core.log.info(_("User '%s' tries to log in") % username) + + return self.core.db.checkAuth(username, password) + + @staticmethod + def isAuthorized(func, user): + """checks if the user is authorized for specific method + + :param func: function name + :param user: `User` + :return: boolean + """ + if user.isAdmin(): + return True + elif func in perm_map and user.hasPermission(perm_map[func]): + return True + else: + return False + + +class UserApi(Api): + """ Proxy object for api that provides all methods in user context """ + + def __init__(self, core, user): + # No need to init super class + self.core = core + self._user = user + + def withUserContext(self, uid): + raise Exception("Not allowed") + + @property + def user(self): + return self._user
\ No newline at end of file diff --git a/pyload/Core.py b/pyload/Core.py new file mode 100644 index 000000000..abcc328f3 --- /dev/null +++ b/pyload/Core.py @@ -0,0 +1,696 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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: spoob +# @author: sebnapi +# @author: RaNaN +# @author: mkaay +# @version: v0.5.0 +############################################################################### + +from pyload import __version__ as CURRENT_VERSION + +import __builtin__ + +from getopt import getopt, GetoptError +import logging +import logging.handlers +import os +from os import _exit, execl, getcwd, remove, walk, chdir, close +import signal +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 + +import InitHomeDir + +from AccountManager import AccountManager +from config.ConfigParser import ConfigParser +from config.ConfigManager import ConfigManager +from PluginManager import PluginManager +from interaction.EventManager import EventManager +from network.RequestFactory import RequestFactory +from web.ServerThread import WebServer +from Scheduler import Scheduler +from remote.RemoteManager import RemoteManager +from utils.JsEngine import JsEngine + +import utils.pylgettext as gettext +from utils import formatSize, get_console_encoding +from utils.fs import free_space, exists, makedirs, join, chmod + +from codecs import getwriter + +# 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.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 + + if len(argv) > 1: + try: + options, args = getopt(argv[1:], 'vchdusqp:', + ["version", "clear", "clean", "help", "debug", "user", + "setup", "configdir=", "changedir", "daemon", + "quit", "status", "no-remote","pidfile="]) + + for option, argument in options: + if option in ("-v", "--version"): + print "pyLoad", CURRENT_VERSION + exit() + elif option in ("-p", "--pidfile"): + self.pidfile = argument + elif option == "--daemon": + self.daemon = True + elif option in ("-c", "--clear"): + self.deleteLinks = True + elif option in ("-h", "--help"): + self.print_help() + exit() + elif option in ("-d", "--debug"): + self.doDebug = True + elif option in ("-u", "--user"): + from setup.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.set_user() + exit() + elif option in ("-s", "--setup"): + from setup.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.start() + exit() + elif option == "--changedir": + from setup.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.conf_path(True) + exit() + elif option in ("-q", "--quit"): + self.quitInstance() + exit() + elif option == "--status": + pid = self.isAlreadyRunning() + if self.isAlreadyRunning(): + print pid + exit(0) + else: + print "false" + exit(1) + elif option == "--clean": + self.cleanTree() + exit() + elif option == "--no-remote": + self.remote = False + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(argv[1:]) + self.print_help() + exit() + + def print_help(self): + print "" + print "pyLoad v%s 2008-2013 the pyLoad Team" % CURRENT_VERSION + print "" + if sys.argv[0].endswith(".py"): + print "Usage: python pyload.py [options]" + else: + print "Usage: pyload [options]" + print "" + print "<Options>" + print " -v, --version", " " * 10, "Print version to terminal" + print " -c, --clear", " " * 12, "Delete all saved packages/links" + #print " -a, --add=<link/list>", " " * 2, "Add the specified links" + print " -u, --user", " " * 13, "Manages users" + print " -d, --debug", " " * 12, "Enable debug mode" + print " -s, --setup", " " * 12, "Run setup assistant" + print " --configdir=<dir>", " " * 6, "Run with <dir> as configuration directory" + print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>" + print " --changedir", " " * 12, "Change configuration directory permanently" + print " --daemon", " " * 15, "Daemonize after startup" + print " --no-remote", " " * 12, "Disable remote access" + print " --status", " " * 15, "Display pid if running or False" + print " --clean", " " * 16, "Remove .pyc/.pyo files" + print " -q, --quit", " " * 13, "Quit a running pyLoad instance" + print " -h, --help", " " * 13, "Display this help screen" + print "" + + + def quit(self, a, b): + self.shutdown() + self.log.info(_("Received Quit signal")) + _exit(1) + + def writePidFile(self): + self.deletePidFile() + pid = os.getpid() + f = open(self.pidfile, "wb") + f.write(str(pid)) + f.close() + chmod(self.pidfile, 0660) + + def deletePidFile(self): + if self.checkPidFile(): + self.log.debug("Deleting old pidfile %s" % self.pidfile) + os.remove(self.pidfile) + + def checkPidFile(self): + """ return pid as int or 0""" + if os.path.isfile(self.pidfile): + f = open(self.pidfile, "rb") + pid = f.read().strip() + f.close() + if pid: + pid = int(pid) + return pid + + return 0 + + def isAlreadyRunning(self): + pid = self.checkPidFile() + if not pid or os.name == "nt": return False + try: + os.kill(pid, 0) # 0 - default signal (does nothing) + except: + return 0 + + return pid + + def quitInstance(self): + if os.name == "nt": + print "Not supported on windows." + return + + pid = self.isAlreadyRunning() + if not pid: + print "No pyLoad running." + return + + try: + os.kill(pid, 3) #SIGUIT + + t = time() + print "waiting for pyLoad to quit" + + while exists(self.pidfile) and t + 10 > time(): + sleep(0.25) + + if not exists(self.pidfile): + print "pyLoad successfully stopped" + else: + os.kill(pid, 9) #SIGKILL + print "pyLoad did not respond" + print "Kill signal was send to process with id %s" % pid + + except: + print "Error quitting pyLoad" + + + def cleanTree(self): + for path, dirs, files in walk(self.path("pyload")): + for f in files: + if not f.endswith(".pyo") and not f.endswith(".pyc"): + continue + + if "_25" in f or "_26" in f or "_27" in f: + continue + + print join(path, f) + remove(join(path, f)) + + def start(self, rpc=True, web=True, tests=False): + """ starts the fun :D """ + + self.version = CURRENT_VERSION + + if not exists("pyload.conf") and not tests: + from setup.Setup import Setup + + print "This is your first start, running configuration assistant now." + self.config = ConfigParser() + s = Setup(pypath, self.config) + res = False + try: + res = s.start() + except SystemExit: + pass + except KeyboardInterrupt: + print "\nSetup interrupted" + except: + res = False + print_exc() + print "Setup failed" + if not res: + remove("pyload.conf") + + exit() + + try: signal.signal(signal.SIGQUIT, self.quit) + except: pass + + self.config = ConfigParser() + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoad", self.path("locale"), + languages=[self.config['general']['language'],"en"],fallback=True) + translation.install(True) + + # 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() + # don't exit when in test runner + if pid and not tests: + print _("pyLoad already running with pid %s") % pid + exit() + + if os.name != "nt" and self.config["general"]["renice"]: + os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid())) + + if self.config["permission"]["change_group"]: + if os.name != "nt": + try: + from grp import getgrnam + + group = getgrnam(self.config["permission"]["group"]) + os.setgid(group[2]) + except Exception, e: + print _("Failed changing group: %s") % e + + if self.config["permission"]["change_user"]: + if os.name != "nt": + try: + from pwd import getpwnam + + user = getpwnam(self.config["permission"]["user"]) + os.setuid(user[2]) + except Exception, e: + print _("Failed changing user: %s") % e + + if self.debug: + self.init_logger(logging.DEBUG) # logging level + else: + self.init_logger(logging.INFO) # logging level + + self.do_kill = False + self.do_restart = False + self.shuttedDown = False + + self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION) + self.log.info(_("Using home directory: %s") % getcwd()) + + if not tests: + self.writePidFile() + + self.captcha = True # checks seems to fail, although tesseract is available + + self.eventManager = self.evm = EventManager(self) + self.setupDB() + + # Upgrade to configManager + self.config = ConfigManager(self, self.config) + + if self.deleteLinks: + self.log.info(_("All links removed")) + self.db.purgeLinks() + + self.requestFactory = RequestFactory(self) + __builtin__.pyreq = self.requestFactory + + # deferred import, could improve start-up time + from Api import Api + from AddonManager import AddonManager + from interaction.InteractionManager import InteractionManager + from threads.ThreadManager import ThreadManager + + Api.initComponents() + self.api = Api(self) + + self.scheduler = Scheduler(self) + + #hell yeah, so many important managers :D + self.pluginManager = PluginManager(self) + self.interactionManager = self.im = InteractionManager(self) + self.accountManager = AccountManager(self) + self.threadManager = ThreadManager(self) + self.addonManager = AddonManager(self) + self.remoteManager = RemoteManager(self) + + self.js = JsEngine() + + # enough initialization for test cases + if tests: return + + self.log.info(_("Download time: %s") % self.api.isTimeDownload()) + + if rpc: + self.remoteManager.startBackends() + + if web: + self.init_webserver() + + 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)) + + self.config.save() #save so config files gets filled + + link_file = join(pypath, "links.txt") + + if exists(link_file): + f = open(link_file, "rb") + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + f.close() + + link_file = "links.txt" + if exists(link_file): + f = open(link_file, "rb") + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + f.close() + + #self.scheduler.addJob(0, self.accountManager.getAccountInfos) + self.log.info(_("Activating Accounts...")) + self.accountManager.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.addonManager.activateAddons() + + self.log.info(_("pyLoad is up and running")) + self.eventManager.dispatchEvent("core:ready") + + #test api +# from pyload.common.APIExerciser import startApiExerciser +# startApiExerciser(self, 3) + + #some memory stats +# from guppy import hpy +# hp=hpy() +# print hp.heap() +# import objgraph +# 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(1.5) + if self.do_restart: + self.log.info(_("restarting pyLoad")) + self.restart() + if self.do_kill: + self.shutdown() + self.log.info(_("pyLoad quits")) + self.removeLogger() + _exit(0) + # TODO check exits codes, clean exit is still blocked + + self.threadManager.work() + self.interactionManager.work() + self.scheduler.work() + + def setupDB(self): + from database import DatabaseBackend + from FileManager import FileManager + + self.db = DatabaseBackend(self) # the backend + self.db.setup() + + self.files = FileManager(self) + self.db.manager = self.files #ugly? + + def init_webserver(self): + if self.config['webinterface']['activated']: + self.webserver = WebServer(self) + self.webserver.start() + + def init_logger(self, level): + console = logging.StreamHandler(sys.stdout) + + # try to get a time formatting depending on system locale + datefmt = None + try: # change current locale to default if it is not set + current_locale = locale.getlocale() + if current_locale == (None, None): + current_locale = locale.setlocale(locale.LC_ALL, '') + + # We use timeformat provided by locale when available + if current_locale != (None, None): + datefmt = locale.nl_langinfo(locale.D_FMT) + " " + locale.nl_langinfo(locale.T_FMT) + except: # something did go wrong, locale is heavily platform dependant + pass + + # default formatting when no one was obtained (ex.: 2013-10-22 18:27:46) + if not datefmt: + datefmt = "%Y-%m-%d %H:%M:%S" + + # file handler formatter + fhfmt = "%(asctime)s %(levelname)-8s %(message)s" + fh_frm = logging.Formatter(fhfmt, datefmt) + + # console formatter + if self.config['log']['console_color'] == "No": + console_frm = fh_frm + else: + from lib.colorlog import ColoredFormatter + + if self.config['log']['console_color'] == "Full": + cfmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)+8s %(reset)s %(message)s" + clr = { + 'DEBUG': 'bg_cyan', + 'INFO': 'bg_green', + 'WARNING': 'bg_yellow', + 'ERROR': 'bg_red', + 'CRITICAL': 'bg_purple', + } + elif self.config['log']['console_color'] == "Light": + cfmt = "%(log_color)s%(asctime)s %(levelname)-8s %(message)s" + clr = { + 'DEBUG': 'cyan', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'purple', + } + console_frm = ColoredFormatter(cfmt, datefmt, clr) + + #: set console formatter + console.setFormatter(console_frm) + + 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']: + file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'), + maxBytes=self.config['log']['log_size'] * 1024, + backupCount=int(self.config['log']['log_count']), + encoding="utf8") + else: + file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8") + + #: set file handler formatter + file_handler.setFormatter(fh_frm) + self.log.addHandler(file_handler) + + self.log.addHandler(console) #if console logging + self.log.setLevel(level) + + def removeLogger(self): + for h in list(self.log.handlers): + self.log.removeHandler(h) + h.close() + + + def restart(self): + self.shutdown() + chdir(owd) + # close some open fds + for i in range(3,50): + try: + close(i) + except : + pass + + execl(executable, executable, *sys.argv) + _exit(0) + + def shutdown(self): + self.log.info(_("shutting down...")) + self.eventManager.dispatchEvent("coreShutdown") + try: + if self.config['webinterface']['activated'] and hasattr(self, "webserver"): + pass # TODO: quit webserver? +# self.webserver.quit() + + for thread in self.threadManager.threads: + thread.put("quit") + + self.api.stopAllDownloads() + self.addonManager.deactivateAddons() + + except: + self.print_exc() + self.log.info(_("error while shutting down")) + + finally: + self.files.syncSave() + self.db.shutdown() + self.shuttedDown = True + + 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, force=False): + if self.debug or force: + print_exc() + + def path(self, *args): + return join(pypath, *args) + + +def deamon(): + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # decouple from parent environment + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent, print eventual PID before + print "Daemon PID %d" % pid + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # Iterate through and close some file descriptors. + for fd in range(0, 3): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + os.open(os.devnull, os.O_RDWR) # standard input (0) + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) + + pyload_core = Core() + pyload_core.start() + +# And so it begins... +def main(): + #change name to 'pyLoadCore' + #from module.lib.rename_process import renameProcess + #renameProcess('pyLoadCore') + if "--daemon" in sys.argv: + deamon() + else: + pyload_core = Core() + try: + pyload_core.start() + except KeyboardInterrupt: + pyload_core.shutdown() + pyload_core.log.info(_("killed pyLoad from terminal")) + pyload_core.removeLogger() + _exit(1) + + +if __name__ == "__main__": + print "This file can not be started directly." diff --git a/pyload/FileManager.py b/pyload/FileManager.py new file mode 100644 index 000000000..614418f99 --- /dev/null +++ b/pyload/FileManager.py @@ -0,0 +1,582 @@ +#!/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 ReadWriteLock import ReadWriteLock + +from pyload.utils import lock, read_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].downloadstats = {} + args[0].queuestats = {} + 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"), _("not possible"), + _("decrypting"), _("processing"), _("custom"), _("unknown")] + + self.files = {} # holds instances for files + self.packages = {} # same for packages + + self.jobCache = {} + + # locking the caches, db is already locked implicit + self.lock = ReadWriteLock() + #self.lock._Verbose__verbose = True + + self.downloadstats = {} # cached dl stats + self.queuestats = {} # cached queue stats + + self.db = self.core.db + + def save(self): + """saves all data to backend""" + self.db.commit() + + @read_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("package:updated", 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("package:inserted", 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 + + @read_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 + + @read_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) + + @read_lock + def getTree(self, pid, full, state, search=None): + """ 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, state=state, search=search) + + # 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 only accessed by one thread, should not need a lock + #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 getDownloadStats(self, user=None): + """ return number of downloads """ + if user not in self.downloadstats: + self.downloadstats[user] = self.db.downloadstats(user) + + return self.downloadstats[user] + + def getQueueStats(self, user=None, force=False): + """number of files that have to be processed, failed files will not be included""" + if user not in self.queuestats or force: + self.queuestats[user] = self.db.queuestats(user) + + return self.queuestats[user] + + def scanDownloadFolder(self): + pass + + @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("package:deleted", 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("file:deleted", 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) + + # This event is thrown with pyfile or only fid + self.evm.dispatchEvent("file:updated", pyfile) + + def updatePackage(self, pypack): + """updates a package""" + self.db.updatePackage(pypack) + self.evm.dispatchEvent("package:updated", pypack.pid) + + @invalidate + def updateFileInfo(self, data, pid): + """ updates file info (name, size, status,[ hash,] url)""" + self.db.updateLinkInfo(data) + self.evm.dispatchEvent("package:updated", pid) + + def checkAllLinksFinished(self): + """checks if all files are finished and dispatch event""" + + # TODO: user context? + if not self.db.queuestats()[0]: + self.core.addonManager.dispatchEvent("download:allFinished") + 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() + + # TODO: user context? + if not self.db.processcount(fid): + self.core.addonManager.dispatchEvent("download:allProcessed") + 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 + + @read_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("package:updated", pid) + + @read_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("file:updated", 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("package:reordered", 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("file:reordered", pid) + + @read_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 + + @read_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 + + + @invalidate + 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/pyload/InitHomeDir.py b/pyload/InitHomeDir.py new file mode 100644 index 000000000..4c7fce2eb --- /dev/null +++ b/pyload/InitHomeDir.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN + + This modules inits working directories and global variables, pydir and homedir +""" + +from os import makedirs, path, chdir +from os.path import join +import sys +from sys import argv, platform + +from . import __dev__ + +import __builtin__ + +__builtin__.owd = path.abspath("") #original working directory +__builtin__.pypath = path.abspath(path.join(__file__, "..", "..")) + +# Before changing the cwd, the abspath of the module must be manifested +if 'pyload' in sys.modules: + rel_pyload = sys.modules['pyload'].__path__[0] + abs_pyload = path.abspath(rel_pyload) + if abs_pyload != rel_pyload: + sys.modules['pyload'].__path__.insert(0, abs_pyload) + +sys.path.append(join(pypath, "pyload", "lib")) + +homedir = "" + +if platform == 'nt': + homedir = path.expanduser("~") + if homedir == "~": + import ctypes + + CSIDL_APPDATA = 26 + _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW + _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND, + ctypes.c_int, + ctypes.wintypes.HANDLE, + ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR] + + path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf) + homedir = path_buf.value +else: + homedir = path.expanduser("~") + +__builtin__.homedir = homedir + +configdir = None +args = " ".join(argv) +# dirty method to set configdir from commandline arguments +if "--configdir=" in args: + for arg in argv: + if arg.startswith("--configdir="): + configdir = arg.replace('--configdir=', '').strip() + +elif "nosetests" in args: + print "Running in test mode" + configdir = join(pypath, "tests", "config") + +elif path.exists(path.join(pypath, "pyload", "config", "configdir")): + f = open(path.join(pypath, "pyload", "config", "configdir"), "rb") + c = f.read().strip() + f.close() + configdir = path.join(pypath, c) + +# default config dir +if not configdir: + # suffix when running dev version + dev = "-dev" if __dev__ else "" + configname = ".pyload" if platform in ("posix", "linux2", "darwin") else "pyload" + configdir = path.join(homedir, configname + dev) + +if not path.exists(configdir): + makedirs(configdir, 0700) + +__builtin__.configdir = configdir +chdir(configdir) + +#print "Using %s as working directory." % configdir diff --git a/pyload/PluginManager.py b/pyload/PluginManager.py new file mode 100644 index 000000000..2e3c66e03 --- /dev/null +++ b/pyload/PluginManager.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 sys + +from os.path import abspath, join +from pyload.utils.PluginLoader import LoaderFactory, PluginLoader + + +class PluginMatcher(object): + """ Abstract class that allows modify which plugins to match and to load """ + + def matchURL(self, url): + return None + + def getPlugin(self, plugin, name): + return False + + +class PluginManager: + ROOT = "pyload.plugins" + LOCALROOT = "localplugins" + + MATCH_HISTORY = 10 + DEFAULT_PLUGIN = "BasePlugin" + + def __init__(self, core): + self.core = core + self.log = core.log + + # cached modules (type, name) + self.modules = {} + # match history to speedup parsing (type, name) + self.history = [] + + #register for import addon + sys.meta_path.append(self) + + # add to path, so we can import from userplugins + sys.path.append(abspath("")) + self.loader = LoaderFactory(PluginLoader(abspath(self.LOCALROOT), self.LOCALROOT, self.core.config), + PluginLoader(abspath(join(pypath, "pyload", "plugins")), self.ROOT, + self.core.config)) + + self.loader.checkVersions() + + # plugin matcher to overwrite some behaviour + self.matcher = [] + + def addMatcher(self, matcher, index=0): + """ Inserts matcher at given index, first position by default """ + if not isinstance(matcher, PluginMatcher): + raise TypeError("Expected type of PluginMatcher, got %s instead" % type(matcher)) + + if matcher in self.matcher: + self.matcher.remove(matcher) + + self.matcher.insert(index, matcher) + + def removeMatcher(self, matcher): + """ Removes a matcher if it exists """ + if matcher in self.matcher: + self.matcher.remove(matcher) + + 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.loader.getPlugin(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 loader in self.loader: + for name, plugin in loader.getPlugins(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, self.DEFAULT_PLUGIN)) + + return res["hoster"], res["crypter"] + + def getPlugins(self, plugin): + """ Get all plugins of a certain type in a dict """ + plugins = {} + for loader in self.loader: + plugins.update(loader.getPlugins(plugin)) + return plugins + + def findPlugin(self, name, pluginlist=("hoster", "crypter")): + # TODO: use matcher + for loader in self.loader: + for plugin in pluginlist: + if loader.hasPlugin(plugin, name): + return plugin, loader.getPlugin(plugin, name) + + return None, None + + def getPluginClass(self, name, overwrite=True): + """Gives the plugin class of a hoster or crypter plugin + + :param overwrite: allow the use of overwritten plugins + """ + # TODO: use matcher + type, plugin = self.findPlugin(name) + return self.loadClass(type, name) + + def loadAttributes(self, plugin, name): + for loader in self.loader: + if loader.hasPlugin(plugin, name): + return loader.loadAttributes(plugin, name) + + return {} + + def loadModule(self, plugin, name): + """ Returns loaded module for plugin + + :param plugin: plugin type, subfolder of module.plugins + """ + if (plugin, name) in self.modules: return self.modules[(plugin, name)] + for loader in self.loader: + if loader.hasPlugin(plugin, name): + try: + module = loader.loadModule(plugin, name) + # cache import + self.modules[(plugin, name)] = module + 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, plugin, name): + """Returns the class of a plugin with the same name""" + module = self.loadModule(plugin, name) + if module: return getattr(module, name) + + def find_module(self, fullname, path=None): + #redirecting imports if necessary + for loader in self.loader: + if not fullname.startswith(loader.package): + continue + + # TODO not well tested + offset = 1 - loader.package.count(".") + + split = fullname.split(".") + if len(split) != 4 - offset: return + plugin, name = split[2 - offset:4 - offset] + + # check if a different loader than the current one has the plugin + # in this case import needs redirect + for l2 in self.loader: + if l2 is not loader and l2.hasPlugin(plugin, name): + return self + + # TODO: Remove when all plugin imports are adapted + if "module" in fullname: + return self + + def load_module(self, name, replace=True): + if name not in sys.modules: #could be already in modules + + # TODO: only temporary + if name.endswith("module"): + # name = "pyload." + name = name.replace(".module", "") + self.log.debug("Old import reference detected, use %s" % name) + replace = False + return __import__("pyload") + if name.startswith("module"): + name = name.replace("module", "pyload") + self.log.debug("Old import reference detected, use %s" % name) + replace = False + + # TODO: this still works but does not respect other loaders + if replace: + if self.ROOT in name: + newname = name.replace(self.ROOT, self.LOCALROOT) + else: + newname = name.replace(self.LOCALROOT, 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 """ + # TODO + # check if reloadable + # reload + # save new plugins + # update index + # reload accounts + + def isUserPlugin(self, plugin): + """ A plugin suitable for multiple user """ + return any(l.isUserPlugin(plugin) for l in self.loader) + + def getCategory(self, plugin): + plugin = self.loader.getPlugin("addons", plugin) + if plugin: + return plugin.category or "addon" + + def loadIcon(self, 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/Scheduler.py b/pyload/Scheduler.py index 0bc396b69..0bc396b69 100644 --- a/module/Scheduler.py +++ b/pyload/Scheduler.py diff --git a/pyload/__init__.py b/pyload/__init__.py new file mode 100644 index 000000000..0fb52399b --- /dev/null +++ b/pyload/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +__dev__ = True +__version_info__ = ('0', '4', '9', '9') +__version__ = '.'.join(__version_info__) + ("-dev" if __dev__ else "")
\ No newline at end of file diff --git a/pyload/api/AccountApi.py b/pyload/api/AccountApi.py new file mode 100644 index 000000000..d4b39c12b --- /dev/null +++ b/pyload/api/AccountApi.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.utils import to_bool +from pyload.Api import Api, RequirePerm, Permission, Conflict +from ApiComponent import ApiComponent + + +class AccountApi(ApiComponent): + """ All methods to control accounts """ + + @RequirePerm(Permission.All) + def getAccountTypes(self): + """All available account types. + + :return: string list + """ + return self.core.pluginManager.getPlugins("accounts").keys() + + @RequirePerm(Permission.Accounts) + def getAccounts(self): + """Get information about all entered accounts. + + :return: list of `AccountInfo` + """ + accounts = self.core.accountManager.getAllAccounts(self.primaryUID) + return [acc.toInfoData() for acc in accounts] + + @RequirePerm(Permission.Accounts) + def getAccountInfo(self, plugin, loginname, refresh=False): + """ Returns :class:`AccountInfo` for a specific account + + :param refresh: reload account info + """ + account = self.core.accountManager.getAccount(plugin, loginname) + + # Admins can see and refresh accounts + if not account or (self.primaryUID and self.primaryUID != account.owner): + return None + + if refresh: + # reload account in place + account.getAccountInfo(True) + + return account.toInfoData() + + @RequirePerm(Permission.Accounts) + def updateAccount(self, plugin, loginname, password): + """Creates an account if not existent or updates the password + + :return: newly created or updated account info + """ + # TODO: None pointer + return self.core.accountManager.updateAccount(plugin, loginname, password, self.user).toInfoData() + + + @RequirePerm(Permission.Accounts) + def updateAccountInfo(self, account): + """ Update account settings from :class:`AccountInfo` """ + inst = self.core.accountManager.getAccount(account.plugin, account.loginname, self.user) + if not account: + return + + inst.activated = to_bool(account.activated) + inst.shared = to_bool(account.shared) + inst.updateConfig(account.config) + + + @RequirePerm(Permission.Accounts) + def removeAccount(self, account): + """Remove account from pyload. + + :param account: :class:`ÀccountInfo` instance + """ + self.core.accountManager.removeAccount(account.plugin, account.loginname, self.primaryUID) + + +if Api.extend(AccountApi): + del AccountApi
\ No newline at end of file diff --git a/pyload/api/AddonApi.py b/pyload/api/AddonApi.py new file mode 100644 index 000000000..4ae686d2d --- /dev/null +++ b/pyload/api/AddonApi.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission + +from ApiComponent import ApiComponent + +class AddonApi(ApiComponent): + """ Methods to interact with addons """ + + def getAllInfo(self): + """Returns all information stored by addon plugins. Values are always strings + + :return: {"plugin": {"name": value } } + """ + return self.core.addonManager.getAllInfo() + + def getInfoByPlugin(self, plugin): + """Returns information stored by a specific plugin. + + :param plugin: pluginname + :return: dict of attr names mapped to value {"name": value} + """ + return self.core.addonManager.getInfo(plugin) + +if Api.extend(AddonApi): + del AddonApi
\ No newline at end of file diff --git a/pyload/api/ApiComponent.py b/pyload/api/ApiComponent.py new file mode 100644 index 000000000..bb333c259 --- /dev/null +++ b/pyload/api/ApiComponent.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.remote.apitypes import Iface + +# Workaround to let code-completion think, this is subclass of Iface +Iface = object +class ApiComponent(Iface): + + __slots__ = [] + + def __init__(self, core, user): + # Only for auto completion, this class can not be instantiated + from pyload import Core + from pyload.datatypes.User import User + assert isinstance(core, Core) + assert issubclass(ApiComponent, Iface) + self.core = core + assert isinstance(user, User) + self.user = user + self.primaryUID = 0 + # No instantiating! + raise Exception()
\ No newline at end of file diff --git a/pyload/api/ConfigApi.py b/pyload/api/ConfigApi.py new file mode 100644 index 000000000..2adc0c565 --- /dev/null +++ b/pyload/api/ConfigApi.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission, ConfigHolder, ConfigItem, ConfigInfo +from pyload.utils import to_string + +from ApiComponent import ApiComponent + +# helper function to create a ConfigHolder +def toConfigHolder(section, config, values): + holder = ConfigHolder(section, config.label, config.description, config.explanation) + holder.items = [ConfigItem(option, x.label, x.description, x.input, + to_string(values.get(option, x.input.default_value))) for option, x in + config.config.iteritems()] + return holder + + +class ConfigApi(ApiComponent): + """ Everything related to configuration """ + + def getConfigValue(self, section, option): + """Retrieve config value. + + :param section: name of category, or plugin + :param option: config option + :rtype: str + :return: config value as string + """ + value = self.core.config.get(section, option, self.primaryUID) + return to_string(value) + + def setConfigValue(self, section, option, value): + """Set new config value. + + :param section: + :param option: + :param value: new config value + """ + if option in ("limit_speed", "max_speed"): #not so nice to update the limit + self.core.requestFactory.updateBucket() + + self.core.config.set(section, option, value, self.primaryUID) + + def getConfig(self): + """Retrieves complete config of core. + + :rtype: dict of section -> ConfigHolder + """ + data = {} + for section, config, values in self.core.config.iterCoreSections(): + data[section] = toConfigHolder(section, config, values) + return data + + def getCoreConfig(self): + """ Retrieves core config sections + + :rtype: list of PluginInfo + """ + return [ConfigInfo(section, config.label, config.description, False, False) + for section, config, values in self.core.config.iterCoreSections()] + + @RequirePerm(Permission.Plugins) + def getPluginConfig(self): + """All plugins and addons the current user has configured + + :rtype: list of PluginInfo + """ + # TODO: include addons that are activated by default + # TODO: multi user + # TODO: better plugin / addon activated config + data = [] + active = [x.getName() for x in self.core.addonManager.activePlugins()] + for name, config, values in self.core.config.iterSections(self.primaryUID): + # skip unmodified and inactive addons + if not values and name not in active: continue + + item = ConfigInfo(name, config.label, config.description, + self.core.pluginManager.getCategory(name), + self.core.pluginManager.isUserPlugin(name), + # TODO: won't work probably + values.get("activated", None if "activated" not in config.config else config.config[ + "activated"].input.default_value)) + data.append(item) + + return data + + @RequirePerm(Permission.Plugins) + def getAvailablePlugins(self): + """List of all available plugins, that are configurable + + :rtype: list of PluginInfo + """ + # TODO: filter user_context / addons when not allowed + plugins = [ConfigInfo(name, config.label, config.description, + self.core.pluginManager.getCategory(name), + self.core.pluginManager.isUserPlugin(name)) + for name, config, values in self.core.config.iterSections(self.primaryUID)] + + return plugins + + @RequirePerm(Permission.Plugins) + def loadConfig(self, name): + """Get complete config options for desired section + + :param name: Name of plugin or config section + :rtype: ConfigHolder + """ + # requires at least plugin permissions, but only admin can load core config + config, values = self.core.config.getSection(name, self.primaryUID) + return toConfigHolder(name, config, values) + + + @RequirePerm(Permission.Plugins) + def saveConfig(self, config): + """Used to save a configuration, core config can only be saved by admins + + :param config: :class:`ConfigHolder` + """ + for item in config.items: + self.core.config.set(config.name, item.name, item.value, sync=False, user=self.primaryUID) + # save the changes + self.core.config.saveValues(self.primaryUID, config.name) + + @RequirePerm(Permission.Plugins) + def deleteConfig(self, plugin): + """Deletes modified config + + :param plugin: plugin name + """ + #TODO: delete should deactivate addons? + self.core.config.delete(plugin, self.primaryUID) + + +if Api.extend(ConfigApi): + del ConfigApi
\ No newline at end of file diff --git a/pyload/api/CoreApi.py b/pyload/api/CoreApi.py new file mode 100644 index 000000000..ebb194134 --- /dev/null +++ b/pyload/api/CoreApi.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission, ServerStatus, Interaction +from pyload.utils.fs import join, free_space +from pyload.utils import compare_time + +from ApiComponent import ApiComponent + +class CoreApi(ApiComponent): + """ This module provides methods for general interaction with the core, like status or progress retrieval """ + + @RequirePerm(Permission.All) + def getServerVersion(self): + """pyLoad Core version """ + return self.core.version + + @RequirePerm(Permission.All) + def getWSAddress(self): + """Gets and address for the websocket based on configuration""" + # TODO SSL (wss) + return "ws://%%s:%d" % self.core.config['remote']['port'] + + @RequirePerm(Permission.All) + def getServerStatus(self): + """Some general information about the current status of pyLoad. + + :return: `ServerStatus` + """ + queue = self.core.files.getQueueStats(self.primaryUID) + total = self.core.files.getDownloadStats(self.primaryUID) + + serverStatus = ServerStatus(0, + total[0], queue[0], + total[1], queue[1], + self.isInteractionWaiting(Interaction.All), + not self.core.threadManager.pause and self.isTimeDownload(), + self.core.threadManager.pause, + self.core.config['reconnect']['activated'] and self.isTimeReconnect()) + + + for pyfile in self.core.threadManager.getActiveDownloads(self.primaryUID): + serverStatus.speed += pyfile.getSpeed() #bytes/s + + return serverStatus + + @RequirePerm(Permission.All) + def getProgressInfo(self): + """ Status of all currently running tasks + + :rtype: list of :class:`ProgressInfo` + """ + return self.core.threadManager.getProgressList(self.primaryUID) + + def pauseServer(self): + """Pause server: It won't start any new downloads, but nothing gets aborted.""" + self.core.threadManager.pause = True + + def unpauseServer(self): + """Unpause server: New Downloads will be started.""" + self.core.threadManager.pause = False + + def togglePause(self): + """Toggle pause state. + + :return: new pause state + """ + self.core.threadManager.pause ^= True + return self.core.threadManager.pause + + def toggleReconnect(self): + """Toggle reconnect activation. + + :return: new reconnect state + """ + self.core.config["reconnect"]["activated"] ^= True + return self.core.config["reconnect"]["activated"] + + def freeSpace(self): + """Available free space at download directory in bytes""" + return free_space(self.core.config["general"]["download_folder"]) + + + def quit(self): + """Clean way to quit pyLoad""" + self.core.do_kill = True + + def restart(self): + """Restart pyload core""" + self.core.do_restart = True + + def getLog(self, offset=0): + """Returns most recent log entries. + + :param offset: line offset + :return: List of log entries + """ + filename = join(self.core.config['log']['log_folder'], 'log.txt') + try: + fh = open(filename, "r") + lines = fh.readlines() + fh.close() + if offset >= len(lines): + return [] + return lines[offset:] + except: + return ['No log available'] + + @RequirePerm(Permission.All) + def isTimeDownload(self): + """Checks if pyload will start new downloads according to time in config. + + :return: bool + """ + start = self.core.config['downloadTime']['start'].split(":") + end = self.core.config['downloadTime']['end'].split(":") + return compare_time(start, end) + + @RequirePerm(Permission.All) + def isTimeReconnect(self): + """Checks if pyload will try to make a reconnect + + :return: bool + """ + start = self.core.config['reconnect']['startTime'].split(":") + end = self.core.config['reconnect']['endTime'].split(":") + return compare_time(start, end) and self.core.config["reconnect"]["activated"] + + +if Api.extend(CoreApi): + del CoreApi
\ No newline at end of file diff --git a/pyload/api/DownloadApi.py b/pyload/api/DownloadApi.py new file mode 100644 index 000000000..d855dd882 --- /dev/null +++ b/pyload/api/DownloadApi.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from os.path import isabs + +from pyload.Api import Api, RequirePerm, Permission +from pyload.utils.fs import join + +from ApiComponent import ApiComponent + +class DownloadApi(ApiComponent): + """ Component to create, add, delete or modify downloads.""" + + @RequirePerm(Permission.Add) + def createPackage(self, name, folder, root, password="", site="", comment="", paused=False): + """Create a new package. + + :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 + """ + + if isabs(folder): + folder = folder.replace("/", "_") + + folder = folder.replace("http://", "").replace(":", "").replace("\\", "_").replace("..", "") + + 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 + """ + return self.addPackageChild(name, links, password, -1, False) + + @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 = "" + + pid = self.createPackage(name, folder, root, password) + self.addLinks(pid, links) + + return pid + + @RequirePerm(Permission.Add) + def addLinks(self, pid, links): + """Adds links to specific package. Initiates online status fetching. + + :param pid: package id + :param links: list of urls + """ + hoster, crypter = self.core.pluginManager.parseUrls(links) + + if hoster: + self.core.files.addLinks(hoster, pid) + self.core.threadManager.createInfoThread(hoster, pid) + + self.core.threadManager.createDecryptThread(crypter, pid) + + self.core.log.info((_("Added %d links to package") + " #%d" % pid) % len(hoster)) + self.core.files.save() + + @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 + """ + th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb") + th.write(str(data)) + th.close() + + return self.addPackage(th.name, [th.name]) + + @RequirePerm(Permission.Delete) + def deleteFiles(self, fids): + """Deletes several file entries from pyload. + + :param fids: list of file ids + """ + for fid in fids: + self.core.files.deleteFile(fid) + + self.core.files.save() + + @RequirePerm(Permission.Delete) + def deletePackages(self, pids): + """Deletes packages and containing links. + + :param pids: list of package ids + """ + for pid in pids: + self.core.files.deletePackage(pid) + + self.core.files.save() + + + @RequirePerm(Permission.Modify) + def restartPackage(self, pid): + """Restarts a package, resets every containing files. + + :param pid: package id + """ + self.core.files.restartPackage(pid) + + @RequirePerm(Permission.Modify) + def restartFile(self, fid): + """Resets file status, so it will be downloaded again. + + :param fid: file id + """ + self.core.files.restartFile(fid) + + @RequirePerm(Permission.Modify) + def recheckPackage(self, pid): + """Check online status of all files in a package, also a default action when package is added. """ + self.core.files.reCheckPackage(pid) + + @RequirePerm(Permission.Modify) + def restartFailed(self): + """Restarts all failed failes.""" + self.core.files.restartFailed() + + @RequirePerm(Permission.Modify) + def stopAllDownloads(self): + """Aborts all running downloads.""" + + pyfiles = self.core.files.cachedFiles() + for pyfile in pyfiles: + pyfile.abortDownload() + + @RequirePerm(Permission.Modify) + def stopDownloads(self, fids): + """Aborts specific downloads. + + :param fids: list of file ids + :return: + """ + pyfiles = self.core.files.cachedFiles() + for pyfile in pyfiles: + if pyfile.id in fids: + pyfile.abortDownload() + + +if Api.extend(DownloadApi): + del DownloadApi
\ No newline at end of file diff --git a/pyload/api/DownloadPreparingApi.py b/pyload/api/DownloadPreparingApi.py new file mode 100644 index 000000000..a7e32c4eb --- /dev/null +++ b/pyload/api/DownloadPreparingApi.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from itertools import chain + +from pyload.Api import Api, DownloadStatus as DS,\ + RequirePerm, Permission, OnlineCheck, LinkStatus, urlmatcher +from pyload.utils import uniqify +from pyload.utils.fs import join +from pyload.utils.packagetools import parseNames +from pyload.network.RequestFactory import getURL + +from ApiComponent import ApiComponent + +class DownloadPreparingApi(ApiComponent): + """ All kind of methods to parse links or retrieve online status """ + + @RequirePerm(Permission.Add) + def parseLinks(self, links): + """ Gets urls and returns pluginname mapped to list of matching urls. + + :param links: + :return: {plugin: urls} + """ + data, crypter = self.core.pluginManager.parseUrls(links) + plugins = {} + + for url, plugin in chain(data, crypter): + if plugin in plugins: + plugins[plugin].append(url) + else: + plugins[plugin] = [url] + + return plugins + + @RequirePerm(Permission.Add) + def checkLinks(self, links): + """ initiates online status check, will also decrypt files. + + :param links: + :return: initial set of data as :class:`OnlineCheck` instance containing the result id + """ + hoster, crypter = self.core.pluginManager.parseUrls(links) + + #: TODO: withhold crypter, derypt or add later + # initial result does not contain the crypter links + tmp = [(url, LinkStatus(url, url, -1, DS.Queued, pluginname)) for url, pluginname in hoster] + data = parseNames(tmp) + rid = self.core.threadManager.createResultThread(self.primaryUID, hoster + crypter) + + return OnlineCheck(rid, data) + + @RequirePerm(Permission.Add) + def checkContainer(self, filename, data): + """ checks online status of urls and a submitted container file + + :param filename: name of the file + :param data: file content + :return: :class:`OnlineCheck` + """ + th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb") + th.write(str(data)) + th.close() + return self.checkLinks([th.name]) + + @RequirePerm(Permission.Add) + def checkHTML(self, html, url): + """Parses html content or any arbitrary text for links and returns result of `checkURLs` + + :param html: html source + :return: + """ + urls = [] + if html: + urls += [x[0] for x in urlmatcher.findall(html)] + if url: + page = getURL(url) + urls += [x[0] for x in urlmatcher.findall(page)] + + return self.checkLinks(uniqify(urls)) + + @RequirePerm(Permission.Add) + def pollResults(self, rid): + """ Polls the result available for ResultID + + :param rid: `ResultID` + :return: `OnlineCheck`, if rid is -1 then there is no more data available + """ + result = self.core.threadManager.getInfoResult(rid) + if result and result.owner == self.primaryUID: + return result.toApiData() + + @RequirePerm(Permission.Add) + def generatePackages(self, links): + """ Parses links, generates packages names from urls + + :param links: list of urls + :return: package names mapped to urls + """ + result = parseNames((x, x) for x in links) + return result + + +if Api.extend(DownloadPreparingApi): + del DownloadPreparingApi
\ No newline at end of file diff --git a/pyload/api/FileApi.py b/pyload/api/FileApi.py new file mode 100644 index 000000000..984729b8c --- /dev/null +++ b/pyload/api/FileApi.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission, DownloadState, PackageDoesNotExists, FileDoesNotExists +from pyload.utils import uniqify + +from ApiComponent import ApiComponent + +# TODO: user context +class FileApi(ApiComponent): + """Everything related to available packages or files. Deleting, Modifying and so on.""" + + @RequirePerm(Permission.All) + def getAllFiles(self): + """ same as `getFileTree` for toplevel root and full tree""" + return self.getFileTree(-1, True) + + @RequirePerm(Permission.All) + def getFilteredFiles(self, state): + """ same as `getFilteredFileTree` for toplevel root and full tree""" + return self.getFilteredFileTree(-1, state, 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 self.core.files.getTree(pid, full, DownloadState.All) + + @RequirePerm(Permission.All) + def getFilteredFileTree(self, pid, full, state): + """ Same as `getFileTree` but only contains files with specific download state. + + :param pid: package id + :param full: go down the complete tree or only the first layer + :param state: :class:`DownloadState`, the attributes used for filtering + :return: :class:`TreeCollection` + """ + return self.core.files.getTree(pid, full, state) + + @RequirePerm(Permission.All) + def getPackageContent(self, pid): + """ Only retrieve content of a specific package. see `getFileTree`""" + return self.getFileTree(pid, False) + + @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` + """ + info = self.core.files.getPackageInfo(pid) + if not info: + raise PackageDoesNotExists(pid) + return info + + @RequirePerm(Permission.All) + def getFileInfo(self, fid): + """ Info for specific file + + :param fid: file id + :raises FileDoesNotExists: + :return: :class:`FileInfo` + + """ + info = self.core.files.getFileInfo(fid) + if not info: + raise FileDoesNotExists(fid) + return info + + def getFilePath(self, fid): + """ Internal method to get the filepath""" + info = self.getFileInfo(fid) + pack = self.core.files.getPackage(info.package) + return pack.getPath(), info.name + + @RequirePerm(Permission.All) + def findFiles(self, pattern): + return self.core.files.getTree(-1, True, DownloadState.All, pattern) + + @RequirePerm(Permission.All) + def searchSuggestions(self, pattern): + names = self.core.db.getMatchingFilenames(pattern, self.primaryUID) + # TODO: stemming and reducing the names to provide better suggestions + return uniqify(names) + + @RequirePerm(Permission.All) + def findPackages(self, tags): + pass + + @RequirePerm(Permission.Modify) + def updatePackage(self, pack): + """Allows to modify several package attributes. + + :param pid: package id + :param data: :class:`PackageInfo` + """ + pid = pack.pid + p = self.core.files.getPackage(pid) + if not p: raise PackageDoesNotExists(pid) + + #TODO: fix + for key, value in data.iteritems(): + if key == "id": continue + setattr(p, key, value) + + p.sync() + self.core.files.save() + + @RequirePerm(Permission.Modify) + def setPackageFolder(self, pid, path): + pass + + @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 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 + """ + return self.core.files.movePackage(pid, root) + + @RequirePerm(Permission.Modify) + def moveFiles(self, fids, pid): + """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: False if files can't be moved + """ + return self.core.files.moveFiles(fids, pid) + + @RequirePerm(Permission.Modify) + def orderPackage(self, pid, position): + """Set new position for a package. + + :param pid: package id + :param position: new position, 0 for very beginning + """ + self.core.files.orderPackage(pid, position) + + @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 fids: list of file ids + :param pid: package id of parent package + :param position: new position: 0 for very beginning + """ + self.core.files.orderFiles(fids, pid, position) + + +if Api.extend(FileApi): + del FileApi
\ No newline at end of file diff --git a/pyload/api/UserApi.py b/pyload/api/UserApi.py new file mode 100644 index 000000000..d6fbb2646 --- /dev/null +++ b/pyload/api/UserApi.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission + +from ApiComponent import ApiComponent + +class UserApi(ApiComponent): + """ Api methods to retrieve user profile and manage users. """ + + @RequirePerm(Permission.All) + def getUserData(self): + """ Retrieves :class:`UserData` for the currently logged in user. """ + + @RequirePerm(Permission.All) + def setPassword(self, username, old_password, new_password): + """ Changes password for specific user. User can only change their password. + Admins can change every password! """ + + def getAllUserData(self): + """ Retrieves :class:`UserData` of all exisitng users.""" + + def addUser(self, username, password): + """ Adds an user to the db. + + :param username: desired username + :param password: password for authentication + """ + + def updateUserData(self, data): + """ Change parameters of user account. """ + + def removeUser(self, uid): + """ Removes user from the db. + + :param uid: users uid + """ + + +if Api.extend(UserApi): + del UserApi
\ No newline at end of file diff --git a/pyload/api/UserInteractionApi.py b/pyload/api/UserInteractionApi.py new file mode 100644 index 000000000..f5a9e9290 --- /dev/null +++ b/pyload/api/UserInteractionApi.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission, Interaction + +from ApiComponent import ApiComponent + +class UserInteractionApi(ApiComponent): + """ Everything needed for user interaction """ + + @RequirePerm(Permission.Interaction) + def isInteractionWaiting(self, mode): + """ Check if task is waiting. + + :param mode: binary or'ed output type + :return: boolean + """ + return self.core.interactionManager.isTaskWaiting(self.primaryUID, mode) + + @RequirePerm(Permission.Interaction) + def getInteractionTasks(self, mode): + """Retrieve task for specific mode. + + :param mode: binary or'ed interaction types which should be retrieved + :rtype list of :class:`InteractionTask` + """ + tasks = self.core.interactionManager.getTasks(self.primaryUID, mode) + # retrieved tasks count as seen + for t in tasks: + t.seen = True + if t.type == Interaction.Notification: + t.setWaiting(self.core.interactionManager.CLIENT_THRESHOLD) + + return tasks + + @RequirePerm(Permission.Interaction) + def setInteractionResult(self, iid, result): + """Set Result for a interaction task. It will be immediately removed from task queue afterwards + + :param iid: interaction id + :param result: result as json string + """ + task = self.core.interactionManager.getTaskByID(iid) + if task and self.primaryUID == task.owner: + task.setResult(result) + + @RequirePerm(Permission.Interaction) + def getAddonHandler(self): + pass + + @RequirePerm(Permission.Interaction) + def callAddonHandler(self, plugin, func, pid_or_fid): + pass + + @RequirePerm(Permission.Download) + def generateDownloadLink(self, fid, timeout): + pass + + +if Api.extend(UserInteractionApi): + del UserInteractionApi
\ No newline at end of file diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py new file mode 100644 index 000000000..a2b292a27 --- /dev/null +++ b/pyload/api/__init__.py @@ -0,0 +1,8 @@ +__all__ = ["CoreApi", "ConfigApi", "DownloadApi", "DownloadPreparingApi", "FileApi", + "UserInteractionApi", "AccountApi", "AddonApi", "UserApi"] + +# Import all components +# from .import * +# Above does not work in py 2.5 +for name in __all__: + __import__(__name__ + "." + name)
\ No newline at end of file diff --git a/module/cli/AddPackage.py b/pyload/cli/AddPackage.py index a73401586..a73401586 100644 --- a/module/cli/AddPackage.py +++ b/pyload/cli/AddPackage.py diff --git a/pyload/cli/Cli.py b/pyload/cli/Cli.py new file mode 100644 index 000000000..9285ad3a2 --- /dev/null +++ b/pyload/cli/Cli.py @@ -0,0 +1,586 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#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 +#the Free Software Foundation; either version 3 of the License, +#or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +### +from __future__ import with_statement +from getopt import GetoptError, getopt + +import pyload.common.pylgettext as gettext +import os +from os import _exit +from os.path import join, exists, abspath, basename +import sys +from sys import exit +from threading import Thread, Lock +from time import sleep +from traceback import print_exc + +import ConfigParser + +from codecs import getwriter + +if os.name == "nt": + enc = "cp850" +else: + enc = "utf8" + +sys.stdout = getwriter(enc)(sys.stdout, errors="replace") + +from pyload import InitHomeDir +from pyload.cli.printer import * +from pyload.cli import AddPackage, ManageFiles + +from pyload.Api import Destination +from pyload.utils import formatSize, decode +from pyload.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed +from pyload.lib.Getch import Getch +from pyload.lib.rename_process import renameProcess + +class Cli: + def __init__(self, client, command): + self.client = client + self.command = command + + if not self.command: + renameProcess('pyLoadCli') + self.getch = Getch() + self.input = "" + self.inputline = 0 + self.lastLowestLine = 0 + self.menuline = 0 + + self.lock = Lock() + + #processor funcions, these will be changed dynamically depending on control flow + self.headerHandler = self #the download status + self.bodyHandler = self #the menu section + self.inputHandler = self + + os.system("clear") + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.thread = RefreshThread(self) + self.thread.start() + + self.start() + else: + self.processCommand() + + def reset(self): + """ reset to initial main menu """ + self.input = "" + self.headerHandler = self.bodyHandler = self.inputHandler = self + + def start(self): + """ main loop. handle input """ + while True: + #inp = raw_input() + inp = self.getch.impl() + if ord(inp) == 3: + os.system("clear") + sys.exit() # ctrl + c + elif ord(inp) == 13: #enter + try: + self.lock.acquire() + self.inputHandler.onEnter(self.input) + + except Exception, e: + println(2, red(e)) + finally: + self.lock.release() + + elif ord(inp) == 127: + self.input = self.input[:-1] #backspace + try: + self.lock.acquire() + self.inputHandler.onBackSpace() + finally: + self.lock.release() + + elif ord(inp) == 27: #ugly symbol + pass + else: + self.input += inp + try: + self.lock.acquire() + self.inputHandler.onChar(inp) + finally: + self.lock.release() + + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + + def refresh(self): + """refresh screen""" + + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.lock.acquire() + + self.menuline = self.headerHandler.renderHeader(3) + 1 + println(self.menuline - 1, "") + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + self.lock.release() + + + def setInput(self, string=""): + self.input = string + + def setHandler(self, klass): + #create new handler with reference to cli + self.bodyHandler = self.inputHandler = klass(self) + self.input = "" + + def renderHeader(self, line): + """ prints download status """ + #print updated information + # print "\033[J" #clear screen + # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + # self.println(2, "") + # self.println(3, white(_("%s Downloads:") % (len(data)))) + + data = self.client.statusDownloads() + speed = 0 + + println(line, white(_("%s Downloads:") % (len(data)))) + line += 1 + + for download in data: + if download.status == 12: # downloading + percent = download.percent + z = percent / 4 + speed += download.speed + println(line, cyan(download.name)) + line += 1 + println(line, + blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _( + " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green( + download.format_size) + _(" Finished in: ") + green(download.format_eta) + _( + " ID: ") + green(download.fid)) + line += 1 + if download.status == 5: + println(line, cyan(download.name)) + line += 1 + println(line, _("waiting: ") + green(download.format_wait)) + line += 1 + + println(line, "") + line += 1 + status = self.client.statusServer() + if status.pause: + paused = _("Status:") + " " + red(_("paused")) + else: + paused = _("Status:") + " " + red(_("running")) + + println(line,"%s %s: %s %s: %s %s: %s" % ( + paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red( + status.queue), _("Total"), red(status.total))) + + return line + 1 + + def renderBody(self, line): + """ prints initial menu """ + println(line, white(_("Menu:"))) + println(line + 1, "") + println(line + 2, mag("1.") + _(" Add Links")) + println(line + 3, mag("2.") + _(" Manage Queue")) + println(line + 4, mag("3.") + _(" Manage Collector")) + println(line + 5, mag("4.") + _(" (Un)Pause Server")) + println(line + 6, mag("5.") + _(" Kill Server")) + println(line + 7, mag("6.") + _(" Quit")) + + return line + 8 + + def renderFooter(self, line): + """ prints out the input line with input """ + println(line, "") + line += 1 + + println(line, white(" Input: ") + decode(self.input)) + + #clear old output + if line < self.lastLowestLine: + for i in range(line + 1, self.lastLowestLine + 1): + println(i, "") + + self.lastLowestLine = line + + #set cursor to position + print "\033[" + str(self.inputline) + ";0H" + + def onChar(self, char): + """ default no special handling for single chars """ + if char == "1": + self.setHandler(AddPackage) + elif char == "2": + self.setHandler(ManageFiles) + elif char == "3": + self.setHandler(ManageFiles) + self.bodyHandler.target = Destination.Collector + elif char == "4": + self.client.togglePause() + self.setInput() + elif char == "5": + self.client.kill() + self.client.close() + sys.exit() + elif char == "6": + os.system('clear') + sys.exit() + + def onEnter(self, inp): + pass + + def onBackSpace(self): + pass + + def processCommand(self): + command = self.command[0] + args = [] + if len(self.command) > 1: + args = self.command[1:] + + if command == "status": + files = self.client.statusDownloads() + + if not files: + print "No downloads running." + + for download in files: + if download.status == 12: # downloading + print print_status(download) + print "\tDownloading: %s @ %s/s\t %s (%s%%)" % ( + download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft), + download.percent) + elif download.status == 5: + print print_status(download) + print "\tWaiting: %s" % download.format_wait + else: + print print_status(download) + + elif command == "queue": + print_packages(self.client.getQueueData()) + + elif command == "collector": + print_packages(self.client.getCollectorData()) + + elif command == "add": + if len(args) < 2: + print _("Please use this syntax: add <Package name> <link> <link2> ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Queue, "") + + elif command == "add_coll": + if len(args) < 2: + print _("Please use this syntax: add <Package name> <link> <link2> ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Collector, "") + + elif command == "del_file": + self.client.deleteFiles([int(x) for x in args]) + print "Files deleted." + + elif command == "del_package": + self.client.deletePackages([int(x) for x in args]) + print "Packages deleted." + + elif command == "move": + for pid in args: + pack = self.client.getPackageInfo(int(pid)) + self.client.movePackage((pack.dest + 1) % 2, pack.pid) + + elif command == "check": + print _("Checking %d links:") % len(args) + print + rid = self.client.checkOnlineStatus(args).rid + self.printOnlineCheck(self.client, rid) + + + elif command == "check_container": + path = args[0] + if not exists(join(owd, path)): + print _("File does not exists.") + return + + f = open(join(owd, path), "rb") + content = f.read() + f.close() + + rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid + self.printOnlineCheck(self.client, rid) + + + elif command == "pause": + self.client.pauseServer() + + elif command == "unpause": + self.client.unpauseServer() + + elif command == "toggle": + self.client.togglePause() + + elif command == "kill": + self.client.kill() + elif command == "restart_file": + for x in args: + self.client.restartFile(int(x)) + print "Files restarted." + elif command == "restart_package": + for pid in args: + self.client.restartPackage(int(pid)) + print "Packages restarted." + + else: + print_commands() + + def printOnlineCheck(self, client, rid): + while True: + sleep(1) + result = client.pollResults(rid) + for url, status in result.data.iteritems(): + if status.status == 2: check = "Online" + elif status.status == 1: check = "Offline" + else: check = "Unknown" + + print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check) + + if result.rid == -1: break + + +class RefreshThread(Thread): + def __init__(self, cli): + Thread.__init__(self) + self.setDaemon(True) + self.cli = cli + + def run(self): + while True: + sleep(1) + try: + self.cli.refresh() + except ConnectionClosed: + os.system("clear") + print _("pyLoad was terminated") + _exit(0) + except Exception, e: + println(2, red(str(e))) + self.cli.reset() + print_exc() + + +def print_help(config): + print + print "pyLoadCli Copyright (c) 2008-2012 the pyLoad Team" + print + print "Usage: [python] pyLoadCli.py [options] [command]" + print + print "<Commands>" + print "See pyLoadCli.py -c for a complete listing." + print + print "<Options>" + print " -i, --interactive", " Start in interactive mode" + print + print " -u, --username=", " " * 2, "Specify user name" + print " --pw=<password>", " " * 2, "Password" + 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 text" + print " -c, --commands", " " * 3, "List all available commands" + print + + +def print_packages(data): + for pack in data: + print "Package %s (#%s):" % (pack.name, pack.pid) + for download in pack.links: + print "\t" + print_file(download) + print + + +def print_file(download): + return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "plugin": download.plugin + } + + +def print_status(download): + return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "size": download.format_size + } + + +def print_commands(): + commands = [("status", _("Prints server status")), + ("queue", _("Prints downloads in queue")), + ("collector", _("Prints downloads in collector")), + ("add <name> <link1> <link2>...", _("Adds package to queue")), + ("add_coll <name> <link1> <link2>...", _("Adds package to collector")), + ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")), + ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")), + ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")), + ("restart_file <fid> <fid2>...", _("Restart files")), + ("restart_package <pid> <pid2>...", _("Restart packages")), + ("check <container|url> ...", _("Check online status, works with local container")), + ("check_container path", _("Checks online status of a container file")), + ("pause", _("Pause the server")), + ("unpause", _("continue downloads")), + ("toggle", _("Toggle pause/unpause")), + ("kill", _("kill server")), ] + + print _("List of commands:") + print + for c in commands: + print "%-35s %s" % c + + +def writeConfig(opts): + try: + with open(join(homedir, ".pyloadcli"), "w") as cfgfile: + cfgfile.write("[cli]") + for opt in opts: + cfgfile.write("%s=%s\n" % (opt, opts[opt])) + except: + print _("Couldn't write user config file") + + +def main(): + config = {"addr": "127.0.0.1", "port": "7227", "language": "en"} + try: + config["language"] = os.environ["LANG"][0:2] + except: + pass + + if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "": + config["language"] = "en" + + configFile = ConfigParser.ConfigParser() + configFile.read(join(homedir, ".pyloadcli")) + + if configFile.has_section("cli"): + for opt in configFile.items("cli"): + config[opt[0]] = opt[1] + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoadCli", join(pypath, "locale"), + languages=[config["language"],"en"],fallback=True) + translation.install(unicode=True) + + interactive = False + command = None + username = "" + password = "" + + shortOptions = 'iu:p:a:hcl:' + longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="] + + try: + opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions) + for option, params in opts: + if option in ("-i", "--interactive"): + interactive = True + elif option in ("-u", "--username"): + username = params + elif option in ("-a", "--address"): + config["addr"] = params + elif option in ("-p", "--port"): + config["port"] = params + elif option in ("-l", "--language"): + config["language"] = params + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoadCli", join(pypath, "locale"), + languages=[config["language"],"en"],fallback=True) + translation.install(unicode=True) + elif option in ("-h", "--help"): + print_help(config) + exit() + elif option in ("--pw"): + password = params + elif option in ("-c", "--comands"): + print_commands() + exit() + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:]) + print_help(config) + exit() + + if len(extraparams) >= 1: + command = extraparams + + client = False + + if interactive: + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + pass + except NoSSL: + print _("You need py-openssl to connect to this pyLoad core.") + exit() + except NoConnection: + config["addr"] = False + config["port"] = False + + if not client: + if not config["addr"]: config["addr"] = raw_input(_("Address: ")) + if not config["port"]: config["port"] = raw_input(_("Port: ")) + if not username: username = raw_input(_("Username: ")) + if not password: + from getpass import getpass + + password = getpass(_("Password: ")) + + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], + "port": config["port"]}) + + else: + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], + "port": config["port"]}) + except NoSSL: + print _("You need py-openssl to connect to this pyLoad core.") + + if interactive and command: print _("Interactive mode ignored since you passed some commands.") + + if client: + writeConfig(config) + cli = Cli(client, command) diff --git a/module/cli/Handler.py b/pyload/cli/Handler.py index 476d09386..476d09386 100644 --- a/module/cli/Handler.py +++ b/pyload/cli/Handler.py diff --git a/pyload/cli/ManageFiles.py b/pyload/cli/ManageFiles.py new file mode 100644 index 000000000..2304af355 --- /dev/null +++ b/pyload/cli/ManageFiles.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2011 RaNaN +# +#This program is free software; you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation; either version 3 of the License, +#or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +### + +from itertools import islice +from time import time + +from Handler import Handler +from printer import * + +from pyload.Api import Destination, PackageData + +class ManageFiles(Handler): + """ possibility to manage queue/collector """ + + def init(self): + self.target = Destination.Queue + self.pos = 0 #position in queue + self.package = -1 #chosen package + self.mode = "" # move/delete/restart + + self.cache = None + self.links = None + self.time = 0 + + def onChar(self, char): + if char in ("m", "d", "r"): + self.mode = char + self.setInput() + elif char == "p": + self.pos = max(0, self.pos - 5) + self.backspace() + elif char == "n": + self.pos += 5 + self.backspace() + + def onBackSpace(self): + if not self.input and self.mode: + self.mode = "" + if not self.input and self.package > -1: + self.package = -1 + + def onEnter(self, input): + if input == "0": + self.cli.reset() + elif self.package < 0 and self.mode: + #mode select + packs = self.parseInput(input) + if self.mode == "m": + [self.client.movePackage((self.target + 1) % 2, x) for x in packs] + elif self.mode == "d": + self.client.deletePackages(packs) + elif self.mode == "r": + [self.client.restartPackage(x) for x in packs] + + elif self.mode: + #edit links + links = self.parseInput(input, False) + + if self.mode == "d": + self.client.deleteFiles(links) + elif self.mode == "r": + map(self.client.restartFile, links) + + else: + #look into package + try: + self.package = int(input) + except: + pass + + self.cache = None + self.links = None + self.pos = 0 + self.mode = "" + self.setInput() + + + def renderBody(self, line): + if self.package < 0: + println(line, white(_("Manage Packages:"))) + else: + println(line, white((_("Manage Links:")))) + line += 1 + + if self.mode: + if self.mode == "m": + println(line, _("What do you want to move?")) + elif self.mode == "d": + println(line, _("What do you want to delete?")) + elif self.mode == "r": + println(line, _("What do you want to restart?")) + + println(line + 1, "Enter a single number, comma separated numbers or ranges. e.g.: 1,2,3 or 1-3.") + line += 2 + else: + 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 + + if self.package < 0: + #print package info + pack = self.getPackages() + i = 0 + for value in islice(pack, self.pos, self.pos + 5): + try: + println(line, mag(str(value.pid)) + ": " + value.name) + line += 1 + i += 1 + except Exception, e: + pass + for x in range(5 - i): + println(line, "") + line += 1 + else: + #print links info + pack = self.getLinks() + i = 0 + for value in islice(pack.links, self.pos, self.pos + 5): + try: + println(line, mag(value.fid) + ": %s | %s | %s" % ( + value.name, value.statusmsg, value.plugin)) + line += 1 + i += 1 + except Exception, e: + pass + for x in range(5 - i): + println(line, "") + line += 1 + + println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next")) + println(line + 1, mag("0.") + _(" back to main menu")) + + return line + 2 + + + def getPackages(self): + if self.cache and self.time + 2 < time(): + return self.cache + + if self.target == Destination.Queue: + data = self.client.getQueue() + else: + data = self.client.getCollector() + + + self.cache = data + self.time = time() + + return data + + def getLinks(self): + if self.links and self.time + 1 < time(): + return self.links + + try: + data = self.client.getPackageData(self.package) + except: + data = PackageData(links=[]) + + self.links = data + self.time = time() + + return data + + def parseInput(self, inp, package=True): + inp = inp.strip() + if "-" in inp: + l, n, h = inp.partition("-") + l = int(l) + h = int(h) + r = range(l, h + 1) + + ret = [] + if package: + for p in self.cache: + if p.pid in r: + ret.append(p.pid) + else: + for l in self.links.links: + if l.lid in r: + ret.append(l.lid) + + return ret + + else: + return [int(x) for x in inp.split(",")] diff --git a/module/cli/__init__.py b/pyload/cli/__init__.py index fa8a09291..fa8a09291 100644 --- a/module/cli/__init__.py +++ b/pyload/cli/__init__.py diff --git a/module/cli/printer.py b/pyload/cli/printer.py index c62c1800e..c62c1800e 100644 --- a/module/cli/printer.py +++ b/pyload/cli/printer.py diff --git a/pyload/config/ConfigManager.py b/pyload/config/ConfigManager.py new file mode 100644 index 000000000..33bd151c3 --- /dev/null +++ b/pyload/config/ConfigManager.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from new_collections import OrderedDict + +from pyload.Api import InvalidConfigSection +from pyload.utils import json + +from ConfigParser import ConfigParser + +from convert import to_input, from_string + +def convertKeyError(func): + """ converts KeyError into InvalidConfigSection """ + + def conv(*args, **kwargs): + try: + return func(*args, **kwargs) + except KeyError: + raise InvalidConfigSection(args[1]) + + return conv + + +class ConfigManager(ConfigParser): + """ Manages the core config and configs for addons and single user. + Has similar interface to ConfigParser. """ + + def __init__(self, core, parser): + # No __init__ call to super class is needed! + + self.core = core + self.db = core.db + # The config parser, holding the core config + self.parser = parser + + # similar to parser, separated meta data and values + self.config = OrderedDict() + + # Value cache for multiple user configs + # Values are populated from db on first access + # Entries are saved as (user, section) keys + self.values = {} + # TODO: similar to a cache, could be deleted periodically + + def save(self): + self.parser.save() + + @convertKeyError + def get(self, section, option, user=None): + """get config value, core config only available for admins. + if user is not valid default value will be returned""" + + # Core config loaded from parser, when no user is given or he is admin + if section in self.parser and user is None: + return self.parser.get(section, option) + else: + # We need the id and not the instance + # Will be None for admin user and so the same as internal access + try: + # Check if this config exists + # Configs without meta data can not be loaded! + data = self.config[section].config[option] + return self.loadValues(user, section)[option] + except KeyError: + pass # Returns default value later + + return self.config[section].config[option].input.default_value + + def loadValues(self, user, section): + if (user, section) not in self.values: + conf = self.db.loadConfig(section, user) + try: + self.values[user, section] = json.loads(conf) if conf else {} + except ValueError: # Something did go wrong when parsing + self.values[user, section] = {} + self.core.print_exc() + + return self.values[user, section] + + @convertKeyError + def set(self, section, option, value, sync=True, user=None): + """ set config value """ + + changed = False + if section in self.parser and user is None: + changed = self.parser.set(section, option, value, sync) + else: + data = self.config[section].config[option] + value = from_string(value, data.input.type) + old_value = self.get(section, option) + + # Values will always be saved to db, sync is ignored + if value != old_value: + changed = True + self.values[user, section][option] = value + if sync: self.saveValues(user, section) + + if changed: self.core.evm.dispatchEvent("config:changed", section, option, value) + return changed + + def saveValues(self, user, section): + if section in self.parser and user is None: + self.save() + elif (user, section) in self.values: + self.db.saveConfig(section, json.dumps(self.values[user, section]), user) + + def delete(self, section, user=None): + """ Deletes values saved in db and cached values for given user, NOT meta data + Does not trigger an error when nothing was deleted. """ + if (user, section) in self.values: + del self.values[user, section] + + self.db.deleteConfig(section, user) + self.core.evm.dispatchEvent("config:deleted", section, user) + + def iterCoreSections(self): + return self.parser.iterSections() + + def iterSections(self, user=None): + """ Yields: section, metadata, values """ + values = self.db.loadConfigsForUser(user) + + # Every section needs to be json decoded + for section, data in values.items(): + try: + values[section] = json.loads(data) if data else {} + except ValueError: + values[section] = {} + self.core.print_exc() + + for name, config in self.config.iteritems(): + yield name, config, values[name] if name in values else {} + + def getSection(self, section, user=None): + if section in self.parser and user is None: + return self.parser.getSection(section) + + values = self.loadValues(user, section) + return self.config.get(section), values diff --git a/pyload/config/ConfigParser.py b/pyload/config/ConfigParser.py new file mode 100644 index 000000000..0f96fd8b9 --- /dev/null +++ b/pyload/config/ConfigParser.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement +from os.path import exists +from gettext import gettext +from new_collections import namedtuple, OrderedDict + +from pyload.Api import Input, InputType +from pyload.utils.fs import chmod + +from default import make_config +from convert import to_configdata, from_string + +CONF_VERSION = 2 +SectionTuple = namedtuple("SectionTuple", "label description explanation config") + + +class ConfigParser: + """ + Holds and manages the configuration + meta data for config read from file. + """ + + CONFIG = "pyload.conf" + + def __init__(self, config=None): + + if config: self.CONFIG = config + + # Meta data information + self.config = OrderedDict() + # The actual config values + self.values = {} + + self.checkVersion() + + self.loadDefault() + self.parseValues(self.CONFIG) + + def loadDefault(self): + make_config(self) + + def checkVersion(self): + """Determines if config needs to be deleted""" + if exists(self.CONFIG): + f = open(self.CONFIG, "rb") + v = f.readline() + f.close() + v = v[v.find(":") + 1:].strip() + + if not v or int(v) < CONF_VERSION: + f = open(self.CONFIG, "wb") + f.write("version: " + str(CONF_VERSION)) + f.close() + print "Old version of %s deleted" % self.CONFIG + else: + f = open(self.CONFIG, "wb") + f.write("version:" + str(CONF_VERSION)) + f.close() + + 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""" + + configs = [] + f = open(self.CONFIG, "wb") + configs.append(f) + chmod(self.CONFIG, 0600) + f.write("version: %i\n\n" % CONF_VERSION) + + for section, data in self.config.iteritems(): + 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() + + def __getitem__(self, section): + """provides dictionary like access: c['section']['option']""" + return Section(self, section) + + def __contains__(self, section): + """ checks if parser contains section """ + return section in self.config + + def get(self, section, option): + """get value or default""" + try: + return self.values[section][option] + except KeyError: + return self.config[section].config[option].input.default_value + + def set(self, section, option, value, sync=True): + """set value""" + + data = self.config[section].config[option] + value = from_string(value, data.input.type) + old_value = self.get(section, option) + + # only save when different values + if value != old_value: + if section not in self.values: self.values[section] = {} + self.values[section][option] = value + if sync: + self.save() + return True + + return False + + def getMetaData(self, section, option): + """ get all config data for an option """ + return self.config[section].config[option] + + def iterSections(self): + """ Yields section, config info, values, for all sections """ + + for name, config in self.config.iteritems(): + yield name, config, self.values[name] if name in self.values else {} + + def getSection(self, section): + """ Retrieves single config as tuple (section, values) """ + return self.config[section], self.values[section] if section in self.values else {} + + def addConfigSection(self, section, label, desc, expl, config): + """Adds a section to the config. `config` is a list of config tuple as used in plugin api defined as: + The order of the config elements is preserved with OrderedDict + """ + d = OrderedDict() + + for entry in config: + name, data = to_configdata(entry) + d[name] = data + + data = SectionTuple(gettext(label), gettext(desc), gettext(expl), d) + self.config[section] = data + + +class Section: + """provides dictionary like access for configparser""" + + def __init__(self, parser, section): + """Constructor""" + self.parser = parser + self.section = section + + def __getitem__(self, item): + """getitem""" + return self.parser.get(self.section, item) + + def __setitem__(self, item, value): + """setitem""" + self.parser.set(self.section, item, value) diff --git a/pyload/config/__init__.py b/pyload/config/__init__.py new file mode 100644 index 000000000..4b31e848b --- /dev/null +++ b/pyload/config/__init__.py @@ -0,0 +1 @@ +__author__ = 'christian' diff --git a/pyload/config/convert.py b/pyload/config/convert.py new file mode 100644 index 000000000..59f814020 --- /dev/null +++ b/pyload/config/convert.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +from gettext import gettext + +from new_collections import namedtuple + +from pyload.Api import Input, InputType +from pyload.utils import decode, to_bool + +ConfigData = namedtuple("ConfigData", "label description input") + +# Maps old config formats to new values +input_dict = { + "int": InputType.Int, + "bool": InputType.Bool, + "time": InputType.Time, + "file": InputType.File, + "list": InputType.List, + "folder": InputType.Folder +} + + +def to_input(typ): + """ Converts old config format to input type""" + return input_dict.get(typ, InputType.Text) + + +def to_configdata(entry): + if len(entry) != 4: + raise ValueError("Config entry must be of length 4") + + # Values can have different roles depending on the two config formats + conf_name, type_label, label_desc, default_input = entry + + # name, label, desc, input + if isinstance(default_input, Input): + _input = default_input + conf_label = type_label + conf_desc = label_desc + # name, type, label, default + else: + _input = Input(to_input(type_label)) + _input.default_value = from_string(default_input, _input.type) + conf_label = label_desc + conf_desc = "" + + return conf_name, ConfigData(gettext(conf_label), gettext(conf_desc), _input) + + +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 == InputType.Int: + return int(value) + elif typ == InputType.Bool: + return to_bool(value) + elif typ == InputType.Time: + if not value: value = "0:00" + if not ":" in value: value += ":00" + return value + else: + return value
\ No newline at end of file diff --git a/pyload/config/default.py b/pyload/config/default.py new file mode 100644 index 000000000..8e2dcae74 --- /dev/null +++ b/pyload/config/default.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +""" +Configuration layout for default base config +""" + +#TODO: write tooltips and descriptions +#TODO: use apis config related classes + +def make_config(config): + # Check if gettext is installed + _ = lambda x: x + + config.addConfigSection("remote", _("Remote"), _("Description"), _("Long description"), + [ + ("activated", "bool", _("Activated"), True), + ("port", "int", _("Port"), 7227), + ("listenaddr", "ip", _("Address"), "0.0.0.0"), + ]) + + config.addConfigSection("log", _("Log"), _("Description"), _("Long description"), + [ + ("log_size", "int", _("Size in kb"), 100), + ("log_folder", "folder", _("Folder"), "Logs"), + ("file_log", "bool", _("File Log"), True), + ("log_count", "int", _("Count"), 5), + ("log_rotate", "bool", _("Log Rotate"), True), + ("console_color", "No;Light;Full", _("Colorize Console"), "Light"), + ]) + + config.addConfigSection("permission", _("Permissions"), _("Description"), _("Long description"), + [ + ("group", "str", _("Groupname"), "users"), + ("change_dl", "bool", _("Change Group and User of Downloads"), False), + ("change_file", "bool", _("Change file mode of downloads"), False), + ("user", "str", _("Username"), "user"), + ("file", "str", _("Filemode for Downloads"), "0644"), + ("change_group", "bool", _("Change group of running process"), False), + ("folder", "str", _("Folder Permission mode"), "0755"), + ("change_user", "bool", _("Change user of running process"), False), + ]) + + config.addConfigSection("general", _("General"), _("Description"), _("Long description"), + [ + ("language", "en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR", _("Language"), "en"), + ("download_folder", "folder", _("Download Folder"), "Downloads"), + ("checksum", "bool", _("Use Checksum"), False), + ("folder_per_package", "bool", _("Create folder for each package"), True), + ("debug_mode", "bool", _("Debug Mode"), False), + ("min_free_space", "int", _("Min Free Space (MB)"), 200), + ("renice", "int", _("CPU Priority"), 0), + ]) + + config.addConfigSection("ssl", _("SSL"), _("Description"), _("Long description"), + [ + ("cert", "file", _("SSL Certificate"), "ssl.crt"), + ("activated", "bool", _("Activated"), False), + ("key", "file", _("SSL Key"), "ssl.key"), + ]) + + config.addConfigSection("webinterface", _("Webinterface"), _("Description"), _("Long description"), + [ + ("template", "str", _("Template"), "default"), + ("activated", "bool", _("Activated"), True), + ("prefix", "str", _("Path Prefix"), ""), + ("server", "auto;threaded;fallback;fastcgi", _("Server"), "auto"), + ("force_server", "str", _("Favor specific server"), ""), + ("host", "ip", _("IP"), "0.0.0.0"), + ("https", "bool", _("Use HTTPS"), False), + ("port", "int", _("Port"), 8001), + ("develop", "bool", _("Development mode"), False), + ]) + + config.addConfigSection("proxy", _("Proxy"), _("Description"), _("Long description"), + [ + ("username", "str", _("Username"), ""), + ("proxy", "bool", _("Use Proxy"), False), + ("address", "str", _("Address"), "localhost"), + ("password", "password", _("Password"), ""), + ("type", "http;socks4;socks5", _("Protocol"), "http"), + ("port", "int", _("Port"), 7070), + ]) + + config.addConfigSection("reconnect", _("Reconnect"), _("Description"), _("Long description"), + [ + ("endTime", "time", _("End"), "0:00"), + ("activated", "bool", _("Use Reconnect"), False), + ("method", "str", _("Method"), "./reconnect.sh"), + ("startTime", "time", _("Start"), "0:00"), + ]) + + config.addConfigSection("download", _("Download"), _("Description"), _("Long description"), + [ + ("max_downloads", "int", _("Max Parallel Downloads"), 3), + ("limit_speed", "bool", _("Limit Download Speed"), False), + ("interface", "str", _("Download interface to bind (ip or Name)"), ""), + ("skip_existing", "bool", _("Skip already existing files"), False), + ("max_speed", "int", _("Max Download Speed in kb/s"), -1), + ("ipv6", "bool", _("Allow IPv6"), False), + ("chunks", "int", _("Max connections for one download"), 3), + ("restart_failed", "bool", _("Restart failed downloads on startup"), False), + ]) + + config.addConfigSection("downloadTime", _("Download Time"), _("Description"), _("Long description"), + [ + ("start", "time", _("Start"), "0:00"), + ("end", "time", _("End"), "0:00"), + ]) diff --git a/pyload/database/AccountDatabase.py b/pyload/database/AccountDatabase.py new file mode 100644 index 000000000..3ca841fbc --- /dev/null +++ b/pyload/database/AccountDatabase.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.Api import AccountInfo +from pyload.database import DatabaseMethods, queue, async + + +class AccountMethods(DatabaseMethods): + @queue + def loadAccounts(self): + self.c.execute('SELECT plugin, loginname, owner, activated, shared, password, options FROM accounts') + + return [(AccountInfo(r[0], r[1], r[2], activated=r[3] is 1, shared=r[4] is 1), r[5], r[6]) for r in self.c] + + @async + def saveAccounts(self, data): + + self.c.executemany( + 'INSERT INTO accounts(plugin, loginname, owner, activated, shared, password, options) VALUES(?,?,?,?,?,?,?)', + data) + + @async + def removeAccount(self, plugin, loginname): + self.c.execute('DELETE FROM accounts WHERE plugin=? AND loginname=?', (plugin, loginname)) + + @queue + def purgeAccounts(self): + self.c.execute('DELETE FROM accounts') + +AccountMethods.register()
\ No newline at end of file diff --git a/pyload/database/ConfigDatabase.py b/pyload/database/ConfigDatabase.py new file mode 100644 index 000000000..0c0dd72dd --- /dev/null +++ b/pyload/database/ConfigDatabase.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.database import DatabaseMethods, queue, async + +class ConfigMethods(DatabaseMethods): + + @async + def saveConfig(self, plugin, config, user=None): + if user is None: user = -1 + self.c.execute('INSERT INTO settings(plugin, config, user) VALUES(?,?,?)', (plugin, config, user)) + + + @queue + def loadConfig(self, plugin, user=None): + if user is None: user = -1 + self.c.execute('SELECT config FROM settings WHERE plugin=? AND user=?', (plugin, user)) + + r = self.c.fetchone() + return r[0] if r else "" + + @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 + + @queue + def loadConfigsForUser(self, user=None): + if user is None: user = -1 + self.c.execute('SELECT plugin, config FROM settings WHERE user=?', (user,)) + configs = {} + for r in self.c: + configs[r[0]] = r[1] + + return configs + + @async + def clearAllConfigs(self): + self.c.execute('DELETE FROM settings') + + +ConfigMethods.register()
\ No newline at end of file diff --git a/pyload/database/DatabaseBackend.py b/pyload/database/DatabaseBackend.py new file mode 100644 index 000000000..df8c6e704 --- /dev/null +++ b/pyload/database/DatabaseBackend.py @@ -0,0 +1,500 @@ +#!/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 Thread, Event +from shutil import move + +from Queue import Queue +from traceback import print_exc + +from pyload.utils.fs import chmod, exists, remove + +try: + from pysqlite2 import dbapi2 as sqlite3 +except: + import sqlite3 + +DB = None +DB_VERSION = 6 + + +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 + + @classmethod + 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() + + def __repr__(self): + from os.path import basename + + frame = self.frame.f_back + output = "" + for i in range(5): + output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name) + frame = frame.f_back + del frame + del self.frame + + return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result) + + def processJob(self): + try: + self.result = self.f(*self.args, **self.kwargs) + except Exception, e: + print_exc() + try: + print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e + except: + pass + + self.exception = e + finally: + self.done.set() + + def wait(self): + self.done.wait() + + +class DatabaseBackend(Thread): + subs = [] + + 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.error = None + self.running = Event() + + self.jobs = Queue() + + set_DB(self) + + def setup(self): + """ *MUST* be called before db can be used !""" + self.start() + self.running.wait() + + def init(self): + """main loop, which executes commands""" + + 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(_("Database was deleted due to incompatible version.")) + except: + print "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() + + + def run(self): + try: + self.init() + except Exception, e: + self.error = e + finally: + self.running.set() + + while True: + j = self.jobs.get() + if j == "quit": + self.c.close() + self.conn.commit() + self.conn.close() + self.closing.set() + break + j.processJob() + + + def shutdown(self): + self.running.clear() + self.closing = Event() + self.jobs.put("quit") + self.closing.wait(1) + + def _checkVersion(self): + """ 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(self.VERSION_FILE, "rb") + v = int(f.read().strip()) + f.close() + + return v + + def _convertDB(self, v): + try: + return getattr(self, "_convertV%i" % v)() + except: + return False + + #--convert scripts start + + def _convertV6(self): + return False + + #--convert scripts end + + def _createTables(self): + """create tables for database""" + + 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,' + '"tags" TEXT DEFAULT "" NOT NULL,' + '"shared" 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 DEFAULT -1, ' + '"activated" INTEGER NOT NULL DEFAULT 1, ' + '"password" TEXT DEFAULT "", ' + '"shared" INTEGER NOT NULL 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(fid) FROM files') + fid = self.c.fetchone()[0] + fid = int(fid) if fid else 0 + self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "files")) + + self.c.execute('SELECT max(pid) FROM packages') + pid = self.c.fetchone()[0] + pid = int(pid) if pid else 0 + self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages")) + + self.c.execute('VACUUM') + + + def createCursor(self): + return self.conn.cursor() + + @async + def commit(self): + self.conn.commit() + + @queue + def syncSave(self): + self.conn.commit() + + @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): + # Raise previous error of initialization + if self.error: raise self.error + args = (self, ) + args + job = DatabaseJob(f, *args, **kwargs) + self.jobs.put(job) + + # 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(): + @queue + def insert(db): + c = db.createCursor() + for i in range(1000): + c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) + + @async + def insert2(db): + c = db.createCursor() + for i in range(1000 * 1000): + c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) + + @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() + + @queue + def error(db): + c = db.createCursor() + print "a" + c.execute("SELECT myerror FROM storage WHERE identifier=? AND key=?", ("foo", i)) + print "e" + + db.registerSub(Test) + from time import time + + start = time() + for i in range(100): + db.insert() + end = time() + print end - start + + start = time() + db.insert2() + end = time() + print end - start + + db.error() + diff --git a/pyload/database/FileDatabase.py b/pyload/database/FileDatabase.py new file mode 100644 index 000000000..a07486443 --- /dev/null +++ b/pyload/database/FileDatabase.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python +# -*- 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 pyload.Api import DownloadInfo, FileInfo, PackageInfo, PackageStats, DownloadState as DS, state_string +from pyload.database import DatabaseMethods, queue, async, inner +from pyload.utils.filetypes import guess_type + +zero_stats = PackageStats(0, 0, 0, 0) + + +class FileMethods(DatabaseMethods): + @queue + def filecount(self): + """returns number of files, currently only used for debugging""" + self.c.execute("SELECT COUNT(*) FROM files") + return self.c.fetchone()[0] + + @queue + def downloadstats(self, user=None): + """ number of downloads and size """ + if user is None: + self.c.execute("SELECT COUNT(*), SUM(f.size) FROM files f WHERE dlstatus != 0") + else: + self.c.execute( + "SELECT COUNT(*), SUM(f.size) FROM files f, packages p WHERE f.package = p.pid AND dlstatus != 0", + user) + + r = self.c.fetchone() + # sum is None when no elements are added + return (r[0], r[1] if r[1] is not None else 0) if r else (0, 0) + + @queue + def queuestats(self, user=None): + """ number and size of files in queue not finished yet""" + # status not in NA, finished, skipped + if user is None: + self.c.execute("SELECT COUNT(*), SUM(f.size) FROM files f WHERE dlstatus NOT IN (0,5,6)") + else: + self.c.execute( + "SELECT COUNT(*), SUM(f.size) FROM files f, package p WHERE f.package = p.pid AND p.owner=? AND dlstatus NOT IN (0,5,6)", + user) + + r = self.c.fetchone() + return (r[0], r[1] if r[1] is not None else 0) if r else (0, 0) + + + # TODO: multi user? + @queue + def processcount(self, fid=-1, user=None): + """ number of files which have to be processed """ + # status in online, queued, starting, waiting, downloading + self.c.execute("SELECT COUNT(*), SUM(size) FROM files WHERE dlstatus IN (2,3,8,9,10) AND fid != ?", (fid, )) + return self.c.fetchone()[0] + + @queue + def processstats(self, user=None): + if user is None: + self.c.execute("SELECT COUNT(*), SUM(size) FROM files WHERE dlstatus IN (2,3,8,9,10)") + else: + self.c.execute( + "SELECT COUNT(*), SUM(f.size) FROM files f, packages p WHERE f.package = p.pid AND dlstatus IN (2,3,8,9,10)", + user) + r = self.c.fetchone() + return (r[0], r[1] if r[1] is not None else 0) if r else (0, 0) + + @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 + + @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 + + @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 + + @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)) + + @queue + def getAllFiles(self, package=None, search=None, state=None, owner=None): + """ Return dict with file information + + :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 ') + + arg = [] + + if state is not None and state != DS.All: + qry += 'dlstatus IN (%s) AND ' % state_string(state) + if owner is not None: + qry += 'owner=? AND ' + arg.append(owner) + + 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 ? " + + # make qry valid + if qry.endswith("WHERE "): qry = qry[:-6] + if qry.endswith("AND "): qry = qry[:-4] + + self.c.execute(qry + "ORDER BY package, fileorder", arg) + + 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]) + + data[r[0]] = f + + return data + + @queue + def getMatchingFilenames(self, pattern, owner=None): + """ Return matching file names for pattern, useful for search suggestions """ + qry = 'SELECT name FROM files WHERE name LIKE ?' + args = ["%%%s%%" % pattern.strip("%")] + if owner: + qry += " AND owner=?" + args.append(owner) + + self.c.execute(qry, args) + return [r[0] for r in self.c] + + @queue + def getAllPackages(self, root=None, owner=None, tags=None): + """ Return dict with package information + + :param root: optional root to filter + :param owner: optional user id + :param tags: optional tag list + """ + qry = ( + 'SELECT pid, name, folder, root, owner, site, comment, password, added, tags, status, shared, packageorder ' + 'FROM packages%s ORDER BY root, packageorder') + + if root is None: + stats = self.getPackageStats(owner=owner) + if owner is None: + self.c.execute(qry % "") + else: + self.c.execute(qry % " WHERE owner=?", (owner,)) + else: + stats = self.getPackageStats(root=root, owner=owner) + if owner is None: + self.c.execute(qry % ' WHERE root=? OR pid=?', (root, root)) + else: + self.c.execute(qry % ' WHERE (root=? OR pid=?) AND owner=?', (root, root, owner)) + + 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].split(","), r[10], r[11], r[12], + stats.get(r[0], zero_stats) + ) + + return data + + @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: + self.c.execute(qry % {"sub": ""}) + + data = {} + for r in self.c: + 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 + + @queue + def getStatsForPackage(self, pid): + return self.getPackageStats(pid=pid)[pid] + + @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 + 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]) + + return f + + @queue + def getPackageInfo(self, pid, stats=True): + """get data for a specific package, optionally with package stats""" + if stats: + stats = self.getPackageStats(pid=pid) + + self.c.execute( + 'SELECT pid, name, folder, root, owner, site, comment, password, added, tags, status, shared, 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].split(","), r[10], r[11], r[12], + stats.get(r[0], zero_stats) if stats else None + ) + + # TODO: does this need owner? + @async + def updateLinkInfo(self, data): + """ data is list of tuples (name, size, status,[ hash,] url)""" + + # inserts media type as n-1th arguments + data = [t[:-1] + (guess_type(t[0]), t[-1]) for t in data] + + # status in (NA, Offline, Online, Queued, TempOffline) + if data and len(data[0]) == 5: + self.c.executemany( + 'UPDATE files SET name=?, size=?, dlstatus=?, media=? WHERE url=? AND dlstatus IN (0,1,2,3,11)', + data) + else: + self.c.executemany( + 'UPDATE files SET name=?, size=?, dlstatus=?, hash=?, media=? WHERE url=? AND dlstatus IN (0,1,2,3,11)', + data) + + @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)) + + @async + def updatePackage(self, p): + self.c.execute( + 'UPDATE packages SET name=?, folder=?, site=?, comment=?, password=?, tags=?, status=?, shared=? WHERE pid=?', + (p.name, p.folder, p.site, p.comment, p.password, ",".join(p.tags), p.status, p.shared, 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() + 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)) + + 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() + max = (r[0] if r[0] else 0) + 1 + + self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND root=?', + (order, root)) + + self.c.execute('UPDATE packages SET root=?, packageorder=? WHERE pid=?', (dpid, max, pid)) + + @async + def restartFile(self, fid): + # status -> queued + self.c.execute('UPDATE files SET dlstatus=3, error="" WHERE fid=?', (fid,)) + + @async + def restartPackage(self, pid): + # status -> queued + self.c.execute('UPDATE files SET status=3 WHERE package=?', (pid,)) + + + # TODO: multi user approach + @queue + def getJob(self, occ): + """return pyfile ids, which are suitable for download and don't use a occupied plugin""" + cmd = "(%s)" % ", ".join(["'%s'" % x for x in occ]) + #TODO + + # 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) + + return [x[0] for x in self.c] + + @queue + def getUnfinished(self, pid): + """return list of max length 3 ids with pyfiles in package not finished or processed""" + + # 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] + + @queue + def restartFailed(self, owner=None): + # status=queued, where status in failed, aborted, temp offline + self.c.execute("UPDATE files SET dlstatus=3, error='' WHERE dlstatus IN (7, 11, 12)") + + @queue + def findDuplicates(self, id, folder, filename): + """ checks if filename exists with different id and same package, dlstatus = finished """ + # TODO: also check root of package + self.c.execute( + "SELECT f.plugin FROM files f INNER JOIN packages as p ON f.package=p.pid AND p.folder=? WHERE f.fid!=? AND f.dlstatus=5 AND f.name=?" + , (folder, id, filename)) + return self.c.fetchone() + + @queue + def purgeLinks(self): + # 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/pyload/database/StatisticDatabase.py b/pyload/database/StatisticDatabase.py new file mode 100644 index 000000000..d5f9658f2 --- /dev/null +++ b/pyload/database/StatisticDatabase.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.database import DatabaseMethods, queue, async, inner + +# TODO + +class StatisticMethods(DatabaseMethods): + pass + + + +StatisticMethods.register()
\ No newline at end of file diff --git a/pyload/database/StorageDatabase.py b/pyload/database/StorageDatabase.py new file mode 100644 index 000000000..2d4c8a9c7 --- /dev/null +++ b/pyload/database/StorageDatabase.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from pyload.database import DatabaseBackend, queue + +class StorageMethods(): + @queue + def setStorage(db, identifier, key, value): + db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key)) + if db.c.fetchone() is not None: + db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key)) + else: + db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value)) + + @queue + def getStorage(db, identifier, key=None): + if key is not None: + db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key)) + row = db.c.fetchone() + if row is not None: + return row[0] + else: + db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier, )) + d = {} + for row in db.c: + d[row[0]] = row[1] + return d + + @queue + def delStorage(db, identifier, key): + db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key)) + +DatabaseBackend.registerSub(StorageMethods) diff --git a/pyload/database/UserDatabase.py b/pyload/database/UserDatabase.py new file mode 100644 index 000000000..8d8381a40 --- /dev/null +++ b/pyload/database/UserDatabase.py @@ -0,0 +1,129 @@ +# -*- 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 hashlib import sha1 +from string import letters, digits +from random import choice + +alphnum = letters + digits + +from pyload.Api import UserData + +from DatabaseBackend import DatabaseMethods, queue, async + + +def random_salt(): + return "".join(choice(alphnum) for x in range(0, 5)) + + +class UserMethods(DatabaseMethods): + @queue + def addUser(self, user, password): + salt = random_salt() + h = sha1(salt + password) + password = salt + h.hexdigest() + + 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: + 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 + + + @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 + + salt = r[2][:5] + pw = r[2][5:] + h = sha1(salt + oldpw) + if h.hexdigest() == pw: + salt = random_salt() + h = sha1(salt + newpw) + password = salt + h.hexdigest() + + 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)) + + @async + def setRole(self, user, role): + self.c.execute("UPDATE users SET role=? WHERE name=?", (role, user)) + + # TODO update methods + @async + def removeUserByName(self, name): + self.c.execute("SELECT uid from users WHERE name=?", (name,)) + uid = self.c.fetchone() + if uid: + # deletes user and all associated accounts + self.c.execute('DELETE FROM users WHERE user=?', (uid[0], )) + + +UserMethods.register() diff --git a/pyload/database/__init__.py b/pyload/database/__init__.py new file mode 100644 index 000000000..d3f97fb53 --- /dev/null +++ b/pyload/database/__init__.py @@ -0,0 +1,8 @@ +from DatabaseBackend import DatabaseMethods, DatabaseBackend, queue, async, inner + +from FileDatabase import FileMethods +from UserDatabase import UserMethods +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/pyload/datatypes/OnlineCheck.py b/pyload/datatypes/OnlineCheck.py new file mode 100644 index 000000000..b0b19cf76 --- /dev/null +++ b/pyload/datatypes/OnlineCheck.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from time import time + +from pyload.Api import OnlineCheck as OC + + +class OnlineCheck: + """ Helper class that holds result of an initiated online check """ + + def __init__(self, rid, owner): + self.rid = rid + self.owner = owner + self.result = {} + self.done = False + + self.timestamp = time() + + def isStale(self, timeout=5): + """ checks if the data was updated or accessed recently """ + return self.timestamp + timeout * 60 < time() + + def update(self, result): + self.timestamp = time() + self.result.update(result) + + def toApiData(self): + self.timestamp = time() + oc = OC(self.rid, self.result) + # getting the results clears the older ones + self.result = {} + # indication for no more data + if self.done: + oc.rid = -1 + + return oc
\ No newline at end of file diff --git a/pyload/datatypes/PyFile.py b/pyload/datatypes/PyFile.py new file mode 100644 index 000000000..3ce114beb --- /dev/null +++ b/pyload/datatypes/PyFile.py @@ -0,0 +1,278 @@ +#!/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 sleep, time +from ReadWriteLock import ReadWriteLock + +from pyload.Api import ProgressInfo, DownloadProgress, FileInfo, DownloadInfo, DownloadStatus +from pyload.utils import lock, read_lock +from pyload.utils.filetypes import guess_type + +statusMap = { + "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, + "not possible": 13, + "decrypting": 14, + "processing": 15, + "custom": 16, + "unknown": 17, +} + + +class PyFile(object): + """ + Represents a file object at runtime + """ + __slots__ = ("m", "fid", "_name", "_size", "filestatus", "media", "added", "fileorder", + "url", "pluginname", "hash", "status", "error", "packageid", "owner", + "lock", "plugin", "waitUntil", "abort", "statusname", + "reconnected", "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): + + self.m = manager + + 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.pluginname = pluginname + self.hash = hash + self.status = status + self.error = error + self.owner = owner + self.packageid = package + # database information ends here + + self.lock = ReadWriteLock() + + self.plugin = None + + self.waitUntil = 0 # time() + time to wait + + # status attributes + self.abort = False + self.reconnected = False + self.statusname = None + + + @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") + + # media type is updated if needed + if self._name != name: + self.media = guess_type(name) + self._name = name + + name = property(getName, setName) + + def __repr__(self): + return "<PyFile %s: %s@%s>" % (self.id, self.name, self.pluginname) + + @lock + def initPlugin(self): + """ inits plugin instance """ + if not self.plugin: + self.pluginclass = self.m.core.pluginManager.getPluginClass(self.pluginname) + self.plugin = self.pluginclass(self) + + @read_lock + def hasPlugin(self): + """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] + # needs to sync so status is written to database + self.sync() + + def setCustomStatus(self, msg, status="processing"): + self.statusname = msg + self.setStatus(status) + + def getStatusName(self): + if self.status not in (15, 16) or not self.statusname: + return self.m.statusMsg[self.status] + else: + return self.statusname + + def hasStatus(self, status): + return statusMap[status] == self.status + + def sync(self): + """sync PyFile instance with database""" + self.m.updateFile(self) + + @lock + def release(self): + """sync and remove from cache""" + if hasattr(self, "plugin") and self.plugin: + self.plugin.clean() + del self.plugin + + self.m.releaseFile(self.fid) + + + def toInfoData(self): + return FileInfo(self.fid, self.getName(), self.packageid, self.owner, 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 + + @read_lock + def abortDownload(self): + """abort pyfile if possible""" + # TODO: abort timeout, currently dead locks + while self.id in self.m.core.threadManager.processingIds(): + self.abort = True + if self.plugin and self.plugin.req: + self.plugin.req.abort() + if self.plugin.dl: + self.plugin.dl.abort() + + sleep(0.1) + + self.abort = False + if self.plugin: + self.plugin.req.abort() + if self.plugin.dl: + self.plugin.dl.abort() + + self.release() + + def finishIfDone(self): + """set status to finish and release file if every thread is finished with it""" + + if self.id in self.m.core.threadManager.processingIds(): + return False + + self.setStatus("finished") + self.release() + self.m.checkAllLinksFinished() + return True + + def checkIfProcessed(self): + self.m.checkAllLinksProcessed(self.id) + + def getSpeed(self): + """ calculates speed """ + try: + return self.plugin.dl.speed + except: + return 0 + + def getETA(self): + """ gets established time of arrival / or waiting time""" + try: + if self.status == DownloadStatus.Waiting: + return self.waitUntil - time() + + return self.getBytesLeft() / self.getSpeed() + except: + return 0 + + def getBytesArrived(self): + """ gets bytes arrived """ + try: + return self.plugin.dl.arrived + except: + return 0 + + def getBytesLeft(self): + """ gets bytes left """ + try: + return self.plugin.dl.size - self.plugin.dl.arrived + except: + return 0 + + def getSize(self): + """ get size of download """ + try: + if self.plugin.dl.size: + return self.plugin.dl.size + else: + return self.size + except: + return self.size + + def getProgressInfo(self): + return ProgressInfo(self.pluginname, self.name, self.getStatusName(), self.getETA(), + self.getBytesArrived(), self.getSize(), + DownloadProgress(self.fid, self.packageid, self.getSpeed(), self.status)) diff --git a/pyload/datatypes/PyPackage.py b/pyload/datatypes/PyPackage.py new file mode 100644 index 000000000..ba38781eb --- /dev/null +++ b/pyload/datatypes/PyPackage.py @@ -0,0 +1,115 @@ +#!/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 pyload.Api import PackageInfo, PackageStatus +from pyload.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.tags, info.status, info.shared, info.packageorder) + + def __init__(self, manager, pid, name, folder, root, owner, site, comment, password, added, tags, status, + shared, 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.tags = tags + self.status = status + self.shared = shared + self.packageorder = packageorder + self.timestamp = time() + + #: Finish event already fired + self.setFinished = False + + @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.tags, self.status, self.shared, self.packageorder + ) + + def getChildren(self): + """get fids of container files""" + return self.m.getPackageInfo(self.pid).fids + + 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, False, 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/pyload/datatypes/User.py b/pyload/datatypes/User.py new file mode 100644 index 000000000..645fd0983 --- /dev/null +++ b/pyload/datatypes/User.py @@ -0,0 +1,68 @@ +#!/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 pyload.Api import UserData, Permission, Role +from pyload.utils import bits_set + +#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, **kwargs): + UserData.__init__(self, *args, **kwargs) + self.api = api + + + def toUserData(self): + # TODO + return UserData() + + def hasPermission(self, perms): + """ Accepts permission bit or name """ + if isinstance(perms, basestring) and hasattr(Permission, perms): + perms = getattr(Permission, perms) + + return bits_set(perms, self.permission) + + 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 primary(self): + """ Primary user id, Internal user handle used for most operations + Secondary user account share id with primary user. Only Admins have no primary id. """ + if self.hasRole(Role.Admin): + return None + return self.true_primary + + @property + def true_primary(self): + """ Primary handle that does not distinguish admin accounts """ + return self.user if self.user else self.uid
\ No newline at end of file diff --git a/module/__init__.py b/pyload/datatypes/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/__init__.py +++ b/pyload/datatypes/__init__.py diff --git a/pyload/interaction/EventManager.py b/pyload/interaction/EventManager.py new file mode 100644 index 000000000..8cc1e81d2 --- /dev/null +++ b/pyload/interaction/EventManager.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from threading import Lock +from traceback import print_exc + + +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 + ===================== ================ =========================================================== + event eventName, *args Called for every event, with eventName and original args + download:preparing fid A download was just queued and will be prepared now. + download:start fid A plugin will immediately start the download afterwards. + download:allProcessed All links were handled, pyLoad would idle afterwards. + download:allFinished All downloads in the queue are finished. + config:changed sec, opt, value The config was changed. + ===================== ================ =========================================================== + + | Notes: + | download:allProcessed is *always* called before download:allFinished. + """ + + def __init__(self, core): + self.core = core + self.log = core.log + + self.events = {"event": []} + + self.lock = Lock() + + def listenTo(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 removeFromEvents(self, func): + """ Removes func from all known events """ + for name, events in self.events.iteritems(): + if func in events: + events.remove(func) + + def dispatchEvent(self, event, *args, **kwargs): + """dispatches event with args""" + for f in self.events["event"]: + try: + f(event, *args, **kwargs) + except Exception, e: + self.log.warning("Error calling event handler %s: %s, %s, %s" + % ("event", f, args, str(e))) + self.core.print_exc() + + if event in self.events: + for f in self.events[event]: + try: + f(*args, **kwargs) + except Exception, e: + self.log.warning("Error calling event handler %s: %s, %s, %s" + % (event, f, args, str(e))) + self.core.print_exc()
\ No newline at end of file diff --git a/pyload/interaction/InteractionManager.py b/pyload/interaction/InteractionManager.py new file mode 100644 index 000000000..36d457323 --- /dev/null +++ b/pyload/interaction/InteractionManager.py @@ -0,0 +1,166 @@ +# -*- 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 base64 import standard_b64encode + +from new_collections import OrderedDict + +from pyload.utils import lock, bits_set +from pyload.Api import Interaction as IA +from pyload.Api import InputType, Input + +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. + """ + + # number of seconds a client is classified as active + CLIENT_THRESHOLD = 60 + NOTIFICATION_TIMEOUT = 60 * 60 * 30 + MAX_NOTIFICATIONS = 50 + + def __init__(self, core): + self.lock = Lock() + self.core = core + self.tasks = OrderedDict() #task store, for all outgoing tasks + + self.last_clients = {} + self.ids = 0 #uniue interaction ids + + def isClientConnected(self, user): + return self.last_clients.get(user, 0) + self.CLIENT_THRESHOLD > time() + + @lock + def work(self): + # old notifications will be removed + for n in [k for k, v in self.tasks.iteritems() if v.timedOut()]: + del self.tasks[n] + + # keep notifications count limited + n = [k for k,v in self.tasks.iteritems() if v.type == IA.Notification] + n.reverse() + for v in n[:self.MAX_NOTIFICATIONS]: + del self.tasks[v] + + @lock + def createNotification(self, title, content, desc="", plugin="", owner=None): + """ 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, IA.Notification, Input(InputType.Text, None, content), title, desc, plugin, + owner=owner) + self.ids += 1 + self.queueTask(task) + return task + + @lock + def createQueryTask(self, input, desc, plugin="", owner=None): + # input type was given, create a input widget + if type(input) == int: + input = Input(input) + if not isinstance(input, Input): + raise TypeError("'Input' class expected not '%s'" % type(input)) + + task = InteractionTask(self.ids, IA.Query, input, _("Query"), desc, plugin, owner=owner) + self.ids += 1 + self.queueTask(task) + return task + + @lock + def createCaptchaTask(self, img, format, filename, plugin="", type=InputType.Text, owner=None): + """ Createss a new captcha task. + + :param img: image content (not base encoded) + :param format: img format + :param type: :class:`InputType` + :return: + """ + if type == 'textual': + type = InputType.Text + elif type == 'positional': + type = InputType.Click + + input = Input(type, data=[standard_b64encode(img), format, filename]) + + #todo: title desc plugin + task = InteractionTask(self.ids, IA.Captcha, input, + _("Captcha request"), _("Please solve the captcha."), plugin, owner=owner) + + self.ids += 1 + self.queueTask(task) + return task + + @lock + def removeTask(self, task): + if task.iid in self.tasks: + del self.tasks[task.iid] + self.core.evm.dispatchEvent("interaction:deleted", task.iid) + + @lock + def getTaskByID(self, iid): + return self.tasks.get(iid, None) + + @lock + def getTasks(self, user, mode=IA.All): + # update last active clients + self.last_clients[user] = time() + + # filter current mode + tasks = [t for t in self.tasks.itervalues() if mode == IA.All or bits_set(t.type, mode)] + # filter correct user / or shared + tasks = [t for t in tasks if user is None or user == t.owner or t.shared] + + return tasks + + def isTaskWaiting(self, user, mode=IA.All): + tasks = [t for t in self.getTasks(user, mode) if not t.type == IA.Notification or not t.seen] + return len(tasks) > 0 + + def queueTask(self, task): + cli = self.isClientConnected(task.owner) + + # set waiting times based on threshold + if cli: + task.setWaiting(self.CLIENT_THRESHOLD) + else: # TODO: higher threshold after client connects? + task.setWaiting(self.CLIENT_THRESHOLD / 3) + + if task.type == IA.Notification: + task.setWaiting(self.NOTIFICATION_TIMEOUT) # notifications are valid for 30h + + for plugin in self.core.addonManager.activePlugins(): + try: + plugin.newInteractionTask(task) + except: + self.core.print_exc() + + self.tasks[task.iid] = task + self.core.evm.dispatchEvent("interaction:added", task) + + +if __name__ == "__main__": + it = InteractionTask()
\ No newline at end of file diff --git a/pyload/interaction/InteractionTask.py b/pyload/interaction/InteractionTask.py new file mode 100644 index 000000000..b404aa6ce --- /dev/null +++ b/pyload/interaction/InteractionTask.py @@ -0,0 +1,100 @@ +# -*- 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 pyload.Api import InteractionTask as BaseInteractionTask +from pyload.Api import Interaction, InputType, Input + +#noinspection PyUnresolvedReferences +class InteractionTask(BaseInteractionTask): + """ + General Interaction Task extends ITask defined by api 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 + #: A task that was retrieved counts as seen + seen = False + #: A task that is relevant to every user + shared = False + #: primary uid of the owner + owner = None + + def __init__(self, *args, **kwargs): + if 'owner' in kwargs: + self.owner = kwargs['owner'] + del kwargs['owner'] + if 'shared' in kwargs: + self.shared = kwargs['shared'] + del kwargs['shared'] + + 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 setShared(self): + """ enable shared mode, should not be reversed""" + self.shared = True + + def setResult(self, value): + self.result = self.convertResult(value) + + def setWaiting(self, sec, lock=False): + """ sets waiting in seconds from now, < 0 can be used as infinitive """ + if not self.locked: + if sec < 0: + self.wait_until = -1 + else: + self.wait_until = max(time() + sec, self.wait_until) + + if lock: self.locked = True + + def isWaiting(self): + if self.result or self.error or self.timedOut(): + return False + + return True + + def timedOut(self): + return time() > self.wait_until > -1 + + def correct(self): + [x.taskCorrect(self) for x in self.handler] + + def invalid(self): + [x.taskInvalid(self) for x in self.handler]
\ No newline at end of file diff --git a/module/common/__init__.py b/pyload/interaction/__init__.py index de6d13128..de6d13128 100644 --- a/module/common/__init__.py +++ b/pyload/interaction/__init__.py diff --git a/module/lib/Getch.py b/pyload/lib/Getch.py index 22b7ea7f8..22b7ea7f8 100644 --- a/module/lib/Getch.py +++ b/pyload/lib/Getch.py diff --git a/pyload/lib/ReadWriteLock.py b/pyload/lib/ReadWriteLock.py new file mode 100644 index 000000000..cc82f3d48 --- /dev/null +++ b/pyload/lib/ReadWriteLock.py @@ -0,0 +1,232 @@ +# -*- coding: iso-8859-15 -*- +"""locks.py - Read-Write lock thread lock implementation + +See the class documentation for more info. + +Copyright (C) 2007, Heiko Wundram. +Released under the BSD-license. + +http://code.activestate.com/recipes/502283-read-write-lock-class-rlock-like/ +""" + +# Imports +# ------- + +from threading import Condition, Lock, currentThread +from time import time + + +# Read write lock +# --------------- + +class ReadWriteLock(object): + """Read-Write lock class. A read-write lock differs from a standard + threading.RLock() by allowing multiple threads to simultaneously hold a + read lock, while allowing only a single thread to hold a write lock at the + same point of time. + + When a read lock is requested while a write lock is held, the reader + is blocked; when a write lock is requested while another write lock is + held or there are read locks, the writer is blocked. + + Writers are always preferred by this implementation: if there are blocked + threads waiting for a write lock, current readers may request more read + locks (which they eventually should free, as they starve the waiting + writers otherwise), but a new thread requesting a read lock will not + be granted one, and block. This might mean starvation for readers if + two writer threads interweave their calls to acquireWrite() without + leaving a window only for readers. + + In case a current reader requests a write lock, this can and will be + satisfied without giving up the read locks first, but, only one thread + may perform this kind of lock upgrade, as a deadlock would otherwise + occur. After the write lock has been granted, the thread will hold a + full write lock, and not be downgraded after the upgrading call to + acquireWrite() has been match by a corresponding release(). + """ + + def __init__(self): + """Initialize this read-write lock.""" + + # Condition variable, used to signal waiters of a change in object + # state. + self.__condition = Condition(Lock()) + + # Initialize with no writers. + self.__writer = None + self.__upgradewritercount = 0 + self.__pendingwriters = [] + + # Initialize with no readers. + self.__readers = {} + + def acquire(self, blocking=True, timeout=None, shared=False): + if shared: + self.acquireRead(timeout) + else: + self.acquireWrite(timeout) + + def acquireRead(self, timeout=None): + """Acquire a read lock for the current thread, waiting at most + timeout seconds or doing a non-blocking check in case timeout is <= 0. + + In case timeout is None, the call to acquireRead blocks until the + lock request can be serviced. + + In case the timeout expires before the lock could be serviced, a + RuntimeError is thrown.""" + + if timeout is not None: + endtime = time() + timeout + me = currentThread() + self.__condition.acquire() + try: + if self.__writer is me: + # If we are the writer, grant a new read lock, always. + self.__writercount += 1 + return + while True: + if self.__writer is None: + # Only test anything if there is no current writer. + if self.__upgradewritercount or self.__pendingwriters: + if me in self.__readers: + # Only grant a read lock if we already have one + # in case writers are waiting for their turn. + # This means that writers can't easily get starved + # (but see below, readers can). + self.__readers[me] += 1 + return + # No, we aren't a reader (yet), wait for our turn. + else: + # Grant a new read lock, always, in case there are + # no pending writers (and no writer). + self.__readers[me] = self.__readers.get(me, 0) + 1 + return + if timeout is not None: + remaining = endtime - time() + if remaining <= 0: + # Timeout has expired, signal caller of this. + raise RuntimeError("Acquiring read lock timed out") + self.__condition.wait(remaining) + else: + self.__condition.wait() + finally: + self.__condition.release() + + def acquireWrite(self, timeout=None): + """Acquire a write lock for the current thread, waiting at most + timeout seconds or doing a non-blocking check in case timeout is <= 0. + + In case the write lock cannot be serviced due to the deadlock + condition mentioned above, a ValueError is raised. + + In case timeout is None, the call to acquireWrite blocks until the + lock request can be serviced. + + In case the timeout expires before the lock could be serviced, a + RuntimeError is thrown.""" + + if timeout is not None: + endtime = time() + timeout + me, upgradewriter = currentThread(), False + self.__condition.acquire() + try: + if self.__writer is me: + # If we are the writer, grant a new write lock, always. + self.__writercount += 1 + return + elif me in self.__readers: + # If we are a reader, no need to add us to pendingwriters, + # we get the upgradewriter slot. + if self.__upgradewritercount: + # If we are a reader and want to upgrade, and someone + # else also wants to upgrade, there is no way we can do + # this except if one of us releases all his read locks. + # Signal this to user. + raise ValueError( + "Inevitable dead lock, denying write lock" + ) + upgradewriter = True + self.__upgradewritercount = self.__readers.pop(me) + else: + # We aren't a reader, so add us to the pending writers queue + # for synchronization with the readers. + self.__pendingwriters.append(me) + while True: + if not self.__readers and self.__writer is None: + # Only test anything if there are no readers and writers. + if self.__upgradewritercount: + if upgradewriter: + # There is a writer to upgrade, and it's us. Take + # the write lock. + self.__writer = me + self.__writercount = self.__upgradewritercount + 1 + self.__upgradewritercount = 0 + return + # There is a writer to upgrade, but it's not us. + # Always leave the upgrade writer the advance slot, + # because he presumes he'll get a write lock directly + # from a previously held read lock. + elif self.__pendingwriters[0] is me: + # If there are no readers and writers, it's always + # fine for us to take the writer slot, removing us + # from the pending writers queue. + # This might mean starvation for readers, though. + self.__writer = me + self.__writercount = 1 + self.__pendingwriters = self.__pendingwriters[1:] + return + if timeout is not None: + remaining = endtime - time() + if remaining <= 0: + # Timeout has expired, signal caller of this. + if upgradewriter: + # Put us back on the reader queue. No need to + # signal anyone of this change, because no other + # writer could've taken our spot before we got + # here (because of remaining readers), as the test + # for proper conditions is at the start of the + # loop, not at the end. + self.__readers[me] = self.__upgradewritercount + self.__upgradewritercount = 0 + else: + # We were a simple pending writer, just remove us + # from the FIFO list. + self.__pendingwriters.remove(me) + raise RuntimeError("Acquiring write lock timed out") + self.__condition.wait(remaining) + else: + self.__condition.wait() + finally: + self.__condition.release() + + def release(self): + """Release the currently held lock. + + In case the current thread holds no lock, a ValueError is thrown.""" + + me = currentThread() + self.__condition.acquire() + try: + if self.__writer is me: + # We are the writer, take one nesting depth away. + self.__writercount -= 1 + if not self.__writercount: + # No more write locks; take our writer position away and + # notify waiters of the new circumstances. + self.__writer = None + self.__condition.notifyAll() + elif me in self.__readers: + # We are a reader currently, take one nesting depth away. + self.__readers[me] -= 1 + if not self.__readers[me]: + # No more read locks, take our reader position away. + del self.__readers[me] + if not self.__readers: + # No more readers, notify waiters of the new + # circumstances. + self.__condition.notifyAll() + else: + raise ValueError("Trying to release unheld lock") + finally: + self.__condition.release() diff --git a/module/lib/SafeEval.py b/pyload/lib/SafeEval.py index 8fc57f261..8fc57f261 100644 --- a/module/lib/SafeEval.py +++ b/pyload/lib/SafeEval.py diff --git a/module/lib/__init__.py b/pyload/lib/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/lib/__init__.py +++ b/pyload/lib/__init__.py diff --git a/module/lib/beaker/__init__.py b/pyload/lib/beaker/__init__.py index 792d60054..792d60054 100644 --- a/module/lib/beaker/__init__.py +++ b/pyload/lib/beaker/__init__.py diff --git a/module/lib/beaker/cache.py b/pyload/lib/beaker/cache.py index 4a96537ff..4a96537ff 100644 --- a/module/lib/beaker/cache.py +++ b/pyload/lib/beaker/cache.py diff --git a/module/lib/beaker/container.py b/pyload/lib/beaker/container.py index 515e97af6..515e97af6 100644 --- a/module/lib/beaker/container.py +++ b/pyload/lib/beaker/container.py diff --git a/module/lib/beaker/converters.py b/pyload/lib/beaker/converters.py index f0ad34963..f0ad34963 100644 --- a/module/lib/beaker/converters.py +++ b/pyload/lib/beaker/converters.py diff --git a/module/lib/beaker/crypto/__init__.py b/pyload/lib/beaker/crypto/__init__.py index 3e26b0c13..3e26b0c13 100644 --- a/module/lib/beaker/crypto/__init__.py +++ b/pyload/lib/beaker/crypto/__init__.py diff --git a/module/lib/beaker/crypto/jcecrypto.py b/pyload/lib/beaker/crypto/jcecrypto.py index 4062d513e..4062d513e 100644 --- a/module/lib/beaker/crypto/jcecrypto.py +++ b/pyload/lib/beaker/crypto/jcecrypto.py diff --git a/module/lib/beaker/crypto/pbkdf2.py b/pyload/lib/beaker/crypto/pbkdf2.py index 96dc5fbb2..96dc5fbb2 100644 --- a/module/lib/beaker/crypto/pbkdf2.py +++ b/pyload/lib/beaker/crypto/pbkdf2.py diff --git a/module/lib/beaker/crypto/pycrypto.py b/pyload/lib/beaker/crypto/pycrypto.py index a3eb4d9db..a3eb4d9db 100644 --- a/module/lib/beaker/crypto/pycrypto.py +++ b/pyload/lib/beaker/crypto/pycrypto.py diff --git a/module/lib/beaker/crypto/util.py b/pyload/lib/beaker/crypto/util.py index d97e8ce6f..d97e8ce6f 100644 --- a/module/lib/beaker/crypto/util.py +++ b/pyload/lib/beaker/crypto/util.py diff --git a/module/lib/beaker/exceptions.py b/pyload/lib/beaker/exceptions.py index cc0eed286..cc0eed286 100644 --- a/module/lib/beaker/exceptions.py +++ b/pyload/lib/beaker/exceptions.py diff --git a/module/lib/beaker/ext/__init__.py b/pyload/lib/beaker/ext/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/lib/beaker/ext/__init__.py +++ b/pyload/lib/beaker/ext/__init__.py diff --git a/module/lib/beaker/ext/database.py b/pyload/lib/beaker/ext/database.py index 701e6f7d2..701e6f7d2 100644 --- a/module/lib/beaker/ext/database.py +++ b/pyload/lib/beaker/ext/database.py diff --git a/module/lib/beaker/ext/google.py b/pyload/lib/beaker/ext/google.py index dd8380d7f..dd8380d7f 100644 --- a/module/lib/beaker/ext/google.py +++ b/pyload/lib/beaker/ext/google.py diff --git a/module/lib/beaker/ext/memcached.py b/pyload/lib/beaker/ext/memcached.py index 96516953f..96516953f 100644 --- a/module/lib/beaker/ext/memcached.py +++ b/pyload/lib/beaker/ext/memcached.py diff --git a/module/lib/beaker/ext/sqla.py b/pyload/lib/beaker/ext/sqla.py index 8c79633c1..8c79633c1 100644 --- a/module/lib/beaker/ext/sqla.py +++ b/pyload/lib/beaker/ext/sqla.py diff --git a/module/lib/beaker/middleware.py b/pyload/lib/beaker/middleware.py index 7ba88b37d..7ba88b37d 100644 --- a/module/lib/beaker/middleware.py +++ b/pyload/lib/beaker/middleware.py diff --git a/module/lib/beaker/session.py b/pyload/lib/beaker/session.py index 7d465530b..7d465530b 100644 --- a/module/lib/beaker/session.py +++ b/pyload/lib/beaker/session.py diff --git a/module/lib/beaker/synchronization.py b/pyload/lib/beaker/synchronization.py index 761303707..761303707 100644 --- a/module/lib/beaker/synchronization.py +++ b/pyload/lib/beaker/synchronization.py diff --git a/module/lib/beaker/util.py b/pyload/lib/beaker/util.py index 04c9617c5..04c9617c5 100644 --- a/module/lib/beaker/util.py +++ b/pyload/lib/beaker/util.py diff --git a/pyload/lib/bottle.py b/pyload/lib/bottle.py new file mode 100644 index 000000000..b00bda1c9 --- /dev/null +++ b/pyload/lib/bottle.py @@ -0,0 +1,3251 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Bottle is a fast and simple micro-framework for small web applications. It +offers request dispatching (Routes) with url parameter support, templates, +a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and +template engines - all in a single file and with no dependencies other than the +Python Standard Library. + +Homepage and documentation: http://bottlepy.org/ + +Copyright (c) 2012, Marcel Hellkamp. +License: MIT (see LICENSE for details) +""" + +from __future__ import with_statement + +__author__ = 'Marcel Hellkamp' +__version__ = '0.11.4' +__license__ = 'MIT' + +# The gevent server adapter needs to patch some modules before they are imported +# This is why we parse the commandline parameters here but handle them later +if __name__ == '__main__': + from optparse import OptionParser + _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app") + _opt = _cmd_parser.add_option + _opt("--version", action="store_true", help="show version number.") + _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") + _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.") + _opt("-p", "--plugin", action="append", help="install additional plugin/s.") + _opt("--debug", action="store_true", help="start server in debug mode.") + _opt("--reload", action="store_true", help="auto-reload on file changes.") + _cmd_options, _cmd_args = _cmd_parser.parse_args() + if _cmd_options.server and _cmd_options.server.startswith('gevent'): + import gevent.monkey; gevent.monkey.patch_all() + +import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\ + os, re, subprocess, sys, tempfile, threading, time, urllib, warnings + +from datetime import date as datedate, datetime, timedelta +from tempfile import TemporaryFile +from traceback import format_exc, print_exc + +try: from json import dumps as json_dumps, loads as json_lds +except ImportError: # pragma: no cover + try: from simplejson import dumps as json_dumps, loads as json_lds + except ImportError: + try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds + except ImportError: + def json_dumps(data): + raise ImportError("JSON support requires Python 2.6 or simplejson.") + json_lds = json_dumps + + + +# We now try to fix 2.5/2.6/3.1/3.2 incompatibilities. +# It ain't pretty but it works... Sorry for the mess. + +py = sys.version_info +py3k = py >= (3,0,0) +py25 = py < (2,6,0) +py31 = (3,1,0) <= py < (3,2,0) + +# Workaround for the missing "as" keyword in py3k. +def _e(): return sys.exc_info()[1] + +# Workaround for the "print is a keyword/function" Python 2/3 dilemma +# and a fallback for mod_wsgi (resticts stdout/err attribute access) +try: + _stdout, _stderr = sys.stdout.write, sys.stderr.write +except IOError: + _stdout = lambda x: sys.stdout.write(x) + _stderr = lambda x: sys.stderr.write(x) + +# Lots of stdlib and builtin differences. +if py3k: + import http.client as httplib + import _thread as thread + from urllib.parse import urljoin, SplitResult as UrlSplitResult + from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote + urlunquote = functools.partial(urlunquote, encoding='latin1') + from http.cookies import SimpleCookie + from collections import MutableMapping as DictMixin + import pickle + from io import BytesIO + basestring = str + unicode = str + json_loads = lambda s: json_lds(touni(s)) + callable = lambda x: hasattr(x, '__call__') + imap = map +else: # 2.x + import httplib + import thread + from urlparse import urljoin, SplitResult as UrlSplitResult + from urllib import urlencode, quote as urlquote, unquote as urlunquote + from Cookie import SimpleCookie + from itertools import imap + import cPickle as pickle + from StringIO import StringIO as BytesIO + if py25: + from UserDict import DictMixin + def next(it): return it.next() + bytes = str + else: # 2.6, 2.7 + from collections import MutableMapping as DictMixin + json_loads = json_lds + +# Some helpers for string/byte handling +def tob(s, enc='utf8'): + return s.encode(enc) if isinstance(s, unicode) else bytes(s) +def touni(s, enc='utf8', err='strict'): + return s.decode(enc, err) if isinstance(s, bytes) else unicode(s) +tonat = touni if py3k else tob + +# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense). +# 3.1 needs a workaround. +if py31: + from io import TextIOWrapper + class NCTextIOWrapper(TextIOWrapper): + def close(self): pass # Keep wrapped buffer open. + +# File uploads (which are implemented as empty FiledStorage instances...) +# have a negative truth value. That makes no sense, here is a fix. +class FieldStorage(cgi.FieldStorage): + def __nonzero__(self): return bool(self.list or self.file) + if py3k: __bool__ = __nonzero__ + +# A bug in functools causes it to break if the wrapper is an instance method +def update_wrapper(wrapper, wrapped, *a, **ka): + try: functools.update_wrapper(wrapper, wrapped, *a, **ka) + except AttributeError: pass + + + +# These helpers are used at module level and need to be defined first. +# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense. + +def depr(message): + warnings.warn(message, DeprecationWarning, stacklevel=3) + +def makelist(data): # This is just to handy + if isinstance(data, (tuple, list, set, dict)): return list(data) + elif data: return [data] + else: return [] + + +class DictProperty(object): + ''' Property that maps to a key in a local dict-like attribute. ''' + def __init__(self, attr, key=None, read_only=False): + self.attr, self.key, self.read_only = attr, key, read_only + + def __call__(self, func): + functools.update_wrapper(self, func, updated=[]) + self.getter, self.key = func, self.key or func.__name__ + return self + + def __get__(self, obj, cls): + if obj is None: return self + key, storage = self.key, getattr(obj, self.attr) + if key not in storage: storage[key] = self.getter(obj) + return storage[key] + + def __set__(self, obj, value): + if self.read_only: raise AttributeError("Read-Only property.") + getattr(obj, self.attr)[self.key] = value + + def __delete__(self, obj): + if self.read_only: raise AttributeError("Read-Only property.") + del getattr(obj, self.attr)[self.key] + + +class cached_property(object): + ''' A property that is only computed once per instance and then replaces + itself with an ordinary attribute. Deleting the attribute resets the + property. ''' + + def __init__(self, func): + self.func = func + + def __get__(self, obj, cls): + if obj is None: return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value + + +class lazy_attribute(object): + ''' A property that caches itself to the class object. ''' + def __init__(self, func): + functools.update_wrapper(self, func, updated=[]) + self.getter = func + + def __get__(self, obj, cls): + value = self.getter(cls) + setattr(cls, self.__name__, value) + return value + + + + + + +############################################################################### +# Exceptions and Events ######################################################## +############################################################################### + + +class BottleException(Exception): + """ A base class for exceptions used by bottle. """ + pass + + + + + + +############################################################################### +# Routing ###################################################################### +############################################################################### + + +class RouteError(BottleException): + """ This is a base class for all routing related exceptions """ + + +class RouteReset(BottleException): + """ If raised by a plugin or request handler, the route is reset and all + plugins are re-applied. """ + +class RouterUnknownModeError(RouteError): pass + + +class RouteSyntaxError(RouteError): + """ The route parser found something not supported by this router """ + + +class RouteBuildError(RouteError): + """ The route could not been built """ + + +class Router(object): + ''' A Router is an ordered collection of route->target pairs. It is used to + efficiently match WSGI requests against a number of routes and return + the first target that satisfies the request. The target may be anything, + usually a string, ID or callable object. A route consists of a path-rule + and a HTTP method. + + The path-rule is either a static path (e.g. `/contact`) or a dynamic + path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax + and details on the matching order are described in docs:`routing`. + ''' + + default_pattern = '[^/]+' + default_filter = 're' + #: Sorry for the mess. It works. Trust me. + rule_syntax = re.compile('(\\\\*)'\ + '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ + '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ + '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))') + + def __init__(self, strict=False): + self.rules = {} # A {rule: Rule} mapping + self.builder = {} # A rule/name->build_info mapping + self.static = {} # Cache for static routes: {path: {method: target}} + self.dynamic = [] # Cache for dynamic routes. See _compile() + #: If true, static routes are no longer checked first. + self.strict_order = strict + self.filters = {'re': self.re_filter, 'int': self.int_filter, + 'float': self.float_filter, 'path': self.path_filter} + + def re_filter(self, conf): + return conf or self.default_pattern, None, None + + def int_filter(self, conf): + return r'-?\d+', int, lambda x: str(int(x)) + + def float_filter(self, conf): + return r'-?[\d.]+', float, lambda x: str(float(x)) + + def path_filter(self, conf): + return r'.+?', None, None + + def add_filter(self, name, func): + ''' Add a filter. The provided function is called with the configuration + string as parameter and must return a (regexp, to_python, to_url) tuple. + The first element is a string, the last two are callables or None. ''' + self.filters[name] = func + + def parse_rule(self, rule): + ''' Parses a rule into a (name, filter, conf) token stream. If mode is + None, name contains a static rule part. ''' + offset, prefix = 0, '' + for match in self.rule_syntax.finditer(rule): + prefix += rule[offset:match.start()] + g = match.groups() + if len(g[0])%2: # Escaped wildcard + prefix += match.group(0)[len(g[0]):] + offset = match.end() + continue + if prefix: yield prefix, None, None + name, filtr, conf = g[1:4] if not g[2] is None else g[4:7] + if not filtr: filtr = self.default_filter + yield name, filtr, conf or None + offset, prefix = match.end(), '' + if offset <= len(rule) or prefix: + yield prefix+rule[offset:], None, None + + def add(self, rule, method, target, name=None): + ''' Add a new route or replace the target for an existing route. ''' + if rule in self.rules: + self.rules[rule][method] = target + if name: self.builder[name] = self.builder[rule] + return + + target = self.rules[rule] = {method: target} + + # Build pattern and other structures for dynamic routes + anons = 0 # Number of anonymous wildcards + pattern = '' # Regular expression pattern + filters = [] # Lists of wildcard input filters + builder = [] # Data structure for the URL builder + is_static = True + for key, mode, conf in self.parse_rule(rule): + if mode: + is_static = False + mask, in_filter, out_filter = self.filters[mode](conf) + if key: + pattern += '(?P<%s>%s)' % (key, mask) + else: + pattern += '(?:%s)' % mask + key = 'anon%d' % anons; anons += 1 + if in_filter: filters.append((key, in_filter)) + builder.append((key, out_filter or str)) + elif key: + pattern += re.escape(key) + builder.append((None, key)) + self.builder[rule] = builder + if name: self.builder[name] = builder + + if is_static and not self.strict_order: + self.static[self.build(rule)] = target + return + + def fpat_sub(m): + return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:' + flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern) + + try: + re_match = re.compile('^(%s)$' % pattern).match + except re.error: + raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e())) + + def match(path): + """ Return an url-argument dictionary. """ + url_args = re_match(path).groupdict() + for name, wildcard_filter in filters: + try: + url_args[name] = wildcard_filter(url_args[name]) + except ValueError: + raise HTTPError(400, 'Path has wrong format.') + return url_args + + try: + combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern) + self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1]) + self.dynamic[-1][1].append((match, target)) + except (AssertionError, IndexError): # AssertionError: Too many groups + self.dynamic.append((re.compile('(^%s$)' % flat_pattern), + [(match, target)])) + return match + + def build(self, _name, *anons, **query): + ''' Build an URL by filling the wildcards in a rule. ''' + builder = self.builder.get(_name) + if not builder: raise RouteBuildError("No route with that name.", _name) + try: + for i, value in enumerate(anons): query['anon%d'%i] = value + url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder]) + return url if not query else url+'?'+urlencode(query) + except KeyError: + raise RouteBuildError('Missing URL argument: %r' % _e().args[0]) + + def match(self, environ): + ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). ''' + path, targets, urlargs = environ['PATH_INFO'] or '/', None, {} + if path in self.static: + targets = self.static[path] + else: + for combined, rules in self.dynamic: + match = combined.match(path) + if not match: continue + getargs, targets = rules[match.lastindex - 1] + urlargs = getargs(path) if getargs else {} + break + + if not targets: + raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO'])) + method = environ['REQUEST_METHOD'].upper() + if method in targets: + return targets[method], urlargs + if method == 'HEAD' and 'GET' in targets: + return targets['GET'], urlargs + if 'ANY' in targets: + return targets['ANY'], urlargs + allowed = [verb for verb in targets if verb != 'ANY'] + if 'GET' in allowed and 'HEAD' not in allowed: + allowed.append('HEAD') + raise HTTPError(405, "Method not allowed.", Allow=",".join(allowed)) + + +class Route(object): + ''' This class wraps a route callback along with route specific metadata and + configuration and applies Plugins on demand. It is also responsible for + turing an URL path rule into a regular expression usable by the Router. + ''' + + def __init__(self, app, rule, method, callback, name=None, + plugins=None, skiplist=None, **config): + #: The application this route is installed to. + self.app = app + #: The path-rule string (e.g. ``/wiki/:page``). + self.rule = rule + #: The HTTP method as a string (e.g. ``GET``). + self.method = method + #: The original callback with no plugins applied. Useful for introspection. + self.callback = callback + #: The name of the route (if specified) or ``None``. + self.name = name or None + #: A list of route-specific plugins (see :meth:`Bottle.route`). + self.plugins = plugins or [] + #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). + self.skiplist = skiplist or [] + #: Additional keyword arguments passed to the :meth:`Bottle.route` + #: decorator are stored in this dictionary. Used for route-specific + #: plugin configuration and meta-data. + self.config = ConfigDict(config) + + def __call__(self, *a, **ka): + depr("Some APIs changed to return Route() instances instead of"\ + " callables. Make sure to use the Route.call method and not to"\ + " call Route instances directly.") + return self.call(*a, **ka) + + @cached_property + def call(self): + ''' The route callback with all plugins applied. This property is + created on demand and then cached to speed up subsequent requests.''' + return self._make_callback() + + def reset(self): + ''' Forget any cached values. The next time :attr:`call` is accessed, + all plugins are re-applied. ''' + self.__dict__.pop('call', None) + + def prepare(self): + ''' Do all on-demand work immediately (useful for debugging).''' + self.call + + @property + def _context(self): + depr('Switch to Plugin API v2 and access the Route object directly.') + return dict(rule=self.rule, method=self.method, callback=self.callback, + name=self.name, app=self.app, config=self.config, + apply=self.plugins, skip=self.skiplist) + + def all_plugins(self): + ''' Yield all Plugins affecting this route. ''' + unique = set() + for p in reversed(self.app.plugins + self.plugins): + if True in self.skiplist: break + name = getattr(p, 'name', False) + if name and (name in self.skiplist or name in unique): continue + if p in self.skiplist or type(p) in self.skiplist: continue + if name: unique.add(name) + yield p + + def _make_callback(self): + callback = self.callback + for plugin in self.all_plugins(): + try: + if hasattr(plugin, 'apply'): + api = getattr(plugin, 'api', 1) + context = self if api > 1 else self._context + callback = plugin.apply(callback, context) + else: + callback = plugin(callback) + except RouteReset: # Try again with changed configuration. + return self._make_callback() + if not callback is self.callback: + update_wrapper(callback, self.callback) + return callback + + def __repr__(self): + return '<%s %r %r>' % (self.method, self.rule, self.callback) + + + + + + +############################################################################### +# Application Object ########################################################### +############################################################################### + + +class Bottle(object): + """ Each Bottle object represents a single, distinct web application and + consists of routes, callbacks, plugins, resources and configuration. + Instances are callable WSGI applications. + + :param catchall: If true (default), handle all exceptions. Turn off to + let debugging middleware handle exceptions. + """ + + def __init__(self, catchall=True, autojson=True): + #: If true, most exceptions are caught and returned as :exc:`HTTPError` + self.catchall = catchall + + #: A :class:`ResourceManager` for application files + self.resources = ResourceManager() + + #: A :class:`ConfigDict` for app specific configuration. + self.config = ConfigDict() + self.config.autojson = autojson + + self.routes = [] # List of installed :class:`Route` instances. + self.router = Router() # Maps requests to :class:`Route` instances. + self.error_handler = {} + + # Core plugins + self.plugins = [] # List of installed plugins. + self.hooks = HooksPlugin() + self.install(self.hooks) + if self.config.autojson: + self.install(JSONPlugin()) + self.install(TemplatePlugin()) + + + def mount(self, prefix, app, **options): + ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific + URL prefix. Example:: + + root_app.mount('/admin/', admin_app) + + :param prefix: path prefix or `mount-point`. If it ends in a slash, + that slash is mandatory. + :param app: an instance of :class:`Bottle` or a WSGI application. + + All other parameters are passed to the underlying :meth:`route` call. + ''' + if isinstance(app, basestring): + prefix, app = app, prefix + depr('Parameter order of Bottle.mount() changed.') # 0.10 + + segments = [p for p in prefix.split('/') if p] + if not segments: raise ValueError('Empty path prefix.') + path_depth = len(segments) + + def mountpoint_wrapper(): + try: + request.path_shift(path_depth) + rs = BaseResponse([], 200) + def start_response(status, header): + rs.status = status + for name, value in header: rs.add_header(name, value) + return rs.body.append + body = app(request.environ, start_response) + body = itertools.chain(rs.body, body) + return HTTPResponse(body, rs.status_code, **rs.headers) + finally: + request.path_shift(-path_depth) + + options.setdefault('skip', True) + options.setdefault('method', 'ANY') + options.setdefault('mountpoint', {'prefix': prefix, 'target': app}) + options['callback'] = mountpoint_wrapper + + self.route('/%s/<:re:.*>' % '/'.join(segments), **options) + if not prefix.endswith('/'): + self.route('/' + '/'.join(segments), **options) + + def merge(self, routes): + ''' Merge the routes of another :class:`Bottle` application or a list of + :class:`Route` objects into this application. The routes keep their + 'owner', meaning that the :data:`Route.app` attribute is not + changed. ''' + if isinstance(routes, Bottle): + routes = routes.routes + for route in routes: + self.add_route(route) + + def install(self, plugin): + ''' Add a plugin to the list of plugins and prepare it for being + applied to all routes of this application. A plugin may be a simple + decorator or an object that implements the :class:`Plugin` API. + ''' + if hasattr(plugin, 'setup'): plugin.setup(self) + if not callable(plugin) and not hasattr(plugin, 'apply'): + raise TypeError("Plugins must be callable or implement .apply()") + self.plugins.append(plugin) + self.reset() + return plugin + + def uninstall(self, plugin): + ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type + object to remove all plugins that match that type, a string to remove + all plugins with a matching ``name`` attribute or ``True`` to remove all + plugins. Return the list of removed plugins. ''' + removed, remove = [], plugin + for i, plugin in list(enumerate(self.plugins))[::-1]: + if remove is True or remove is plugin or remove is type(plugin) \ + or getattr(plugin, 'name', True) == remove: + removed.append(plugin) + del self.plugins[i] + if hasattr(plugin, 'close'): plugin.close() + if removed: self.reset() + return removed + + def run(self, **kwargs): + ''' Calls :func:`run` with the same parameters. ''' + run(self, **kwargs) + + def reset(self, route=None): + ''' Reset all routes (force plugins to be re-applied) and clear all + caches. If an ID or route object is given, only that specific route + is affected. ''' + if route is None: routes = self.routes + elif isinstance(route, Route): routes = [route] + else: routes = [self.routes[route]] + for route in routes: route.reset() + if DEBUG: + for route in routes: route.prepare() + self.hooks.trigger('app_reset') + + def close(self): + ''' Close the application and all installed plugins. ''' + for plugin in self.plugins: + if hasattr(plugin, 'close'): plugin.close() + self.stopped = True + + def match(self, environ): + """ Search for a matching route and return a (:class:`Route` , urlargs) + tuple. The second value is a dictionary with parameters extracted + from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" + return self.router.match(environ) + + def get_url(self, routename, **kargs): + """ Return a string that matches a named route """ + scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' + location = self.router.build(routename, **kargs).lstrip('/') + return urljoin(urljoin('/', scriptname), location) + + def add_route(self, route): + ''' Add a route object, but do not change the :data:`Route.app` + attribute.''' + self.routes.append(route) + self.router.add(route.rule, route.method, route, name=route.name) + if DEBUG: route.prepare() + + def route(self, path=None, method='GET', callback=None, name=None, + apply=None, skip=None, **config): + """ A decorator to bind a function to a request URL. Example:: + + @app.route('/hello/:name') + def hello(name): + return 'Hello %s' % name + + The ``:name`` part is a wildcard. See :class:`Router` for syntax + details. + + :param path: Request path or a list of paths to listen to. If no + path is specified, it is automatically generated from the + signature of the function. + :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of + methods to listen to. (default: `GET`) + :param callback: An optional shortcut to avoid the decorator + syntax. ``route(..., callback=func)`` equals ``route(...)(func)`` + :param name: The name for this route. (default: None) + :param apply: A decorator or plugin or a list of plugins. These are + applied to the route callback in addition to installed plugins. + :param skip: A list of plugins, plugin classes or names. Matching + plugins are not installed to this route. ``True`` skips all. + + Any additional keyword arguments are stored as route-specific + configuration and passed to plugins (see :meth:`Plugin.apply`). + """ + if callable(path): path, callback = None, path + plugins = makelist(apply) + skiplist = makelist(skip) + def decorator(callback): + # TODO: Documentation and tests + if isinstance(callback, basestring): callback = load(callback) + for rule in makelist(path) or yieldroutes(callback): + for verb in makelist(method): + verb = verb.upper() + route = Route(self, rule, verb, callback, name=name, + plugins=plugins, skiplist=skiplist, **config) + self.add_route(route) + return callback + return decorator(callback) if callback else decorator + + def get(self, path=None, method='GET', **options): + """ Equals :meth:`route`. """ + return self.route(path, method, **options) + + def post(self, path=None, method='POST', **options): + """ Equals :meth:`route` with a ``POST`` method parameter. """ + return self.route(path, method, **options) + + def put(self, path=None, method='PUT', **options): + """ Equals :meth:`route` with a ``PUT`` method parameter. """ + return self.route(path, method, **options) + + def delete(self, path=None, method='DELETE', **options): + """ Equals :meth:`route` with a ``DELETE`` method parameter. """ + return self.route(path, method, **options) + + def error(self, code=500): + """ Decorator: Register an output handler for a HTTP error code""" + def wrapper(handler): + self.error_handler[int(code)] = handler + return handler + return wrapper + + def hook(self, name): + """ Return a decorator that attaches a callback to a hook. Three hooks + are currently implemented: + + - before_request: Executed once before each request + - after_request: Executed once after each request + - app_reset: Called whenever :meth:`reset` is called. + """ + def wrapper(func): + self.hooks.add(name, func) + return func + return wrapper + + def handle(self, path, method='GET'): + """ (deprecated) Execute the first matching route callback and return + the result. :exc:`HTTPResponse` exceptions are caught and returned. + If :attr:`Bottle.catchall` is true, other exceptions are caught as + well and returned as :exc:`HTTPError` instances (500). + """ + depr("This method will change semantics in 0.10. Try to avoid it.") + if isinstance(path, dict): + return self._handle(path) + return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()}) + + def default_error_handler(self, res): + return tob(template(ERROR_PAGE_TEMPLATE, e=res)) + + def _handle(self, environ): + try: + environ['bottle.app'] = self + request.bind(environ) + response.bind() + route, args = self.router.match(environ) + environ['route.handle'] = route + environ['bottle.route'] = route + environ['route.url_args'] = args + return route.call(**args) + except HTTPResponse: + return _e() + except RouteReset: + route.reset() + return self._handle(environ) + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + stacktrace = format_exc() + environ['wsgi.errors'].write(stacktrace) + return HTTPError(500, "Internal Server Error", _e(), stacktrace) + + def _cast(self, out, peek=None): + """ Try to convert the parameter into something WSGI compatible and set + correct HTTP headers when possible. + Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like, + iterable of strings and iterable of unicodes + """ + + # Empty output is done here + if not out: + if 'Content-Length' not in response: + response['Content-Length'] = 0 + return [] + # Join lists of byte or unicode strings. Mixed lists are NOT supported + if isinstance(out, (tuple, list))\ + and isinstance(out[0], (bytes, unicode)): + out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' + # Encode unicode strings + if isinstance(out, unicode): + out = out.encode(response.charset) + # Byte Strings are just returned + if isinstance(out, bytes): + if 'Content-Length' not in response: + response['Content-Length'] = len(out) + return [out] + # HTTPError or HTTPException (recursive, because they may wrap anything) + # TODO: Handle these explicitly in handle() or make them iterable. + if isinstance(out, HTTPError): + out.apply(response) + out = self.error_handler.get(out.status_code, self.default_error_handler)(out) + return self._cast(out) + if isinstance(out, HTTPResponse): + out.apply(response) + return self._cast(out.body) + + # File-like objects. + if hasattr(out, 'read'): + if 'wsgi.file_wrapper' in request.environ: + return request.environ['wsgi.file_wrapper'](out) + elif hasattr(out, 'close') or not hasattr(out, '__iter__'): + return WSGIFileWrapper(out) + + # Handle Iterables. We peek into them to detect their inner type. + try: + out = iter(out) + first = next(out) + while not first: + first = next(out) + except StopIteration: + return self._cast('') + except HTTPResponse: + first = _e() + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + first = HTTPError(500, 'Unhandled exception', _e(), format_exc()) + + # These are the inner types allowed in iterator or generator objects. + if isinstance(first, HTTPResponse): + return self._cast(first) + if isinstance(first, bytes): + return itertools.chain([first], out) + if isinstance(first, unicode): + return imap(lambda x: x.encode(response.charset), + itertools.chain([first], out)) + return self._cast(HTTPError(500, 'Unsupported response type: %s'\ + % type(first))) + + def wsgi(self, environ, start_response): + """ The bottle WSGI-interface. """ + try: + out = self._cast(self._handle(environ)) + # rfc2616 section 4.3 + if response._status_code in (100, 101, 204, 304)\ + or environ['REQUEST_METHOD'] == 'HEAD': + if hasattr(out, 'close'): out.close() + out = [] + start_response(response._status_line, response.headerlist) + return out + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + err = '<h1>Critical error while processing request: %s</h1>' \ + % html_escape(environ.get('PATH_INFO', '/')) + if DEBUG: + err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \ + '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \ + % (html_escape(repr(_e())), html_escape(format_exc())) + environ['wsgi.errors'].write(err) + headers = [('Content-Type', 'text/html; charset=UTF-8')] + start_response('500 INTERNAL SERVER ERROR', headers) + return [tob(err)] + + def __call__(self, environ, start_response): + ''' Each instance of :class:'Bottle' is a WSGI application. ''' + return self.wsgi(environ, start_response) + + + + + + +############################################################################### +# HTTP and WSGI Tools ########################################################## +############################################################################### + + +class BaseRequest(object): + """ A wrapper for WSGI environment dictionaries that adds a lot of + convenient access methods and properties. Most of them are read-only. + + Adding new attributes to a request actually adds them to the environ + dictionary (as 'bottle.request.ext.<name>'). This is the recommended + way to store and access request-specific data. + """ + + __slots__ = ('environ') + + #: Maximum size of memory buffer for :attr:`body` in bytes. + MEMFILE_MAX = 102400 + #: Maximum number pr GET or POST parameters per request + MAX_PARAMS = 100 + + def __init__(self, environ=None): + """ Wrap a WSGI environ dictionary. """ + #: The wrapped WSGI environ dictionary. This is the only real attribute. + #: All other attributes actually are read-only properties. + self.environ = {} if environ is None else environ + self.environ['bottle.request'] = self + + @DictProperty('environ', 'bottle.app', read_only=True) + def app(self): + ''' Bottle application handling this request. ''' + raise RuntimeError('This request is not connected to an application.') + + @property + def path(self): + ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix + broken clients and avoid the "empty path" edge case). ''' + return '/' + self.environ.get('PATH_INFO','').lstrip('/') + + @property + def method(self): + ''' The ``REQUEST_METHOD`` value as an uppercase string. ''' + return self.environ.get('REQUEST_METHOD', 'GET').upper() + + @DictProperty('environ', 'bottle.request.headers', read_only=True) + def headers(self): + ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to + HTTP request headers. ''' + return WSGIHeaderDict(self.environ) + + def get_header(self, name, default=None): + ''' Return the value of a request header, or a given default value. ''' + return self.headers.get(name, default) + + @DictProperty('environ', 'bottle.request.cookies', read_only=True) + def cookies(self): + """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT + decoded. Use :meth:`get_cookie` if you expect signed cookies. """ + cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')) + cookies = list(cookies.values())[:self.MAX_PARAMS] + return FormsDict((c.key, c.value) for c in cookies) + + def get_cookie(self, key, default=None, secret=None): + """ Return the content of a cookie. To read a `Signed Cookie`, the + `secret` must match the one used to create the cookie (see + :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing + cookie or wrong signature), return a default value. """ + value = self.cookies.get(key) + if secret and value: + dec = cookie_decode(value, secret) # (key, value) tuple or None + return dec[1] if dec and dec[0] == key else default + return value or default + + @DictProperty('environ', 'bottle.request.query', read_only=True) + def query(self): + ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These + values are sometimes called "URL arguments" or "GET parameters", but + not to be confused with "URL wildcards" as they are provided by the + :class:`Router`. ''' + get = self.environ['bottle.get'] = FormsDict() + pairs = _parse_qsl(self.environ.get('QUERY_STRING', '')) + for key, value in pairs[:self.MAX_PARAMS]: + get[key] = value + return get + + @DictProperty('environ', 'bottle.request.forms', read_only=True) + def forms(self): + """ Form values parsed from an `url-encoded` or `multipart/form-data` + encoded POST or PUT request body. The result is retuned as a + :class:`FormsDict`. All keys and values are strings. File uploads + are stored separately in :attr:`files`. """ + forms = FormsDict() + for name, item in self.POST.allitems(): + if not hasattr(item, 'filename'): + forms[name] = item + return forms + + @DictProperty('environ', 'bottle.request.params', read_only=True) + def params(self): + """ A :class:`FormsDict` with the combined values of :attr:`query` and + :attr:`forms`. File uploads are stored in :attr:`files`. """ + params = FormsDict() + for key, value in self.query.allitems(): + params[key] = value + for key, value in self.forms.allitems(): + params[key] = value + return params + + @DictProperty('environ', 'bottle.request.files', read_only=True) + def files(self): + """ File uploads parsed from an `url-encoded` or `multipart/form-data` + encoded POST or PUT request body. The values are instances of + :class:`cgi.FieldStorage`. The most important attributes are: + + filename + The filename, if specified; otherwise None; this is the client + side filename, *not* the file name on which it is stored (that's + a temporary file you don't deal with) + file + The file(-like) object from which you can read the data. + value + The value as a *string*; for file uploads, this transparently + reads the file every time you request the value. Do not do this + on big files. + """ + files = FormsDict() + for name, item in self.POST.allitems(): + if hasattr(item, 'filename'): + files[name] = item + return files + + @DictProperty('environ', 'bottle.request.json', read_only=True) + def json(self): + ''' If the ``Content-Type`` header is ``application/json``, this + property holds the parsed content of the request body. Only requests + smaller than :attr:`MEMFILE_MAX` are processed to avoid memory + exhaustion. ''' + if 'application/json' in self.environ.get('CONTENT_TYPE', '') \ + and 0 < self.content_length < self.MEMFILE_MAX: + return json_loads(self.body.read(self.MEMFILE_MAX)) + return None + + @DictProperty('environ', 'bottle.request.body', read_only=True) + def _body(self): + maxread = max(0, self.content_length) + stream = self.environ['wsgi.input'] + body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b') + while maxread > 0: + part = stream.read(min(maxread, self.MEMFILE_MAX)) + if not part: break + body.write(part) + maxread -= len(part) + self.environ['wsgi.input'] = body + body.seek(0) + return body + + @property + def body(self): + """ The HTTP request body as a seek-able file-like object. Depending on + :attr:`MEMFILE_MAX`, this is either a temporary file or a + :class:`io.BytesIO` instance. Accessing this property for the first + time reads and replaces the ``wsgi.input`` environ variable. + Subsequent accesses just do a `seek(0)` on the file object. """ + self._body.seek(0) + return self._body + + #: An alias for :attr:`query`. + GET = query + + @DictProperty('environ', 'bottle.request.post', read_only=True) + def POST(self): + """ The values of :attr:`forms` and :attr:`files` combined into a single + :class:`FormsDict`. Values are either strings (form values) or + instances of :class:`cgi.FieldStorage` (file uploads). + """ + post = FormsDict() + # We default to application/x-www-form-urlencoded for everything that + # is not multipart and take the fast path (also: 3.1 workaround) + if not self.content_type.startswith('multipart/'): + maxlen = max(0, min(self.content_length, self.MEMFILE_MAX)) + pairs = _parse_qsl(tonat(self.body.read(maxlen), 'latin1')) + for key, value in pairs[:self.MAX_PARAMS]: + post[key] = value + return post + + safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi + for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): + if key in self.environ: safe_env[key] = self.environ[key] + args = dict(fp=self.body, environ=safe_env, keep_blank_values=True) + if py31: + args['fp'] = NCTextIOWrapper(args['fp'], encoding='ISO-8859-1', + newline='\n') + elif py3k: + args['encoding'] = 'ISO-8859-1' + data = FieldStorage(**args) + for item in (data.list or [])[:self.MAX_PARAMS]: + post[item.name] = item if item.filename else item.value + return post + + @property + def COOKIES(self): + ''' Alias for :attr:`cookies` (deprecated). ''' + depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).') + return self.cookies + + @property + def url(self): + """ The full request URI including hostname and scheme. If your app + lives behind a reverse proxy or load balancer and you get confusing + results, make sure that the ``X-Forwarded-Host`` header is set + correctly. """ + return self.urlparts.geturl() + + @DictProperty('environ', 'bottle.request.urlparts', read_only=True) + def urlparts(self): + ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. + The tuple contains (scheme, host, path, query_string and fragment), + but the fragment is always empty because it is not visible to the + server. ''' + env = self.environ + http = env.get('HTTP_X_FORWARDED_PROTO') or env.get('wsgi.url_scheme', 'http') + host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') + if not host: + # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. + host = env.get('SERVER_NAME', '127.0.0.1') + port = env.get('SERVER_PORT') + if port and port != ('80' if http == 'http' else '443'): + host += ':' + port + path = urlquote(self.fullpath) + return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '') + + @property + def fullpath(self): + """ Request path including :attr:`script_name` (if present). """ + return urljoin(self.script_name, self.path.lstrip('/')) + + @property + def query_string(self): + """ The raw :attr:`query` part of the URL (everything in between ``?`` + and ``#``) as a string. """ + return self.environ.get('QUERY_STRING', '') + + @property + def script_name(self): + ''' The initial portion of the URL's `path` that was removed by a higher + level (server or routing middleware) before the application was + called. This script path is returned with leading and tailing + slashes. ''' + script_name = self.environ.get('SCRIPT_NAME', '').strip('/') + return '/' + script_name + '/' if script_name else '/' + + def path_shift(self, shift=1): + ''' Shift path segments from :attr:`path` to :attr:`script_name` and + vice versa. + + :param shift: The number of path segments to shift. May be negative + to change the shift direction. (default: 1) + ''' + script = self.environ.get('SCRIPT_NAME','/') + self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift) + + @property + def content_length(self): + ''' The request body length as an integer. The client is responsible to + set this header. Otherwise, the real length of the body is unknown + and -1 is returned. In this case, :attr:`body` will be empty. ''' + return int(self.environ.get('CONTENT_LENGTH') or -1) + + @property + def content_type(self): + ''' The Content-Type header as a lowercase-string (default: empty). ''' + return self.environ.get('CONTENT_TYPE', '').lower() + + @property + def is_xhr(self): + ''' True if the request was triggered by a XMLHttpRequest. This only + works with JavaScript libraries that support the `X-Requested-With` + header (most of the popular libraries do). ''' + requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','') + return requested_with.lower() == 'xmlhttprequest' + + @property + def is_ajax(self): + ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. ''' + return self.is_xhr + + @property + def auth(self): + """ HTTP authentication data as a (user, password) tuple. This + implementation currently supports basic (not digest) authentication + only. If the authentication happened at a higher level (e.g. in the + front web-server or a middleware), the password field is None, but + the user field is looked up from the ``REMOTE_USER`` environ + variable. On any errors, None is returned. """ + basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION','')) + if basic: return basic + ruser = self.environ.get('REMOTE_USER') + if ruser: return (ruser, None) + return None + + @property + def remote_route(self): + """ A list of all IPs that were involved in this request, starting with + the client IP and followed by zero or more proxies. This does only + work if all proxies support the ```X-Forwarded-For`` header. Note + that this information can be forged by malicious clients. """ + proxy = self.environ.get('HTTP_X_FORWARDED_FOR') + if proxy: return [ip.strip() for ip in proxy.split(',')] + remote = self.environ.get('REMOTE_ADDR') + return [remote] if remote else [] + + @property + def remote_addr(self): + """ The client IP as a string. Note that this information can be forged + by malicious clients. """ + route = self.remote_route + return route[0] if route else None + + def copy(self): + """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """ + return Request(self.environ.copy()) + + def get(self, value, default=None): return self.environ.get(value, default) + def __getitem__(self, key): return self.environ[key] + def __delitem__(self, key): self[key] = ""; del(self.environ[key]) + def __iter__(self): return iter(self.environ) + def __len__(self): return len(self.environ) + def keys(self): return self.environ.keys() + def __setitem__(self, key, value): + """ Change an environ value and clear all caches that depend on it. """ + + if self.environ.get('bottle.request.readonly'): + raise KeyError('The environ dictionary is read-only.') + + self.environ[key] = value + todelete = () + + if key == 'wsgi.input': + todelete = ('body', 'forms', 'files', 'params', 'post', 'json') + elif key == 'QUERY_STRING': + todelete = ('query', 'params') + elif key.startswith('HTTP_'): + todelete = ('headers', 'cookies') + + for key in todelete: + self.environ.pop('bottle.request.'+key, None) + + def __repr__(self): + return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url) + + def __getattr__(self, name): + ''' Search in self.environ for additional user defined attributes. ''' + try: + var = self.environ['bottle.request.ext.%s'%name] + return var.__get__(self) if hasattr(var, '__get__') else var + except KeyError: + raise AttributeError('Attribute %r not defined.' % name) + + def __setattr__(self, name, value): + if name == 'environ': return object.__setattr__(self, name, value) + self.environ['bottle.request.ext.%s'%name] = value + + + + +def _hkey(s): + return s.title().replace('_','-') + + +class HeaderProperty(object): + def __init__(self, name, reader=None, writer=str, default=''): + self.name, self.default = name, default + self.reader, self.writer = reader, writer + self.__doc__ = 'Current value of the %r header.' % name.title() + + def __get__(self, obj, cls): + if obj is None: return self + value = obj.headers.get(self.name, self.default) + return self.reader(value) if self.reader else value + + def __set__(self, obj, value): + obj.headers[self.name] = self.writer(value) + + def __delete__(self, obj): + del obj.headers[self.name] + + +class BaseResponse(object): + """ Storage class for a response body as well as headers and cookies. + + This class does support dict-like case-insensitive item-access to + headers, but is NOT a dict. Most notably, iterating over a response + yields parts of the body and not the headers. + """ + + default_status = 200 + default_content_type = 'text/html; charset=UTF-8' + + # Header blacklist for specific response codes + # (rfc2616 section 10.2.3 and 10.3.5) + bad_headers = { + 204: set(('Content-Type',)), + 304: set(('Allow', 'Content-Encoding', 'Content-Language', + 'Content-Length', 'Content-Range', 'Content-Type', + 'Content-Md5', 'Last-Modified'))} + + def __init__(self, body='', status=None, **headers): + self._cookies = None + self._headers = {'Content-Type': [self.default_content_type]} + self.body = body + self.status = status or self.default_status + if headers: + for name, value in headers.items(): + self[name] = value + + def copy(self): + ''' Returns a copy of self. ''' + copy = Response() + copy.status = self.status + copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) + return copy + + def __iter__(self): + return iter(self.body) + + def close(self): + if hasattr(self.body, 'close'): + self.body.close() + + @property + def status_line(self): + ''' The HTTP status line as a string (e.g. ``404 Not Found``).''' + return self._status_line + + @property + def status_code(self): + ''' The HTTP status code as an integer (e.g. 404).''' + return self._status_code + + def _set_status(self, status): + if isinstance(status, int): + code, status = status, _HTTP_STATUS_LINES.get(status) + elif ' ' in status: + status = status.strip() + code = int(status.split()[0]) + else: + raise ValueError('String status line without a reason phrase.') + if not 100 <= code <= 999: raise ValueError('Status code out of range.') + self._status_code = code + self._status_line = str(status or ('%d Unknown' % code)) + + def _get_status(self): + return self._status_line + + status = property(_get_status, _set_status, None, + ''' A writeable property to change the HTTP response status. It accepts + either a numeric code (100-999) or a string with a custom reason + phrase (e.g. "404 Brain not found"). Both :data:`status_line` and + :data:`status_code` are updated accordingly. The return value is + always a status string. ''') + del _get_status, _set_status + + @property + def headers(self): + ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like + view on the response headers. ''' + hdict = HeaderDict() + hdict.dict = self._headers + return hdict + + def __contains__(self, name): return _hkey(name) in self._headers + def __delitem__(self, name): del self._headers[_hkey(name)] + def __getitem__(self, name): return self._headers[_hkey(name)][-1] + def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)] + + def get_header(self, name, default=None): + ''' Return the value of a previously defined header. If there is no + header with that name, return a default value. ''' + return self._headers.get(_hkey(name), [default])[-1] + + def set_header(self, name, value): + ''' Create a new response header, replacing any previously defined + headers with the same name. ''' + self._headers[_hkey(name)] = [str(value)] + + def add_header(self, name, value): + ''' Add an additional response header, not removing duplicates. ''' + self._headers.setdefault(_hkey(name), []).append(str(value)) + + def iter_headers(self): + ''' Yield (header, value) tuples, skipping headers that are not + allowed with the current response status code. ''' + return self.headerlist + + def wsgiheader(self): + depr('The wsgiheader method is deprecated. See headerlist.') #0.10 + return self.headerlist + + @property + def headerlist(self): + ''' WSGI conform list of (header, value) tuples. ''' + out = [] + headers = self._headers.items() + if self._status_code in self.bad_headers: + bad_headers = self.bad_headers[self._status_code] + headers = [h for h in headers if h[0] not in bad_headers] + out += [(name, val) for name, vals in headers for val in vals] + if self._cookies: + for c in self._cookies.values(): + out.append(('Set-Cookie', c.OutputString())) + return out + + content_type = HeaderProperty('Content-Type') + content_length = HeaderProperty('Content-Length', reader=int) + + @property + def charset(self): + """ Return the charset specified in the content-type header (default: utf8). """ + if 'charset=' in self.content_type: + return self.content_type.split('charset=')[-1].split(';')[0].strip() + return 'UTF-8' + + @property + def COOKIES(self): + """ A dict-like SimpleCookie instance. This should not be used directly. + See :meth:`set_cookie`. """ + depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10 + if not self._cookies: + self._cookies = SimpleCookie() + return self._cookies + + def set_cookie(self, name, value, secret=None, **options): + ''' Create a new cookie or replace an old one. If the `secret` parameter is + set, create a `Signed Cookie` (described below). + + :param name: the name of the cookie. + :param value: the value of the cookie. + :param secret: a signature key required for signed cookies. + + Additionally, this method accepts all RFC 2109 attributes that are + supported by :class:`cookie.Morsel`, including: + + :param max_age: maximum age in seconds. (default: None) + :param expires: a datetime object or UNIX timestamp. (default: None) + :param domain: the domain that is allowed to read the cookie. + (default: current domain) + :param path: limits the cookie to a given path (default: current path) + :param secure: limit the cookie to HTTPS connections (default: off). + :param httponly: prevents client-side javascript to read this cookie + (default: off, requires Python 2.6 or newer). + + If neither `expires` nor `max_age` is set (default), the cookie will + expire at the end of the browser session (as soon as the browser + window is closed). + + Signed cookies may store any pickle-able object and are + cryptographically signed to prevent manipulation. Keep in mind that + cookies are limited to 4kb in most browsers. + + Warning: Signed cookies are not encrypted (the client can still see + the content) and not copy-protected (the client can restore an old + cookie). The main intention is to make pickling and unpickling + save, not to store secret information at client side. + ''' + if not self._cookies: + self._cookies = SimpleCookie() + + if secret: + value = touni(cookie_encode((name, value), secret)) + elif not isinstance(value, basestring): + raise TypeError('Secret key missing for non-string Cookie.') + + if len(value) > 4096: raise ValueError('Cookie value to long.') + self._cookies[name] = value + + for key, value in options.items(): + if key == 'max_age': + if isinstance(value, timedelta): + value = value.seconds + value.days * 24 * 3600 + if key == 'expires': + if isinstance(value, (datedate, datetime)): + value = value.timetuple() + elif isinstance(value, (int, float)): + value = time.gmtime(value) + value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value) + self._cookies[name][key.replace('_', '-')] = value + + def delete_cookie(self, key, **kwargs): + ''' Delete a cookie. Be sure to use the same `domain` and `path` + settings as used to create the cookie. ''' + kwargs['max_age'] = -1 + kwargs['expires'] = 0 + self.set_cookie(key, '', **kwargs) + + def __repr__(self): + out = '' + for name, value in self.headerlist: + out += '%s: %s\n' % (name.title(), value.strip()) + return out + +#: Thread-local storage for :class:`LocalRequest` and :class:`LocalResponse` +#: attributes. +_lctx = threading.local() + +def local_property(name): + def fget(self): + try: + return getattr(_lctx, name) + except AttributeError: + raise RuntimeError("Request context not initialized.") + def fset(self, value): setattr(_lctx, name, value) + def fdel(self): delattr(_lctx, name) + return property(fget, fset, fdel, + 'Thread-local property stored in :data:`_lctx.%s`' % name) + + +class LocalRequest(BaseRequest): + ''' A thread-local subclass of :class:`BaseRequest` with a different + set of attribues for each thread. There is usually only one global + instance of this class (:data:`request`). If accessed during a + request/response cycle, this instance always refers to the *current* + request (even on a multithreaded server). ''' + bind = BaseRequest.__init__ + environ = local_property('request_environ') + + +class LocalResponse(BaseResponse): + ''' A thread-local subclass of :class:`BaseResponse` with a different + set of attribues for each thread. There is usually only one global + instance of this class (:data:`response`). Its attributes are used + to build the HTTP response at the end of the request/response cycle. + ''' + bind = BaseResponse.__init__ + _status_line = local_property('response_status_line') + _status_code = local_property('response_status_code') + _cookies = local_property('response_cookies') + _headers = local_property('response_headers') + body = local_property('response_body') + +Request = BaseRequest +Response = BaseResponse + +class HTTPResponse(Response, BottleException): + def __init__(self, body='', status=None, header=None, **headers): + if header or 'output' in headers: + depr('Call signature changed (for the better)') + if header: headers.update(header) + if 'output' in headers: body = headers.pop('output') + super(HTTPResponse, self).__init__(body, status, **headers) + + def apply(self, response): + response._status_code = self._status_code + response._status_line = self._status_line + response._headers = self._headers + response._cookies = self._cookies + response.body = self.body + + def _output(self, value=None): + depr('Use HTTPResponse.body instead of HTTPResponse.output') + if value is None: return self.body + self.body = value + + output = property(_output, _output, doc='Alias for .body') + +class HTTPError(HTTPResponse): + default_status = 500 + def __init__(self, status=None, body=None, exception=None, traceback=None, header=None, **headers): + self.exception = exception + self.traceback = traceback + super(HTTPError, self).__init__(body, status, header, **headers) + + + + + +############################################################################### +# Plugins ###################################################################### +############################################################################### + +class PluginError(BottleException): pass + +class JSONPlugin(object): + name = 'json' + api = 2 + + def __init__(self, json_dumps=json_dumps): + self.json_dumps = json_dumps + + def apply(self, callback, route): + dumps = self.json_dumps + if not dumps: return callback + def wrapper(*a, **ka): + rv = callback(*a, **ka) + if isinstance(rv, dict): + #Attempt to serialize, raises exception on failure + json_response = dumps(rv) + #Set content type only if serialization succesful + response.content_type = 'application/json' + return json_response + return rv + return wrapper + + +class HooksPlugin(object): + name = 'hooks' + api = 2 + + _names = 'before_request', 'after_request', 'app_reset' + + def __init__(self): + self.hooks = dict((name, []) for name in self._names) + self.app = None + + def _empty(self): + return not (self.hooks['before_request'] or self.hooks['after_request']) + + def setup(self, app): + self.app = app + + def add(self, name, func): + ''' Attach a callback to a hook. ''' + was_empty = self._empty() + self.hooks.setdefault(name, []).append(func) + if self.app and was_empty and not self._empty(): self.app.reset() + + def remove(self, name, func): + ''' Remove a callback from a hook. ''' + was_empty = self._empty() + if name in self.hooks and func in self.hooks[name]: + self.hooks[name].remove(func) + if self.app and not was_empty and self._empty(): self.app.reset() + + def trigger(self, name, *a, **ka): + ''' Trigger a hook and return a list of results. ''' + hooks = self.hooks[name] + if ka.pop('reversed', False): hooks = hooks[::-1] + return [hook(*a, **ka) for hook in hooks] + + def apply(self, callback, route): + if self._empty(): return callback + def wrapper(*a, **ka): + self.trigger('before_request') + rv = callback(*a, **ka) + self.trigger('after_request', reversed=True) + return rv + return wrapper + + +class TemplatePlugin(object): + ''' This plugin applies the :func:`view` decorator to all routes with a + `template` config parameter. If the parameter is a tuple, the second + element must be a dict with additional options (e.g. `template_engine`) + or default variables for the template. ''' + name = 'template' + api = 2 + + def apply(self, callback, route): + conf = route.config.get('template') + if isinstance(conf, (tuple, list)) and len(conf) == 2: + return view(conf[0], **conf[1])(callback) + elif isinstance(conf, str) and 'template_opts' in route.config: + depr('The `template_opts` parameter is deprecated.') #0.9 + return view(conf, **route.config['template_opts'])(callback) + elif isinstance(conf, str): + return view(conf)(callback) + else: + return callback + + +#: Not a plugin, but part of the plugin API. TODO: Find a better place. +class _ImportRedirect(object): + def __init__(self, name, impmask): + ''' Create a virtual package that redirects imports (see PEP 302). ''' + self.name = name + self.impmask = impmask + self.module = sys.modules.setdefault(name, imp.new_module(name)) + self.module.__dict__.update({'__file__': __file__, '__path__': [], + '__all__': [], '__loader__': self}) + sys.meta_path.append(self) + + def find_module(self, fullname, path=None): + if '.' not in fullname: return + packname, modname = fullname.rsplit('.', 1) + if packname != self.name: return + return self + + def load_module(self, fullname): + if fullname in sys.modules: return sys.modules[fullname] + packname, modname = fullname.rsplit('.', 1) + realname = self.impmask % modname + __import__(realname) + module = sys.modules[fullname] = sys.modules[realname] + setattr(self.module, modname, module) + module.__loader__ = self + return module + + + + + + +############################################################################### +# Common Utilities ############################################################# +############################################################################### + + +class MultiDict(DictMixin): + """ This dict stores multiple values per key, but behaves exactly like a + normal dict in that it returns only the newest value for any given key. + There are special methods available to access the full list of values. + """ + + def __init__(self, *a, **k): + self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items()) + + def __len__(self): return len(self.dict) + def __iter__(self): return iter(self.dict) + def __contains__(self, key): return key in self.dict + def __delitem__(self, key): del self.dict[key] + def __getitem__(self, key): return self.dict[key][-1] + def __setitem__(self, key, value): self.append(key, value) + def keys(self): return self.dict.keys() + + if py3k: + def values(self): return (v[-1] for v in self.dict.values()) + def items(self): return ((k, v[-1]) for k, v in self.dict.items()) + def allitems(self): + return ((k, v) for k, vl in self.dict.items() for v in vl) + iterkeys = keys + itervalues = values + iteritems = items + iterallitems = allitems + + else: + def values(self): return [v[-1] for v in self.dict.values()] + def items(self): return [(k, v[-1]) for k, v in self.dict.items()] + def iterkeys(self): return self.dict.iterkeys() + def itervalues(self): return (v[-1] for v in self.dict.itervalues()) + def iteritems(self): + return ((k, v[-1]) for k, v in self.dict.iteritems()) + def iterallitems(self): + return ((k, v) for k, vl in self.dict.iteritems() for v in vl) + def allitems(self): + return [(k, v) for k, vl in self.dict.iteritems() for v in vl] + + def get(self, key, default=None, index=-1, type=None): + ''' Return the most recent value for a key. + + :param default: The default value to be returned if the key is not + present or the type conversion fails. + :param index: An index for the list of available values. + :param type: If defined, this callable is used to cast the value + into a specific type. Exception are suppressed and result in + the default value to be returned. + ''' + try: + val = self.dict[key][index] + return type(val) if type else val + except Exception: + pass + return default + + def append(self, key, value): + ''' Add a new value to the list of values for this key. ''' + self.dict.setdefault(key, []).append(value) + + def replace(self, key, value): + ''' Replace the list of values with a single value. ''' + self.dict[key] = [value] + + def getall(self, key): + ''' Return a (possibly empty) list of values for a key. ''' + return self.dict.get(key) or [] + + #: Aliases for WTForms to mimic other multi-dict APIs (Django) + getone = get + getlist = getall + + + +class FormsDict(MultiDict): + ''' This :class:`MultiDict` subclass is used to store request form data. + Additionally to the normal dict-like item access methods (which return + unmodified data as native strings), this container also supports + attribute-like access to its values. Attributes are automatically de- + or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing + attributes default to an empty string. ''' + + #: Encoding used for attribute values. + input_encoding = 'utf8' + #: If true (default), unicode strings are first encoded with `latin1` + #: and then decoded to match :attr:`input_encoding`. + recode_unicode = True + + def _fix(self, s, encoding=None): + if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI + s = s.encode('latin1') + if isinstance(s, bytes): # Python 2 WSGI + return s.decode(encoding or self.input_encoding) + return s + + def decode(self, encoding=None): + ''' Returns a copy with all keys and values de- or recoded to match + :attr:`input_encoding`. Some libraries (e.g. WTForms) want a + unicode dictionary. ''' + copy = FormsDict() + enc = copy.input_encoding = encoding or self.input_encoding + copy.recode_unicode = False + for key, value in self.allitems(): + copy.append(self._fix(key, enc), self._fix(value, enc)) + return copy + + def getunicode(self, name, default=None, encoding=None): + try: + return self._fix(self[name], encoding) + except (UnicodeError, KeyError): + return default + + def __getattr__(self, name, default=unicode()): + # Without this guard, pickle generates a cryptic TypeError: + if name.startswith('__') and name.endswith('__'): + return super(FormsDict, self).__getattr__(name) + return self.getunicode(name, default=default) + + +class HeaderDict(MultiDict): + """ A case-insensitive version of :class:`MultiDict` that defaults to + replace the old value instead of appending it. """ + + def __init__(self, *a, **ka): + self.dict = {} + if a or ka: self.update(*a, **ka) + + def __contains__(self, key): return _hkey(key) in self.dict + def __delitem__(self, key): del self.dict[_hkey(key)] + def __getitem__(self, key): return self.dict[_hkey(key)][-1] + def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)] + def append(self, key, value): + self.dict.setdefault(_hkey(key), []).append(str(value)) + def replace(self, key, value): self.dict[_hkey(key)] = [str(value)] + def getall(self, key): return self.dict.get(_hkey(key)) or [] + def get(self, key, default=None, index=-1): + return MultiDict.get(self, _hkey(key), default, index) + def filter(self, names): + for name in [_hkey(n) for n in names]: + if name in self.dict: + del self.dict[name] + + +class WSGIHeaderDict(DictMixin): + ''' This dict-like class wraps a WSGI environ dict and provides convenient + access to HTTP_* fields. Keys and values are native strings + (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI + environment contains non-native string values, these are de- or encoded + using a lossless 'latin1' character set. + + The API will remain stable even on changes to the relevant PEPs. + Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one + that uses non-native strings.) + ''' + #: List of keys that do not have a ``HTTP_`` prefix. + cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') + + def __init__(self, environ): + self.environ = environ + + def _ekey(self, key): + ''' Translate header field name to CGI/WSGI environ key. ''' + key = key.replace('-','_').upper() + if key in self.cgikeys: + return key + return 'HTTP_' + key + + def raw(self, key, default=None): + ''' Return the header value as is (may be bytes or unicode). ''' + return self.environ.get(self._ekey(key), default) + + def __getitem__(self, key): + return tonat(self.environ[self._ekey(key)], 'latin1') + + def __setitem__(self, key, value): + raise TypeError("%s is read-only." % self.__class__) + + def __delitem__(self, key): + raise TypeError("%s is read-only." % self.__class__) + + def __iter__(self): + for key in self.environ: + if key[:5] == 'HTTP_': + yield key[5:].replace('_', '-').title() + elif key in self.cgikeys: + yield key.replace('_', '-').title() + + def keys(self): return [x for x in self] + def __len__(self): return len(self.keys()) + def __contains__(self, key): return self._ekey(key) in self.environ + + +class ConfigDict(dict): + ''' A dict-subclass with some extras: You can access keys like attributes. + Uppercase attributes create new ConfigDicts and act as name-spaces. + Other missing attributes return None. Calling a ConfigDict updates its + values and returns itself. + + >>> cfg = ConfigDict() + >>> cfg.Namespace.value = 5 + >>> cfg.OtherNamespace(a=1, b=2) + >>> cfg + {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}} + ''' + + def __getattr__(self, key): + if key not in self and key[0].isupper(): + self[key] = ConfigDict() + return self.get(key) + + def __setattr__(self, key, value): + if hasattr(dict, key): + raise AttributeError('Read-only attribute.') + if key in self and self[key] and isinstance(self[key], ConfigDict): + raise AttributeError('Non-empty namespace attribute.') + self[key] = value + + def __delattr__(self, key): + if key in self: del self[key] + + def __call__(self, *a, **ka): + for key, value in dict(*a, **ka).items(): setattr(self, key, value) + return self + + +class AppStack(list): + """ A stack-like list. Calling it returns the head of the stack. """ + + def __call__(self): + """ Return the current default application. """ + return self[-1] + + def push(self, value=None): + """ Add a new :class:`Bottle` instance to the stack """ + if not isinstance(value, Bottle): + value = Bottle() + self.append(value) + return value + + +class WSGIFileWrapper(object): + + def __init__(self, fp, buffer_size=1024*64): + self.fp, self.buffer_size = fp, buffer_size + for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'): + if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr)) + + def __iter__(self): + buff, read = self.buffer_size, self.read + while True: + part = read(buff) + if not part: return + yield part + + +class ResourceManager(object): + ''' This class manages a list of search paths and helps to find and open + application-bound resources (files). + + :param base: default value for :meth:`add_path` calls. + :param opener: callable used to open resources. + :param cachemode: controls which lookups are cached. One of 'all', + 'found' or 'none'. + ''' + + def __init__(self, base='./', opener=open, cachemode='all'): + self.opener = open + self.base = base + self.cachemode = cachemode + + #: A list of search paths. See :meth:`add_path` for details. + self.path = [] + #: A cache for resolved paths. ``res.cache.clear()`` clears the cache. + self.cache = {} + + def add_path(self, path, base=None, index=None, create=False): + ''' Add a new path to the list of search paths. Return False if the + path does not exist. + + :param path: The new search path. Relative paths are turned into + an absolute and normalized form. If the path looks like a file + (not ending in `/`), the filename is stripped off. + :param base: Path used to absolutize relative search paths. + Defaults to :attr:`base` which defaults to ``os.getcwd()``. + :param index: Position within the list of search paths. Defaults + to last index (appends to the list). + + The `base` parameter makes it easy to reference files installed + along with a python module or package:: + + res.add_path('./resources/', __file__) + ''' + base = os.path.abspath(os.path.dirname(base or self.base)) + path = os.path.abspath(os.path.join(base, os.path.dirname(path))) + path += os.sep + if path in self.path: + self.path.remove(path) + if create and not os.path.isdir(path): + os.makedirs(path) + if index is None: + self.path.append(path) + else: + self.path.insert(index, path) + self.cache.clear() + return os.path.exists(path) + + def __iter__(self): + ''' Iterate over all existing files in all registered paths. ''' + search = self.path[:] + while search: + path = search.pop() + if not os.path.isdir(path): continue + for name in os.listdir(path): + full = os.path.join(path, name) + if os.path.isdir(full): search.append(full) + else: yield full + + def lookup(self, name): + ''' Search for a resource and return an absolute file path, or `None`. + + The :attr:`path` list is searched in order. The first match is + returend. Symlinks are followed. The result is cached to speed up + future lookups. ''' + if name not in self.cache or DEBUG: + for path in self.path: + fpath = os.path.join(path, name) + if os.path.isfile(fpath): + if self.cachemode in ('all', 'found'): + self.cache[name] = fpath + return fpath + if self.cachemode == 'all': + self.cache[name] = None + return self.cache[name] + + def open(self, name, mode='r', *args, **kwargs): + ''' Find a resource and return a file object, or raise IOError. ''' + fname = self.lookup(name) + if not fname: raise IOError("Resource %r not found." % name) + return self.opener(name, mode=mode, *args, **kwargs) + + + + + + +############################################################################### +# Application Helper ########################################################### +############################################################################### + + +def abort(code=500, text='Unknown Error: Application stopped.'): + """ Aborts execution and causes a HTTP error. """ + raise HTTPError(code, text) + + +def redirect(url, code=None): + """ Aborts execution and causes a 303 or 302 redirect, depending on + the HTTP protocol version. """ + if code is None: + code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 + location = urljoin(request.url, url) + res = HTTPResponse("", status=code, Location=location) + if response._cookies: + res._cookies = response._cookies + raise res + + +def _file_iter_range(fp, offset, bytes, maxread=1024*1024): + ''' Yield chunks from a range in a file. No chunk is bigger than maxread.''' + fp.seek(offset) + while bytes > 0: + part = fp.read(min(bytes, maxread)) + if not part: break + bytes -= len(part) + yield part + + +def static_file(filename, root, mimetype='auto', download=False): + """ Open a file in a safe way and return :exc:`HTTPResponse` with status + code 200, 305, 401 or 404. Set Content-Type, Content-Encoding, + Content-Length and Last-Modified header. Obey If-Modified-Since header + and HEAD requests. + """ + root = os.path.abspath(root) + os.sep + filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) + headers = dict() + + if not filename.startswith(root): + return HTTPError(403, "Access denied.") + if not os.path.exists(filename) or not os.path.isfile(filename): + return HTTPError(404, "File does not exist.") + if not os.access(filename, os.R_OK): + return HTTPError(403, "You do not have permission to access this file.") + + if mimetype == 'auto': + mimetype, encoding = mimetypes.guess_type(filename) + if mimetype: headers['Content-Type'] = mimetype + if encoding: headers['Content-Encoding'] = encoding + elif mimetype: + headers['Content-Type'] = mimetype + + if download: + download = os.path.basename(filename if download == True else download) + headers['Content-Disposition'] = 'attachment; filename="%s"' % download + + stats = os.stat(filename) + headers['Content-Length'] = clen = stats.st_size + lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) + headers['Last-Modified'] = lm + + ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') + if ims: + ims = parse_date(ims.split(";")[0].strip()) + if ims is not None and ims >= int(stats.st_mtime): + headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) + return HTTPResponse(status=304, **headers) + + body = '' if request.method == 'HEAD' else open(filename, 'rb') + + headers["Accept-Ranges"] = "bytes" + ranges = request.environ.get('HTTP_RANGE') + if 'HTTP_RANGE' in request.environ: + ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen)) + if not ranges: + return HTTPError(416, "Requested Range Not Satisfiable") + offset, end = ranges[0] + headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen) + headers["Content-Length"] = str(end-offset) + if body: body = _file_iter_range(body, offset, end-offset) + return HTTPResponse(body, status=206, **headers) + return HTTPResponse(body, **headers) + + + + + + +############################################################################### +# HTTP Utilities and MISC (TODO) ############################################### +############################################################################### + + +def debug(mode=True): + """ Change the debug level. + There is only one debug level supported at the moment.""" + global DEBUG + DEBUG = bool(mode) + + +def parse_date(ims): + """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ + try: + ts = email.utils.parsedate_tz(ims) + return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone + except (TypeError, ValueError, IndexError, OverflowError): + return None + + +def parse_auth(header): + """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" + try: + method, data = header.split(None, 1) + if method.lower() == 'basic': + user, pwd = touni(base64.b64decode(tob(data))).split(':',1) + return user, pwd + except (KeyError, ValueError): + return None + +def parse_range_header(header, maxlen=0): + ''' Yield (start, end) ranges parsed from a HTTP Range header. Skip + unsatisfiable ranges. The end index is non-inclusive.''' + if not header or header[:6] != 'bytes=': return + ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r] + for start, end in ranges: + try: + if not start: # bytes=-100 -> last 100 bytes + start, end = max(0, maxlen-int(end)), maxlen + elif not end: # bytes=100- -> all but the first 99 bytes + start, end = int(start), maxlen + else: # bytes=100-200 -> bytes 100-200 (inclusive) + start, end = int(start), min(int(end)+1, maxlen) + if 0 <= start < end <= maxlen: + yield start, end + except ValueError: + pass + +def _parse_qsl(qs): + r = [] + for pair in qs.replace(';','&').split('&'): + if not pair: continue + nv = pair.split('=', 1) + if len(nv) != 2: nv.append('') + key = urlunquote(nv[0].replace('+', ' ')) + value = urlunquote(nv[1].replace('+', ' ')) + r.append((key, value)) + return r + +def _lscmp(a, b): + ''' Compares two strings in a cryptographically safe way: + Runtime is not affected by length of common prefix. ''' + return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) + + +def cookie_encode(data, key): + ''' Encode and sign a pickle-able object. Return a (byte) string ''' + msg = base64.b64encode(pickle.dumps(data, -1)) + sig = base64.b64encode(hmac.new(tob(key), msg).digest()) + return tob('!') + sig + tob('?') + msg + + +def cookie_decode(data, key): + ''' Verify and decode an encoded string. Return an object or None.''' + data = tob(data) + if cookie_is_encoded(data): + sig, msg = data.split(tob('?'), 1) + if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())): + return pickle.loads(base64.b64decode(msg)) + return None + + +def cookie_is_encoded(data): + ''' Return True if the argument looks like a encoded cookie.''' + return bool(data.startswith(tob('!')) and tob('?') in data) + + +def html_escape(string): + ''' Escape HTML special characters ``&<>`` and quotes ``'"``. ''' + return string.replace('&','&').replace('<','<').replace('>','>')\ + .replace('"','"').replace("'",''') + + +def html_quote(string): + ''' Escape and quote a string to be used as an HTTP attribute.''' + return '"%s"' % html_escape(string).replace('\n','%#10;')\ + .replace('\r',' ').replace('\t','	') + + +def yieldroutes(func): + """ Return a generator for routes that match the signature (name, args) + of the func parameter. This may yield more than one route if the function + takes optional keyword arguments. The output is best described by example:: + + a() -> '/a' + b(x, y) -> '/b/:x/:y' + c(x, y=5) -> '/c/:x' and '/c/:x/:y' + d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y' + """ + import inspect # Expensive module. Only import if necessary. + path = '/' + func.__name__.replace('__','/').lstrip('/') + spec = inspect.getargspec(func) + argc = len(spec[0]) - len(spec[3] or []) + path += ('/:%s' * argc) % tuple(spec[0][:argc]) + yield path + for arg in spec[0][argc:]: + path += '/:%s' % arg + yield path + + +def path_shift(script_name, path_info, shift=1): + ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. + + :return: The modified paths. + :param script_name: The SCRIPT_NAME path. + :param script_name: The PATH_INFO path. + :param shift: The number of path fragments to shift. May be negative to + change the shift direction. (default: 1) + ''' + if shift == 0: return script_name, path_info + pathlist = path_info.strip('/').split('/') + scriptlist = script_name.strip('/').split('/') + if pathlist and pathlist[0] == '': pathlist = [] + if scriptlist and scriptlist[0] == '': scriptlist = [] + if shift > 0 and shift <= len(pathlist): + moved = pathlist[:shift] + scriptlist = scriptlist + moved + pathlist = pathlist[shift:] + elif shift < 0 and shift >= -len(scriptlist): + moved = scriptlist[shift:] + pathlist = moved + pathlist + scriptlist = scriptlist[:shift] + else: + empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO' + raise AssertionError("Cannot shift. Nothing left from %s" % empty) + new_script_name = '/' + '/'.join(scriptlist) + new_path_info = '/' + '/'.join(pathlist) + if path_info.endswith('/') and pathlist: new_path_info += '/' + return new_script_name, new_path_info + + +def validate(**vkargs): + """ + Validates and manipulates keyword arguments by user defined callables. + Handles ValueError and missing arguments by raising HTTPError(403). + """ + depr('Use route wildcard filters instead.') + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kargs): + for key, value in vkargs.items(): + if key not in kargs: + abort(403, 'Missing parameter: %s' % key) + try: + kargs[key] = value(kargs[key]) + except ValueError: + abort(403, 'Wrong parameter format for: %s' % key) + return func(*args, **kargs) + return wrapper + return decorator + + +def auth_basic(check, realm="private", text="Access denied"): + ''' Callback decorator to require HTTP auth (basic). + TODO: Add route(check_auth=...) parameter. ''' + def decorator(func): + def wrapper(*a, **ka): + user, password = request.auth or (None, None) + if user is None or not check(user, password): + response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm + return HTTPError(401, text) + return func(*a, **ka) + return wrapper + return decorator + + +# Shortcuts for common Bottle methods. +# They all refer to the current default application. + +def make_default_app_wrapper(name): + ''' Return a callable that relays calls to the current default app. ''' + @functools.wraps(getattr(Bottle, name)) + def wrapper(*a, **ka): + return getattr(app(), name)(*a, **ka) + return wrapper + +route = make_default_app_wrapper('route') +get = make_default_app_wrapper('get') +post = make_default_app_wrapper('post') +put = make_default_app_wrapper('put') +delete = make_default_app_wrapper('delete') +error = make_default_app_wrapper('error') +mount = make_default_app_wrapper('mount') +hook = make_default_app_wrapper('hook') +install = make_default_app_wrapper('install') +uninstall = make_default_app_wrapper('uninstall') +url = make_default_app_wrapper('get_url') + + + + + + + +############################################################################### +# Server Adapter ############################################################### +############################################################################### + + +class ServerAdapter(object): + quiet = False + def __init__(self, host='127.0.0.1', port=8080, **config): + self.options = config + self.host = host + self.port = int(port) + + def run(self, handler): # pragma: no cover + pass + + def __repr__(self): + args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) + return "%s(%s)" % (self.__class__.__name__, args) + + +class CGIServer(ServerAdapter): + quiet = True + def run(self, handler): # pragma: no cover + from wsgiref.handlers import CGIHandler + def fixed_environ(environ, start_response): + environ.setdefault('PATH_INFO', '') + return handler(environ, start_response) + CGIHandler().run(fixed_environ) + + +class FlupFCGIServer(ServerAdapter): + def run(self, handler): # pragma: no cover + import flup.server.fcgi + self.options.setdefault('bindAddress', (self.host, self.port)) + flup.server.fcgi.WSGIServer(handler, **self.options).run() + + +class WSGIRefServer(ServerAdapter): + def run(self, handler): # pragma: no cover + from wsgiref.simple_server import make_server, WSGIRequestHandler + if self.quiet: + class QuietHandler(WSGIRequestHandler): + def log_request(*args, **kw): pass + self.options['handler_class'] = QuietHandler + srv = make_server(self.host, self.port, handler, **self.options) + srv.serve_forever() + + +class CherryPyServer(ServerAdapter): + def run(self, handler): # pragma: no cover + from cherrypy import wsgiserver + server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) + try: + server.start() + finally: + server.stop() + + +class WaitressServer(ServerAdapter): + def run(self, handler): + from waitress import serve + serve(handler, host=self.host, port=self.port) + + +class PasteServer(ServerAdapter): + def run(self, handler): # pragma: no cover + from paste import httpserver + if not self.quiet: + from paste.translogger import TransLogger + handler = TransLogger(handler) + httpserver.serve(handler, host=self.host, port=str(self.port), + **self.options) + + +class MeinheldServer(ServerAdapter): + def run(self, handler): + from meinheld import server + server.listen((self.host, self.port)) + server.run(handler) + + +class FapwsServer(ServerAdapter): + """ Extremely fast webserver using libev. See http://www.fapws.org/ """ + def run(self, handler): # pragma: no cover + import fapws._evwsgi as evwsgi + from fapws import base, config + port = self.port + if float(config.SERVER_IDENT[-2:]) > 0.4: + # fapws3 silently changed its API in 0.5 + port = str(port) + evwsgi.start(self.host, port) + # fapws3 never releases the GIL. Complain upstream. I tried. No luck. + if 'BOTTLE_CHILD' in os.environ and not self.quiet: + _stderr("WARNING: Auto-reloading does not work with Fapws3.\n") + _stderr(" (Fapws3 breaks python thread support)\n") + evwsgi.set_base_module(base) + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return handler(environ, start_response) + evwsgi.wsgi_cb(('', app)) + evwsgi.run() + + +class TornadoServer(ServerAdapter): + """ The super hyped asynchronous server by facebook. Untested. """ + def run(self, handler): # pragma: no cover + import tornado.wsgi, tornado.httpserver, tornado.ioloop + container = tornado.wsgi.WSGIContainer(handler) + server = tornado.httpserver.HTTPServer(container) + server.listen(port=self.port) + tornado.ioloop.IOLoop.instance().start() + + +class AppEngineServer(ServerAdapter): + """ Adapter for Google App Engine. """ + quiet = True + def run(self, handler): + from google.appengine.ext.webapp import util + # A main() function in the handler script enables 'App Caching'. + # Lets makes sure it is there. This _really_ improves performance. + module = sys.modules.get('__main__') + if module and not hasattr(module, 'main'): + module.main = lambda: util.run_wsgi_app(handler) + util.run_wsgi_app(handler) + + +class TwistedServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from twisted.web import server, wsgi + from twisted.python.threadpool import ThreadPool + from twisted.internet import reactor + thread_pool = ThreadPool() + thread_pool.start() + reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) + factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) + reactor.listenTCP(self.port, factory, interface=self.host) + reactor.run() + + +class DieselServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from diesel.protocols.wsgi import WSGIApplication + app = WSGIApplication(handler, port=self.port) + app.run() + + +class GeventServer(ServerAdapter): + """ Untested. Options: + + * `fast` (default: False) uses libevent's http server, but has some + issues: No streaming, no pipelining, no SSL. + """ + def run(self, handler): + from gevent import wsgi, pywsgi, local + if not isinstance(_lctx, local.local): + msg = "Bottle requires gevent.monkey.patch_all() (before import)" + raise RuntimeError(msg) + if not self.options.get('fast'): wsgi = pywsgi + log = None if self.quiet else 'default' + wsgi.WSGIServer((self.host, self.port), handler, log=log).serve_forever() + + +class GunicornServer(ServerAdapter): + """ Untested. See http://gunicorn.org/configure.html for options. """ + def run(self, handler): + from gunicorn.app.base import Application + + config = {'bind': "%s:%d" % (self.host, int(self.port))} + config.update(self.options) + + class GunicornApplication(Application): + def init(self, parser, opts, args): + return config + + def load(self): + return handler + + GunicornApplication().run() + + +class EventletServer(ServerAdapter): + """ Untested """ + def run(self, handler): + from eventlet import wsgi, listen + try: + wsgi.server(listen((self.host, self.port)), handler, + log_output=(not self.quiet)) + except TypeError: + # Fallback, if we have old version of eventlet + wsgi.server(listen((self.host, self.port)), handler) + + +class RocketServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from rocket import Rocket + server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler }) + server.start() + + +class BjoernServer(ServerAdapter): + """ Fast server written in C: https://github.com/jonashaag/bjoern """ + def run(self, handler): + from bjoern import run + run(handler, self.host, self.port) + + +class AutoServer(ServerAdapter): + """ Untested. """ + adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer] + def run(self, handler): + for sa in self.adapters: + try: + return sa(self.host, self.port, **self.options).run(handler) + except ImportError: + pass + +server_names = { + 'cgi': CGIServer, + 'flup': FlupFCGIServer, + 'wsgiref': WSGIRefServer, + 'waitress': WaitressServer, + 'cherrypy': CherryPyServer, + 'paste': PasteServer, + 'fapws3': FapwsServer, + 'tornado': TornadoServer, + 'gae': AppEngineServer, + 'twisted': TwistedServer, + 'diesel': DieselServer, + 'meinheld': MeinheldServer, + 'gunicorn': GunicornServer, + 'eventlet': EventletServer, + 'gevent': GeventServer, + 'rocket': RocketServer, + 'bjoern' : BjoernServer, + 'auto': AutoServer, +} + + + + + + +############################################################################### +# Application Control ########################################################## +############################################################################### + + +def load(target, **namespace): + """ Import a module or fetch an object from a module. + + * ``package.module`` returns `module` as a module object. + * ``pack.mod:name`` returns the module variable `name` from `pack.mod`. + * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result. + + The last form accepts not only function calls, but any type of + expression. Keyword arguments passed to this function are available as + local variables. Example: ``import_string('re:compile(x)', x='[a-z]')`` + """ + module, target = target.split(":", 1) if ':' in target else (target, None) + if module not in sys.modules: __import__(module) + if not target: return sys.modules[module] + if target.isalnum(): return getattr(sys.modules[module], target) + package_name = module.split('.')[0] + namespace[package_name] = sys.modules[package_name] + return eval('%s.%s' % (module, target), namespace) + + +def load_app(target): + """ Load a bottle application from a module and make sure that the import + does not affect the current default application, but returns a separate + application object. See :func:`load` for the target parameter. """ + global NORUN; NORUN, nr_old = True, NORUN + try: + tmp = default_app.push() # Create a new "default application" + rv = load(target) # Import the target module + return rv if callable(rv) else tmp + finally: + default_app.remove(tmp) # Remove the temporary added default application + NORUN = nr_old + +_debug = debug +def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, + interval=1, reloader=False, quiet=False, plugins=None, + debug=False, **kargs): + """ Start a server instance. This method blocks until the server terminates. + + :param app: WSGI application or target string supported by + :func:`load_app`. (default: :func:`default_app`) + :param server: Server adapter to use. See :data:`server_names` keys + for valid names or pass a :class:`ServerAdapter` subclass. + (default: `wsgiref`) + :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on + all interfaces including the external one. (default: 127.0.0.1) + :param port: Server port to bind to. Values below 1024 require root + privileges. (default: 8080) + :param reloader: Start auto-reloading server? (default: False) + :param interval: Auto-reloader interval in seconds (default: 1) + :param quiet: Suppress output to stdout and stderr? (default: False) + :param options: Options passed to the server adapter. + """ + if NORUN: return + if reloader and not os.environ.get('BOTTLE_CHILD'): + try: + lockfile = None + fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') + os.close(fd) # We only need this file to exist. We never write to it + while os.path.exists(lockfile): + args = [sys.executable] + sys.argv + environ = os.environ.copy() + environ['BOTTLE_CHILD'] = 'true' + environ['BOTTLE_LOCKFILE'] = lockfile + p = subprocess.Popen(args, env=environ) + while p.poll() is None: # Busy wait... + os.utime(lockfile, None) # I am alive! + time.sleep(interval) + if p.poll() != 3: + if os.path.exists(lockfile): os.unlink(lockfile) + sys.exit(p.poll()) + except KeyboardInterrupt: + pass + finally: + if os.path.exists(lockfile): + os.unlink(lockfile) + return + + try: + _debug(debug) + app = app or default_app() + if isinstance(app, basestring): + app = load_app(app) + if not callable(app): + raise ValueError("Application is not callable: %r" % app) + + for plugin in plugins or []: + app.install(plugin) + + if server in server_names: + server = server_names.get(server) + if isinstance(server, basestring): + server = load(server) + if isinstance(server, type): + server = server(host=host, port=port, **kargs) + if not isinstance(server, ServerAdapter): + raise ValueError("Unknown or unsupported server: %r" % server) + + server.quiet = server.quiet or quiet + if not server.quiet: + _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server))) + _stderr("Listening on http://%s:%d/\n" % (server.host, server.port)) + _stderr("Hit Ctrl-C to quit.\n\n") + + if reloader: + lockfile = os.environ.get('BOTTLE_LOCKFILE') + bgcheck = FileCheckerThread(lockfile, interval) + with bgcheck: + server.run(app) + if bgcheck.status == 'reload': + sys.exit(3) + else: + server.run(app) + except KeyboardInterrupt: + pass + except (SystemExit, MemoryError): + raise + except: + if not reloader: raise + if not getattr(server, 'quiet', quiet): + print_exc() + time.sleep(interval) + sys.exit(3) + + + +class FileCheckerThread(threading.Thread): + ''' Interrupt main-thread as soon as a changed module file is detected, + the lockfile gets deleted or gets to old. ''' + + def __init__(self, lockfile, interval): + threading.Thread.__init__(self) + self.lockfile, self.interval = lockfile, interval + #: Is one of 'reload', 'error' or 'exit' + self.status = None + + def run(self): + exists = os.path.exists + mtime = lambda path: os.stat(path).st_mtime + files = dict() + + for module in list(sys.modules.values()): + path = getattr(module, '__file__', '') + if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] + if path and exists(path): files[path] = mtime(path) + + while not self.status: + if not exists(self.lockfile)\ + or mtime(self.lockfile) < time.time() - self.interval - 5: + self.status = 'error' + thread.interrupt_main() + for path, lmtime in list(files.items()): + if not exists(path) or mtime(path) > lmtime: + self.status = 'reload' + thread.interrupt_main() + break + time.sleep(self.interval) + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self.status: self.status = 'exit' # silent exit + self.join() + return exc_type is not None and issubclass(exc_type, KeyboardInterrupt) + + + + + +############################################################################### +# Template Adapters ############################################################ +############################################################################### + + +class TemplateError(HTTPError): + def __init__(self, message): + HTTPError.__init__(self, 500, message) + + +class BaseTemplate(object): + """ Base class and minimal API for template adapters """ + extensions = ['tpl','html','thtml','stpl'] + settings = {} #used in prepare() + defaults = {} #used in render() + + def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings): + """ Create a new template. + If the source parameter (str or buffer) is missing, the name argument + is used to guess a template filename. Subclasses can assume that + self.source and/or self.filename are set. Both are strings. + The lookup, encoding and settings parameters are stored as instance + variables. + The lookup parameter stores a list containing directory paths. + The encoding parameter should be used to decode byte strings or files. + The settings parameter contains a dict for engine-specific settings. + """ + self.name = name + self.source = source.read() if hasattr(source, 'read') else source + self.filename = source.filename if hasattr(source, 'filename') else None + self.lookup = [os.path.abspath(x) for x in lookup] + self.encoding = encoding + self.settings = self.settings.copy() # Copy from class variable + self.settings.update(settings) # Apply + if not self.source and self.name: + self.filename = self.search(self.name, self.lookup) + if not self.filename: + raise TemplateError('Template %s not found.' % repr(name)) + if not self.source and not self.filename: + raise TemplateError('No template specified.') + self.prepare(**self.settings) + + @classmethod + def search(cls, name, lookup=[]): + """ Search name in all directories specified in lookup. + First without, then with common extensions. Return first hit. """ + if not lookup: + depr('The template lookup path list should not be empty.') + lookup = ['.'] + + if os.path.isabs(name) and os.path.isfile(name): + depr('Absolute template path names are deprecated.') + return os.path.abspath(name) + + for spath in lookup: + spath = os.path.abspath(spath) + os.sep + fname = os.path.abspath(os.path.join(spath, name)) + if not fname.startswith(spath): continue + if os.path.isfile(fname): return fname + for ext in cls.extensions: + if os.path.isfile('%s.%s' % (fname, ext)): + return '%s.%s' % (fname, ext) + + @classmethod + def global_config(cls, key, *args): + ''' This reads or sets the global settings stored in class.settings. ''' + if args: + cls.settings = cls.settings.copy() # Make settings local to class + cls.settings[key] = args[0] + else: + return cls.settings[key] + + def prepare(self, **options): + """ Run preparations (parsing, caching, ...). + It should be possible to call this again to refresh a template or to + update settings. + """ + raise NotImplementedError + + def render(self, *args, **kwargs): + """ Render the template with the specified local variables and return + a single byte or unicode string. If it is a byte string, the encoding + must match self.encoding. This method must be thread-safe! + Local variables may be provided in dictionaries (*args) + or directly, as keywords (**kwargs). + """ + raise NotImplementedError + + +class MakoTemplate(BaseTemplate): + def prepare(self, **options): + from mako.template import Template + from mako.lookup import TemplateLookup + options.update({'input_encoding':self.encoding}) + options.setdefault('format_exceptions', bool(DEBUG)) + lookup = TemplateLookup(directories=self.lookup, **options) + if self.source: + self.tpl = Template(self.source, lookup=lookup, **options) + else: + self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + _defaults = self.defaults.copy() + _defaults.update(kwargs) + return self.tpl.render(**_defaults) + + +class CheetahTemplate(BaseTemplate): + def prepare(self, **options): + from Cheetah.Template import Template + self.context = threading.local() + self.context.vars = {} + options['searchList'] = [self.context.vars] + if self.source: + self.tpl = Template(source=self.source, **options) + else: + self.tpl = Template(file=self.filename, **options) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + self.context.vars.update(self.defaults) + self.context.vars.update(kwargs) + out = str(self.tpl) + self.context.vars.clear() + return out + + +class Jinja2Template(BaseTemplate): + def prepare(self, filters=None, tests=None, **kwargs): + from jinja2 import Environment, FunctionLoader + if 'prefix' in kwargs: # TODO: to be removed after a while + raise RuntimeError('The keyword argument `prefix` has been removed. ' + 'Use the full jinja2 environment name line_statement_prefix instead.') + self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) + if filters: self.env.filters.update(filters) + if tests: self.env.tests.update(tests) + if self.source: + self.tpl = self.env.from_string(self.source) + else: + self.tpl = self.env.get_template(self.filename) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + _defaults = self.defaults.copy() + _defaults.update(kwargs) + return self.tpl.render(**_defaults) + + def loader(self, name): + fname = self.search(name, self.lookup) + if not fname: return + with open(fname, "rb") as f: + return f.read().decode(self.encoding) + + +class SimpleTALTemplate(BaseTemplate): + ''' Deprecated, do not use. ''' + def prepare(self, **options): + depr('The SimpleTAL template handler is deprecated'\ + ' and will be removed in 0.12') + from simpletal import simpleTAL + if self.source: + self.tpl = simpleTAL.compileHTMLTemplate(self.source) + else: + with open(self.filename, 'rb') as fp: + self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read())) + + def render(self, *args, **kwargs): + from simpletal import simpleTALES + for dictarg in args: kwargs.update(dictarg) + context = simpleTALES.Context() + for k,v in self.defaults.items(): + context.addGlobal(k, v) + for k,v in kwargs.items(): + context.addGlobal(k, v) + output = StringIO() + self.tpl.expand(context, output) + return output.getvalue() + + +class SimpleTemplate(BaseTemplate): + blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while', + 'with', 'def', 'class') + dedent_blocks = ('elif', 'else', 'except', 'finally') + + @lazy_attribute + def re_pytokens(cls): + ''' This matches comments and all kinds of quoted strings but does + NOT match comments (#...) within quoted strings. (trust me) ''' + return re.compile(r''' + (''(?!')|""(?!")|'{6}|"{6} # Empty strings (all 4 types) + |'(?:[^\\']|\\.)+?' # Single quotes (') + |"(?:[^\\"]|\\.)+?" # Double quotes (") + |'{3}(?:[^\\]|\\.|\n)+?'{3} # Triple-quoted strings (') + |"{3}(?:[^\\]|\\.|\n)+?"{3} # Triple-quoted strings (") + |\#.* # Comments + )''', re.VERBOSE) + + def prepare(self, escape_func=html_escape, noescape=False, **kwargs): + self.cache = {} + enc = self.encoding + self._str = lambda x: touni(x, enc) + self._escape = lambda x: escape_func(touni(x, enc)) + if noescape: + self._str, self._escape = self._escape, self._str + + @classmethod + def split_comment(cls, code): + """ Removes comments (#...) from python code. """ + if '#' not in code: return code + #: Remove comments only (leave quoted strings as they are) + subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0) + return re.sub(cls.re_pytokens, subf, code) + + @cached_property + def co(self): + return compile(self.code, self.filename or '<string>', 'exec') + + @cached_property + def code(self): + stack = [] # Current Code indentation + lineno = 0 # Current line of code + ptrbuffer = [] # Buffer for printable strings and token tuple instances + codebuffer = [] # Buffer for generated python code + multiline = dedent = oneline = False + template = self.source or open(self.filename, 'rb').read() + + def yield_tokens(line): + for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)): + if i % 2: + if part.startswith('!'): yield 'RAW', part[1:] + else: yield 'CMD', part + else: yield 'TXT', part + + def flush(): # Flush the ptrbuffer + if not ptrbuffer: return + cline = '' + for line in ptrbuffer: + for token, value in line: + if token == 'TXT': cline += repr(value) + elif token == 'RAW': cline += '_str(%s)' % value + elif token == 'CMD': cline += '_escape(%s)' % value + cline += ', ' + cline = cline[:-2] + '\\\n' + cline = cline[:-2] + if cline[:-1].endswith('\\\\\\\\\\n'): + cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr' + cline = '_printlist([' + cline + '])' + del ptrbuffer[:] # Do this before calling code() again + code(cline) + + def code(stmt): + for line in stmt.splitlines(): + codebuffer.append(' ' * len(stack) + line.strip()) + + for line in template.splitlines(True): + lineno += 1 + line = touni(line, self.encoding) + sline = line.lstrip() + if lineno <= 2: + m = re.match(r"%\s*#.*coding[:=]\s*([-\w.]+)", sline) + if m: self.encoding = m.group(1) + if m: line = line.replace('coding','coding (removed)') + if sline and sline[0] == '%' and sline[:2] != '%%': + line = line.split('%',1)[1].lstrip() # Full line following the % + cline = self.split_comment(line).strip() + cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0] + flush() # You are actually reading this? Good luck, it's a mess :) + if cmd in self.blocks or multiline: + cmd = multiline or cmd + dedent = cmd in self.dedent_blocks # "else:" + if dedent and not oneline and not multiline: + cmd = stack.pop() + code(line) + oneline = not cline.endswith(':') # "if 1: pass" + multiline = cmd if cline.endswith('\\') else False + if not oneline and not multiline: + stack.append(cmd) + elif cmd == 'end' and stack: + code('#end(%s) %s' % (stack.pop(), line.strip()[3:])) + elif cmd == 'include': + p = cline.split(None, 2)[1:] + if len(p) == 2: + code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1])) + elif p: + code("_=_include(%s, _stdout)" % repr(p[0])) + else: # Empty %include -> reverse of %rebase + code("_printlist(_base)") + elif cmd == 'rebase': + p = cline.split(None, 2)[1:] + if len(p) == 2: + code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1])) + elif p: + code("globals()['_rebase']=(%s, {})" % repr(p[0])) + else: + code(line) + else: # Line starting with text (not '%') or '%%' (escaped) + if line.strip().startswith('%%'): + line = line.replace('%%', '%', 1) + ptrbuffer.append(yield_tokens(line)) + flush() + return '\n'.join(codebuffer) + '\n' + + def subtemplate(self, _name, _stdout, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + if _name not in self.cache: + self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) + return self.cache[_name].execute(_stdout, kwargs) + + def execute(self, _stdout, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + env = self.defaults.copy() + env.update({'_stdout': _stdout, '_printlist': _stdout.extend, + '_include': self.subtemplate, '_str': self._str, + '_escape': self._escape, 'get': env.get, + 'setdefault': env.setdefault, 'defined': env.__contains__}) + env.update(kwargs) + eval(self.co, env) + if '_rebase' in env: + subtpl, rargs = env['_rebase'] + rargs['_base'] = _stdout[:] #copy stdout + del _stdout[:] # clear stdout + return self.subtemplate(subtpl,_stdout,rargs) + return env + + def render(self, *args, **kwargs): + """ Render the template using keyword arguments as local variables. """ + for dictarg in args: kwargs.update(dictarg) + stdout = [] + self.execute(stdout, kwargs) + return ''.join(stdout) + + +def template(*args, **kwargs): + ''' + Get a rendered template as a string iterator. + You can use a name, a filename or a template string as first parameter. + Template rendering arguments can be passed as dictionaries + or directly (as keyword arguments). + ''' + tpl = args[0] if args else None + adapter = kwargs.pop('template_adapter', SimpleTemplate) + lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) + tplid = (id(lookup), tpl) + if tplid not in TEMPLATES or DEBUG: + settings = kwargs.pop('template_settings', {}) + if isinstance(tpl, adapter): + TEMPLATES[tplid] = tpl + if settings: TEMPLATES[tplid].prepare(**settings) + elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: + TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) + else: + TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) + if not TEMPLATES[tplid]: + abort(500, 'Template (%s) not found' % tpl) + for dictarg in args[1:]: kwargs.update(dictarg) + return TEMPLATES[tplid].render(kwargs) + +mako_template = functools.partial(template, template_adapter=MakoTemplate) +cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) +jinja2_template = functools.partial(template, template_adapter=Jinja2Template) +simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate) + + +def view(tpl_name, **defaults): + ''' Decorator: renders a template for a handler. + The handler can control its behavior like that: + + - return a dict of template vars to fill out the template + - return something other than a dict and the view decorator will not + process the template, but return the handler result as is. + This includes returning a HTTPResponse(dict) to get, + for instance, JSON with autojson or other castfilters. + ''' + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + if isinstance(result, (dict, DictMixin)): + tplvars = defaults.copy() + tplvars.update(result) + return template(tpl_name, **tplvars) + return result + return wrapper + return decorator + +mako_view = functools.partial(view, template_adapter=MakoTemplate) +cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) +jinja2_view = functools.partial(view, template_adapter=Jinja2Template) +simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate) + + + + + + +############################################################################### +# Constants and Globals ######################################################## +############################################################################### + + +TEMPLATE_PATH = ['./', './views/'] +TEMPLATES = {} +DEBUG = False +NORUN = False # If set, run() does nothing. Used by load_app() + +#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') +HTTP_CODES = httplib.responses +HTTP_CODES[418] = "I'm a teapot" # RFC 2324 +HTTP_CODES[428] = "Precondition Required" +HTTP_CODES[429] = "Too Many Requests" +HTTP_CODES[431] = "Request Header Fields Too Large" +HTTP_CODES[511] = "Network Authentication Required" +_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items()) + +#: The default template used for error pages. Override with @error() +ERROR_PAGE_TEMPLATE = """ +%%try: + %%from %s import DEBUG, HTTP_CODES, request, touni + <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> + <html> + <head> + <title>Error: {{e.status}}</title> + <style type="text/css"> + html {background-color: #eee; font-family: sans;} + body {background-color: #fff; border: 1px solid #ddd; + padding: 15px; margin: 15px;} + pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;} + </style> + </head> + <body> + <h1>Error: {{e.status}}</h1> + <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt> + caused an error:</p> + <pre>{{e.body}}</pre> + %%if DEBUG and e.exception: + <h2>Exception:</h2> + <pre>{{repr(e.exception)}}</pre> + %%end + %%if DEBUG and e.traceback: + <h2>Traceback:</h2> + <pre>{{e.traceback}}</pre> + %%end + </body> + </html> +%%except ImportError: + <b>ImportError:</b> Could not generate the error page. Please add bottle to + the import path. +%%end +""" % __name__ + +#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a +#: request callback, this instance always refers to the *current* request +#: (even on a multithreaded server). +request = LocalRequest() + +#: A thread-safe instance of :class:`LocalResponse`. It is used to change the +#: HTTP response for the *current* request. +response = LocalResponse() + +#: A thread-safe namespace. Not used by Bottle. +local = threading.local() + +# Initialize app stack (create first empty Bottle app) +# BC: 0.6.4 and needed for run() +app = default_app = AppStack() +app.push() + +#: A virtual package that redirects import statements. +#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. +ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else __name__+".ext", 'bottle_%s').module + +if __name__ == '__main__': + opt, args, parser = _cmd_options, _cmd_args, _cmd_parser + if opt.version: + _stdout('Bottle %s\n'%__version__) + sys.exit(0) + if not args: + parser.print_help() + _stderr('\nError: No application specified.\n') + sys.exit(1) + + sys.path.insert(0, '.') + sys.modules.setdefault('bottle', sys.modules['__main__']) + + host, port = (opt.bind or 'localhost'), 8080 + if ':' in host: + host, port = host.rsplit(':', 1) + + run(args[0], host=host, port=port, server=opt.server, + reloader=opt.reload, plugins=opt.plugin, debug=opt.debug) + + + + +# THE END diff --git a/pyload/lib/colorlog/__init__.py b/pyload/lib/colorlog/__init__.py new file mode 100644 index 000000000..abf5532fb --- /dev/null +++ b/pyload/lib/colorlog/__init__.py @@ -0,0 +1,8 @@ +"""A logging formatter for colored output""" + +from __future__ import absolute_import + +__all__ = ['ColoredFormatter', 'default_log_colors', 'escape_codes'] + +from colorlog.colorlog import ( + ColoredFormatter, default_log_colors, escape_codes) diff --git a/pyload/lib/colorlog/colorlog.py b/pyload/lib/colorlog/colorlog.py new file mode 100644 index 000000000..5491676b8 --- /dev/null +++ b/pyload/lib/colorlog/colorlog.py @@ -0,0 +1,76 @@ +"""The ColoredFormatter class""" + +from __future__ import absolute_import + +import sys +import logging + +from colorlog.escape_codes import escape_codes + +__all__ = ['escape_codes', 'default_log_colors', 'ColoredFormatter'] + +# The default colors to use for the debug levels +default_log_colors = { + 'DEBUG': 'white', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'bold_red', +} + + +class ColoredFormatter(logging.Formatter): + """A formatter that allows colors to be placed in the format string. + + Intended to help in creating more readable logging output.""" + + def __init__(self, format, datefmt=None, + log_colors=default_log_colors, reset=True, style='%'): + """ + :Parameters: + - format (str): The format string to use + - datefmt (str): A format string for the date + - log_colors (dict): + A mapping of log level names to color names + - reset (bool): + Implictly append a color reset to all records unless False + - style ('%' or '{' or '$'): + The format style to use. No meaning prior to Python 3.2. + + The ``format``, ``datefmt`` and ``style`` args are passed on to the + Formatter constructor. + """ + if sys.version_info > (3, 2): + super(ColoredFormatter, self).__init__( + format, datefmt, style=style) + elif sys.version_info > (2, 7): + super(ColoredFormatter, self).__init__(format, datefmt) + else: + logging.Formatter.__init__(self, format, datefmt) + self.log_colors = log_colors + self.reset = reset + + def format(self, record): + # Add the color codes to the record + record.__dict__.update(escape_codes) + + # If we recognise the level name, + # add the levels color as `log_color` + if record.levelname in self.log_colors: + color = self.log_colors[record.levelname] + record.log_color = escape_codes[color] + else: + record.log_color = "" + + # Format the message + if sys.version_info > (2, 7): + message = super(ColoredFormatter, self).format(record) + else: + message = logging.Formatter.format(self, record) + + # Add a reset code to the end of the message + # (if it wasn't explicitly added in format str) + if self.reset and not message.endswith(escape_codes['reset']): + message += escape_codes['reset'] + + return message diff --git a/pyload/lib/colorlog/escape_codes.py b/pyload/lib/colorlog/escape_codes.py new file mode 100644 index 000000000..8d057e9e4 --- /dev/null +++ b/pyload/lib/colorlog/escape_codes.py @@ -0,0 +1,37 @@ +""" +Generates a dictionary of ANSI escape codes + +http://en.wikipedia.org/wiki/ANSI_escape_code +""" + +__all__ = ['escape_codes'] + +# Returns escape codes from format codes +esc = lambda *x: '\033[' + ';'.join(x) + 'm' + +# The initial list of escape codes +escape_codes = { + 'reset': esc('39', '49', '0'), + 'bold': esc('01'), +} + +# The color names +colors = [ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'purple', + 'cyan', + 'white' +] + +# Create foreground and background colors... +for lcode, lname in [('3', ''), ('4', 'bg_')]: + # ...with the list of colors... + for code, name in enumerate(colors): + code = str(code) + # ...and both normal and bold versions of each color + escape_codes[lname + name] = esc(lcode + code) + escape_codes[lname + "bold_" + name] = esc(lcode + code, "01") diff --git a/module/forwarder.py b/pyload/lib/forwarder.py index eacb33c2b..eacb33c2b 100644 --- a/module/forwarder.py +++ b/pyload/lib/forwarder.py diff --git a/pyload/lib/hg_tool.py b/pyload/lib/hg_tool.py new file mode 100644 index 000000000..cd97833df --- /dev/null +++ b/pyload/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/pyload/lib/mod_pywebsocket/COPYING b/pyload/lib/mod_pywebsocket/COPYING new file mode 100644 index 000000000..989d02e4c --- /dev/null +++ b/pyload/lib/mod_pywebsocket/COPYING @@ -0,0 +1,28 @@ +Copyright 2012, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pyload/lib/mod_pywebsocket/__init__.py b/pyload/lib/mod_pywebsocket/__init__.py new file mode 100644 index 000000000..454ae0c45 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/__init__.py @@ -0,0 +1,197 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""WebSocket extension for Apache HTTP Server. + +mod_pywebsocket is a WebSocket extension for Apache HTTP Server +intended for testing or experimental purposes. mod_python is required. + + +Installation +============ + +0. Prepare an Apache HTTP Server for which mod_python is enabled. + +1. Specify the following Apache HTTP Server directives to suit your + configuration. + + If mod_pywebsocket is not in the Python path, specify the following. + <websock_lib> is the directory where mod_pywebsocket is installed. + + PythonPath "sys.path+['<websock_lib>']" + + Always specify the following. <websock_handlers> is the directory where + user-written WebSocket handlers are placed. + + PythonOption mod_pywebsocket.handler_root <websock_handlers> + PythonHeaderParserHandler mod_pywebsocket.headerparserhandler + + To limit the search for WebSocket handlers to a directory <scan_dir> + under <websock_handlers>, configure as follows: + + PythonOption mod_pywebsocket.handler_scan <scan_dir> + + <scan_dir> is useful in saving scan time when <websock_handlers> + contains many non-WebSocket handler files. + + If you want to allow handlers whose canonical path is not under the root + directory (i.e. symbolic link is in root directory but its target is not), + configure as follows: + + PythonOption mod_pywebsocket.allow_handlers_outside_root_dir On + + Example snippet of httpd.conf: + (mod_pywebsocket is in /websock_lib, WebSocket handlers are in + /websock_handlers, port is 80 for ws, 443 for wss.) + + <IfModule python_module> + PythonPath "sys.path+['/websock_lib']" + PythonOption mod_pywebsocket.handler_root /websock_handlers + PythonHeaderParserHandler mod_pywebsocket.headerparserhandler + </IfModule> + +2. Tune Apache parameters for serving WebSocket. We'd like to note that at + least TimeOut directive from core features and RequestReadTimeout + directive from mod_reqtimeout should be modified not to kill connections + in only a few seconds of idle time. + +3. Verify installation. You can use example/console.html to poke the server. + + +Writing WebSocket handlers +========================== + +When a WebSocket request comes in, the resource name +specified in the handshake is considered as if it is a file path under +<websock_handlers> and the handler defined in +<websock_handlers>/<resource_name>_wsh.py is invoked. + +For example, if the resource name is /example/chat, the handler defined in +<websock_handlers>/example/chat_wsh.py is invoked. + +A WebSocket handler is composed of the following three functions: + + web_socket_do_extra_handshake(request) + web_socket_transfer_data(request) + web_socket_passive_closing_handshake(request) + +where: + request: mod_python request. + +web_socket_do_extra_handshake is called during the handshake after the +headers are successfully parsed and WebSocket properties (ws_location, +ws_origin, and ws_resource) are added to request. A handler +can reject the request by raising an exception. + +A request object has the following properties that you can use during the +extra handshake (web_socket_do_extra_handshake): +- ws_resource +- ws_origin +- ws_version +- ws_location (HyBi 00 only) +- ws_extensions (HyBi 06 and later) +- ws_deflate (HyBi 06 and later) +- ws_protocol +- ws_requested_protocols (HyBi 06 and later) + +The last two are a bit tricky. See the next subsection. + + +Subprotocol Negotiation +----------------------- + +For HyBi 06 and later, ws_protocol is always set to None when +web_socket_do_extra_handshake is called. If ws_requested_protocols is not +None, you must choose one subprotocol from this list and set it to +ws_protocol. + +For HyBi 00, when web_socket_do_extra_handshake is called, +ws_protocol is set to the value given by the client in +Sec-WebSocket-Protocol header or None if +such header was not found in the opening handshake request. Finish extra +handshake with ws_protocol untouched to accept the request subprotocol. +Then, Sec-WebSocket-Protocol header will be sent to +the client in response with the same value as requested. Raise an exception +in web_socket_do_extra_handshake to reject the requested subprotocol. + + +Data Transfer +------------- + +web_socket_transfer_data is called after the handshake completed +successfully. A handler can receive/send messages from/to the client +using request. mod_pywebsocket.msgutil module provides utilities +for data transfer. + +You can receive a message by the following statement. + + message = request.ws_stream.receive_message() + +This call blocks until any complete text frame arrives, and the payload data +of the incoming frame will be stored into message. When you're using IETF +HyBi 00 or later protocol, receive_message() will return None on receiving +client-initiated closing handshake. When any error occurs, receive_message() +will raise some exception. + +You can send a message by the following statement. + + request.ws_stream.send_message(message) + + +Closing Connection +------------------ + +Executing the following statement or just return-ing from +web_socket_transfer_data cause connection close. + + request.ws_stream.close_connection() + +close_connection will wait +for closing handshake acknowledgement coming from the client. When it +couldn't receive a valid acknowledgement, raises an exception. + +web_socket_passive_closing_handshake is called after the server receives +incoming closing frame from the client peer immediately. You can specify +code and reason by return values. They are sent as a outgoing closing frame +from the server. A request object has the following properties that you can +use in web_socket_passive_closing_handshake. +- ws_close_code +- ws_close_reason + + +Threading +--------- + +A WebSocket handler must be thread-safe if the server (Apache or +standalone.py) is configured to use threads. +""" + + +# vi:sts=4 sw=4 et tw=72 diff --git a/pyload/lib/mod_pywebsocket/_stream_base.py b/pyload/lib/mod_pywebsocket/_stream_base.py new file mode 100644 index 000000000..60fb33d2c --- /dev/null +++ b/pyload/lib/mod_pywebsocket/_stream_base.py @@ -0,0 +1,165 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Base stream class. +""" + + +# Note: request.connection.write/read are used in this module, even though +# mod_python document says that they should be used only in connection +# handlers. Unfortunately, we have no other options. For example, +# request.write/read are not suitable because they don't allow direct raw bytes +# writing/reading. + + +from mod_pywebsocket import util + + +# Exceptions + + +class ConnectionTerminatedException(Exception): + """This exception will be raised when a connection is terminated + unexpectedly. + """ + + pass + + +class InvalidFrameException(ConnectionTerminatedException): + """This exception will be raised when we received an invalid frame we + cannot parse. + """ + + pass + + +class BadOperationException(Exception): + """This exception will be raised when send_message() is called on + server-terminated connection or receive_message() is called on + client-terminated connection. + """ + + pass + + +class UnsupportedFrameException(Exception): + """This exception will be raised when we receive a frame with flag, opcode + we cannot handle. Handlers can just catch and ignore this exception and + call receive_message() again to continue processing the next frame. + """ + + pass + + +class InvalidUTF8Exception(Exception): + """This exception will be raised when we receive a text frame which + contains invalid UTF-8 strings. + """ + + pass + + +class StreamBase(object): + """Base stream class.""" + + def __init__(self, request): + """Construct an instance. + + Args: + request: mod_python request. + """ + + self._logger = util.get_class_logger(self) + + self._request = request + + def _read(self, length): + """Reads length bytes from connection. In case we catch any exception, + prepends remote address to the exception message and raise again. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + bytes = self._request.connection.read(length) + if not bytes: + raise ConnectionTerminatedException( + 'Receiving %d byte failed. Peer (%r) closed connection' % + (length, (self._request.connection.remote_addr,))) + return bytes + + def _write(self, bytes): + """Writes given bytes to connection. In case we catch any exception, + prepends remote address to the exception message and raise again. + """ + + try: + self._request.connection.write(bytes) + except Exception, e: + util.prepend_message_to_exception( + 'Failed to send message to %r: ' % + (self._request.connection.remote_addr,), + e) + raise + + def receive_bytes(self, length): + """Receives multiple bytes. Retries read when we couldn't receive the + specified amount. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + bytes = [] + while length > 0: + new_bytes = self._read(length) + bytes.append(new_bytes) + length -= len(new_bytes) + return ''.join(bytes) + + def _read_until(self, delim_char): + """Reads bytes until we encounter delim_char. The result will not + contain delim_char. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + bytes = [] + while True: + ch = self._read(1) + if ch == delim_char: + break + bytes.append(ch) + return ''.join(bytes) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/_stream_hixie75.py b/pyload/lib/mod_pywebsocket/_stream_hixie75.py new file mode 100644 index 000000000..94cf5b31b --- /dev/null +++ b/pyload/lib/mod_pywebsocket/_stream_hixie75.py @@ -0,0 +1,229 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file provides a class for parsing/building frames of the WebSocket +protocol version HyBi 00 and Hixie 75. + +Specification: +- HyBi 00 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 +- Hixie 75 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 +""" + + +from mod_pywebsocket import common +from mod_pywebsocket._stream_base import BadOperationException +from mod_pywebsocket._stream_base import ConnectionTerminatedException +from mod_pywebsocket._stream_base import InvalidFrameException +from mod_pywebsocket._stream_base import StreamBase +from mod_pywebsocket._stream_base import UnsupportedFrameException +from mod_pywebsocket import util + + +class StreamHixie75(StreamBase): + """A class for parsing/building frames of the WebSocket protocol version + HyBi 00 and Hixie 75. + """ + + def __init__(self, request, enable_closing_handshake=False): + """Construct an instance. + + Args: + request: mod_python request. + enable_closing_handshake: to let StreamHixie75 perform closing + handshake as specified in HyBi 00, set + this option to True. + """ + + StreamBase.__init__(self, request) + + self._logger = util.get_class_logger(self) + + self._enable_closing_handshake = enable_closing_handshake + + self._request.client_terminated = False + self._request.server_terminated = False + + def send_message(self, message, end=True, binary=False): + """Send message. + + Args: + message: unicode string to send. + binary: not used in hixie75. + + Raises: + BadOperationException: when called on a server-terminated + connection. + """ + + if not end: + raise BadOperationException( + 'StreamHixie75 doesn\'t support send_message with end=False') + + if binary: + raise BadOperationException( + 'StreamHixie75 doesn\'t support send_message with binary=True') + + if self._request.server_terminated: + raise BadOperationException( + 'Requested send_message after sending out a closing handshake') + + self._write(''.join(['\x00', message.encode('utf-8'), '\xff'])) + + def _read_payload_length_hixie75(self): + """Reads a length header in a Hixie75 version frame with length. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + length = 0 + while True: + b_str = self._read(1) + b = ord(b_str) + length = length * 128 + (b & 0x7f) + if (b & 0x80) == 0: + break + return length + + def receive_message(self): + """Receive a WebSocket frame and return its payload an unicode string. + + Returns: + payload unicode string in a WebSocket frame. + + Raises: + ConnectionTerminatedException: when read returns empty + string. + BadOperationException: when called on a client-terminated + connection. + """ + + if self._request.client_terminated: + raise BadOperationException( + 'Requested receive_message after receiving a closing ' + 'handshake') + + while True: + # Read 1 byte. + # mp_conn.read will block if no bytes are available. + # Timeout is controlled by TimeOut directive of Apache. + frame_type_str = self.receive_bytes(1) + frame_type = ord(frame_type_str) + if (frame_type & 0x80) == 0x80: + # The payload length is specified in the frame. + # Read and discard. + length = self._read_payload_length_hixie75() + if length > 0: + _ = self.receive_bytes(length) + # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the + # /client terminated/ flag and abort these steps. + if not self._enable_closing_handshake: + continue + + if frame_type == 0xFF and length == 0: + self._request.client_terminated = True + + if self._request.server_terminated: + self._logger.debug( + 'Received ack for server-initiated closing ' + 'handshake') + return None + + self._logger.debug( + 'Received client-initiated closing handshake') + + self._send_closing_handshake() + self._logger.debug( + 'Sent ack for client-initiated closing handshake') + return None + else: + # The payload is delimited with \xff. + bytes = self._read_until('\xff') + # The WebSocket protocol section 4.4 specifies that invalid + # characters must be replaced with U+fffd REPLACEMENT + # CHARACTER. + message = bytes.decode('utf-8', 'replace') + if frame_type == 0x00: + return message + # Discard data of other types. + + def _send_closing_handshake(self): + if not self._enable_closing_handshake: + raise BadOperationException( + 'Closing handshake is not supported in Hixie 75 protocol') + + self._request.server_terminated = True + + # 5.3 the server may decide to terminate the WebSocket connection by + # running through the following steps: + # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the + # start of the closing handshake. + self._write('\xff\x00') + + def close_connection(self, unused_code='', unused_reason=''): + """Closes a WebSocket connection. + + Raises: + ConnectionTerminatedException: when closing handshake was + not successfull. + """ + + if self._request.server_terminated: + self._logger.debug( + 'Requested close_connection but server is already terminated') + return + + if not self._enable_closing_handshake: + self._request.server_terminated = True + self._logger.debug('Connection closed') + return + + self._send_closing_handshake() + self._logger.debug('Sent server-initiated closing handshake') + + # TODO(ukai): 2. wait until the /client terminated/ flag has been set, + # or until a server-defined timeout expires. + # + # For now, we expect receiving closing handshake right after sending + # out closing handshake, and if we couldn't receive non-handshake + # frame, we take it as ConnectionTerminatedException. + message = self.receive_message() + if message is not None: + raise ConnectionTerminatedException( + 'Didn\'t receive valid ack for closing handshake') + # TODO: 3. close the WebSocket connection. + # note: mod_python Connection (mp_conn) doesn't have close method. + + def send_ping(self, body): + raise BadOperationException( + 'StreamHixie75 doesn\'t support send_ping') + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/_stream_hybi.py b/pyload/lib/mod_pywebsocket/_stream_hybi.py new file mode 100644 index 000000000..bd158fa6b --- /dev/null +++ b/pyload/lib/mod_pywebsocket/_stream_hybi.py @@ -0,0 +1,915 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file provides classes and helper functions for parsing/building frames +of the WebSocket protocol (RFC 6455). + +Specification: +http://tools.ietf.org/html/rfc6455 +""" + + +from collections import deque +import logging +import os +import struct +import time + +from mod_pywebsocket import common +from mod_pywebsocket import util +from mod_pywebsocket._stream_base import BadOperationException +from mod_pywebsocket._stream_base import ConnectionTerminatedException +from mod_pywebsocket._stream_base import InvalidFrameException +from mod_pywebsocket._stream_base import InvalidUTF8Exception +from mod_pywebsocket._stream_base import StreamBase +from mod_pywebsocket._stream_base import UnsupportedFrameException + + +_NOOP_MASKER = util.NoopMasker() + + +class Frame(object): + + def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0, + opcode=None, payload=''): + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.payload = payload + + +# Helper functions made public to be used for writing unittests for WebSocket +# clients. + + +def create_length_header(length, mask): + """Creates a length header. + + Args: + length: Frame length. Must be less than 2^63. + mask: Mask bit. Must be boolean. + + Raises: + ValueError: when bad data is given. + """ + + if mask: + mask_bit = 1 << 7 + else: + mask_bit = 0 + + if length < 0: + raise ValueError('length must be non negative integer') + elif length <= 125: + return chr(mask_bit | length) + elif length < (1 << 16): + return chr(mask_bit | 126) + struct.pack('!H', length) + elif length < (1 << 63): + return chr(mask_bit | 127) + struct.pack('!Q', length) + else: + raise ValueError('Payload is too big for one frame') + + +def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask): + """Creates a frame header. + + Raises: + Exception: when bad data is given. + """ + + if opcode < 0 or 0xf < opcode: + raise ValueError('Opcode out of range') + + if payload_length < 0 or (1 << 63) <= payload_length: + raise ValueError('payload_length out of range') + + if (fin | rsv1 | rsv2 | rsv3) & ~1: + raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1') + + header = '' + + first_byte = ((fin << 7) + | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) + | opcode) + header += chr(first_byte) + header += create_length_header(payload_length, mask) + + return header + + +def _build_frame(header, body, mask): + if not mask: + return header + body + + masking_nonce = os.urandom(4) + masker = util.RepeatedXorMasker(masking_nonce) + + return header + masking_nonce + masker.mask(body) + + +def _filter_and_format_frame_object(frame, mask, frame_filters): + for frame_filter in frame_filters: + frame_filter.filter(frame) + + header = create_header( + frame.opcode, len(frame.payload), frame.fin, + frame.rsv1, frame.rsv2, frame.rsv3, mask) + return _build_frame(header, frame.payload, mask) + + +def create_binary_frame( + message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]): + """Creates a simple binary frame with no extension, reserved bit.""" + + frame = Frame(fin=fin, opcode=opcode, payload=message) + return _filter_and_format_frame_object(frame, mask, frame_filters) + + +def create_text_frame( + message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]): + """Creates a simple text frame with no extension, reserved bit.""" + + encoded_message = message.encode('utf-8') + return create_binary_frame(encoded_message, opcode, fin, mask, + frame_filters) + + +def parse_frame(receive_bytes, logger=None, + ws_version=common.VERSION_HYBI_LATEST, + unmask_receive=True): + """Parses a frame. Returns a tuple containing each header field and + payload. + + Args: + receive_bytes: a function that reads frame data from a stream or + something similar. The function takes length of the bytes to be + read. The function must raise ConnectionTerminatedException if + there is not enough data to be read. + logger: a logging object. + ws_version: the version of WebSocket protocol. + unmask_receive: unmask received frames. When received unmasked + frame, raises InvalidFrameException. + + Raises: + ConnectionTerminatedException: when receive_bytes raises it. + InvalidFrameException: when the frame contains invalid data. + """ + + if not logger: + logger = logging.getLogger() + + logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame') + + received = receive_bytes(2) + + first_byte = ord(received[0]) + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xf + + second_byte = ord(received[1]) + mask = (second_byte >> 7) & 1 + payload_length = second_byte & 0x7f + + logger.log(common.LOGLEVEL_FINE, + 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, ' + 'Mask=%s, Payload_length=%s', + fin, rsv1, rsv2, rsv3, opcode, mask, payload_length) + + if (mask == 1) != unmask_receive: + raise InvalidFrameException( + 'Mask bit on the received frame did\'nt match masking ' + 'configuration for received frames') + + # The HyBi and later specs disallow putting a value in 0x0-0xFFFF + # into the 8-octet extended payload length field (or 0x0-0xFD in + # 2-octet field). + valid_length_encoding = True + length_encoding_bytes = 1 + if payload_length == 127: + logger.log(common.LOGLEVEL_FINE, + 'Receive 8-octet extended payload length') + + extended_payload_length = receive_bytes(8) + payload_length = struct.unpack( + '!Q', extended_payload_length)[0] + if payload_length > 0x7FFFFFFFFFFFFFFF: + raise InvalidFrameException( + 'Extended payload length >= 2^63') + if ws_version >= 13 and payload_length < 0x10000: + valid_length_encoding = False + length_encoding_bytes = 8 + + logger.log(common.LOGLEVEL_FINE, + 'Decoded_payload_length=%s', payload_length) + elif payload_length == 126: + logger.log(common.LOGLEVEL_FINE, + 'Receive 2-octet extended payload length') + + extended_payload_length = receive_bytes(2) + payload_length = struct.unpack( + '!H', extended_payload_length)[0] + if ws_version >= 13 and payload_length < 126: + valid_length_encoding = False + length_encoding_bytes = 2 + + logger.log(common.LOGLEVEL_FINE, + 'Decoded_payload_length=%s', payload_length) + + if not valid_length_encoding: + logger.warning( + 'Payload length is not encoded using the minimal number of ' + 'bytes (%d is encoded using %d bytes)', + payload_length, + length_encoding_bytes) + + if mask == 1: + logger.log(common.LOGLEVEL_FINE, 'Receive mask') + + masking_nonce = receive_bytes(4) + masker = util.RepeatedXorMasker(masking_nonce) + + logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce) + else: + masker = _NOOP_MASKER + + logger.log(common.LOGLEVEL_FINE, 'Receive payload data') + if logger.isEnabledFor(common.LOGLEVEL_FINE): + receive_start = time.time() + + raw_payload_bytes = receive_bytes(payload_length) + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + logger.log( + common.LOGLEVEL_FINE, + 'Done receiving payload data at %s MB/s', + payload_length / (time.time() - receive_start) / 1000 / 1000) + logger.log(common.LOGLEVEL_FINE, 'Unmask payload data') + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + unmask_start = time.time() + + bytes = masker.mask(raw_payload_bytes) + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + logger.log( + common.LOGLEVEL_FINE, + 'Done unmasking payload data at %s MB/s', + payload_length / (time.time() - unmask_start) / 1000 / 1000) + + return opcode, bytes, fin, rsv1, rsv2, rsv3 + + +class FragmentedFrameBuilder(object): + """A stateful class to send a message as fragments.""" + + def __init__(self, mask, frame_filters=[], encode_utf8=True): + """Constructs an instance.""" + + self._mask = mask + self._frame_filters = frame_filters + # This is for skipping UTF-8 encoding when building text type frames + # from compressed data. + self._encode_utf8 = encode_utf8 + + self._started = False + + # Hold opcode of the first frame in messages to verify types of other + # frames in the message are all the same. + self._opcode = common.OPCODE_TEXT + + def build(self, payload_data, end, binary): + if binary: + frame_type = common.OPCODE_BINARY + else: + frame_type = common.OPCODE_TEXT + if self._started: + if self._opcode != frame_type: + raise ValueError('Message types are different in frames for ' + 'the same message') + opcode = common.OPCODE_CONTINUATION + else: + opcode = frame_type + self._opcode = frame_type + + if end: + self._started = False + fin = 1 + else: + self._started = True + fin = 0 + + if binary or not self._encode_utf8: + return create_binary_frame( + payload_data, opcode, fin, self._mask, self._frame_filters) + else: + return create_text_frame( + payload_data, opcode, fin, self._mask, self._frame_filters) + + +def _create_control_frame(opcode, body, mask, frame_filters): + frame = Frame(opcode=opcode, payload=body) + + for frame_filter in frame_filters: + frame_filter.filter(frame) + + if len(frame.payload) > 125: + raise BadOperationException( + 'Payload data size of control frames must be 125 bytes or less') + + header = create_header( + frame.opcode, len(frame.payload), frame.fin, + frame.rsv1, frame.rsv2, frame.rsv3, mask) + return _build_frame(header, frame.payload, mask) + + +def create_ping_frame(body, mask=False, frame_filters=[]): + return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters) + + +def create_pong_frame(body, mask=False, frame_filters=[]): + return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters) + + +def create_close_frame(body, mask=False, frame_filters=[]): + return _create_control_frame( + common.OPCODE_CLOSE, body, mask, frame_filters) + + +def create_closing_handshake_body(code, reason): + body = '' + if code is not None: + if (code > common.STATUS_USER_PRIVATE_MAX or + code < common.STATUS_NORMAL_CLOSURE): + raise BadOperationException('Status code is out of range') + if (code == common.STATUS_NO_STATUS_RECEIVED or + code == common.STATUS_ABNORMAL_CLOSURE or + code == common.STATUS_TLS_HANDSHAKE): + raise BadOperationException('Status code is reserved pseudo ' + 'code') + encoded_reason = reason.encode('utf-8') + body = struct.pack('!H', code) + encoded_reason + return body + + +class StreamOptions(object): + """Holds option values to configure Stream objects.""" + + def __init__(self): + """Constructs StreamOptions.""" + + # Enables deflate-stream extension. + self.deflate_stream = False + + # Filters applied to frames. + self.outgoing_frame_filters = [] + self.incoming_frame_filters = [] + + # Filters applied to messages. Control frames are not affected by them. + self.outgoing_message_filters = [] + self.incoming_message_filters = [] + + self.encode_text_message_to_utf8 = True + self.mask_send = False + self.unmask_receive = True + # RFC6455 disallows fragmented control frames, but mux extension + # relaxes the restriction. + self.allow_fragmented_control_frame = False + + +class Stream(StreamBase): + """A class for parsing/building frames of the WebSocket protocol + (RFC 6455). + """ + + def __init__(self, request, options): + """Constructs an instance. + + Args: + request: mod_python request. + """ + + StreamBase.__init__(self, request) + + self._logger = util.get_class_logger(self) + + self._options = options + + if self._options.deflate_stream: + self._logger.debug('Setup filter for deflate-stream') + self._request = util.DeflateRequest(self._request) + + self._request.client_terminated = False + self._request.server_terminated = False + + # Holds body of received fragments. + self._received_fragments = [] + # Holds the opcode of the first fragment. + self._original_opcode = None + + self._writer = FragmentedFrameBuilder( + self._options.mask_send, self._options.outgoing_frame_filters, + self._options.encode_text_message_to_utf8) + + self._ping_queue = deque() + + def _receive_frame(self): + """Receives a frame and return data in the frame as a tuple containing + each header field and payload separately. + + Raises: + ConnectionTerminatedException: when read returns empty + string. + InvalidFrameException: when the frame contains invalid data. + """ + + def _receive_bytes(length): + return self.receive_bytes(length) + + return parse_frame(receive_bytes=_receive_bytes, + logger=self._logger, + ws_version=self._request.ws_version, + unmask_receive=self._options.unmask_receive) + + def _receive_frame_as_frame_object(self): + opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame() + + return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3, + opcode=opcode, payload=bytes) + + def receive_filtered_frame(self): + """Receives a frame and applies frame filters and message filters. + The frame to be received must satisfy following conditions: + - The frame is not fragmented. + - The opcode of the frame is TEXT or BINARY. + + DO NOT USE this method except for testing purpose. + """ + + frame = self._receive_frame_as_frame_object() + if not frame.fin: + raise InvalidFrameException( + 'Segmented frames must not be received via ' + 'receive_filtered_frame()') + if (frame.opcode != common.OPCODE_TEXT and + frame.opcode != common.OPCODE_BINARY): + raise InvalidFrameException( + 'Control frames must not be received via ' + 'receive_filtered_frame()') + + for frame_filter in self._options.incoming_frame_filters: + frame_filter.filter(frame) + for message_filter in self._options.incoming_message_filters: + frame.payload = message_filter.filter(frame.payload) + return frame + + def send_message(self, message, end=True, binary=False): + """Send message. + + Args: + message: text in unicode or binary in str to send. + binary: send message as binary frame. + + Raises: + BadOperationException: when called on a server-terminated + connection or called with inconsistent message type or + binary parameter. + """ + + if self._request.server_terminated: + raise BadOperationException( + 'Requested send_message after sending out a closing handshake') + + if binary and isinstance(message, unicode): + raise BadOperationException( + 'Message for binary frame must be instance of str') + + for message_filter in self._options.outgoing_message_filters: + message = message_filter.filter(message, end, binary) + + try: + # Set this to any positive integer to limit maximum size of data in + # payload data of each frame. + MAX_PAYLOAD_DATA_SIZE = -1 + + if MAX_PAYLOAD_DATA_SIZE <= 0: + self._write(self._writer.build(message, end, binary)) + return + + bytes_written = 0 + while True: + end_for_this_frame = end + bytes_to_write = len(message) - bytes_written + if (MAX_PAYLOAD_DATA_SIZE > 0 and + bytes_to_write > MAX_PAYLOAD_DATA_SIZE): + end_for_this_frame = False + bytes_to_write = MAX_PAYLOAD_DATA_SIZE + + frame = self._writer.build( + message[bytes_written:bytes_written + bytes_to_write], + end_for_this_frame, + binary) + self._write(frame) + + bytes_written += bytes_to_write + + # This if must be placed here (the end of while block) so that + # at least one frame is sent. + if len(message) <= bytes_written: + break + except ValueError, e: + raise BadOperationException(e) + + def _get_message_from_frame(self, frame): + """Gets a message from frame. If the message is composed of fragmented + frames and the frame is not the last fragmented frame, this method + returns None. The whole message will be returned when the last + fragmented frame is passed to this method. + + Raises: + InvalidFrameException: when the frame doesn't match defragmentation + context, or the frame contains invalid data. + """ + + if frame.opcode == common.OPCODE_CONTINUATION: + if not self._received_fragments: + if frame.fin: + raise InvalidFrameException( + 'Received a termination frame but fragmentation ' + 'not started') + else: + raise InvalidFrameException( + 'Received an intermediate frame but ' + 'fragmentation not started') + + if frame.fin: + # End of fragmentation frame + self._received_fragments.append(frame.payload) + message = ''.join(self._received_fragments) + self._received_fragments = [] + return message + else: + # Intermediate frame + self._received_fragments.append(frame.payload) + return None + else: + if self._received_fragments: + if frame.fin: + raise InvalidFrameException( + 'Received an unfragmented frame without ' + 'terminating existing fragmentation') + else: + raise InvalidFrameException( + 'New fragmentation started without terminating ' + 'existing fragmentation') + + if frame.fin: + # Unfragmented frame + + self._original_opcode = frame.opcode + return frame.payload + else: + # Start of fragmentation frame + + if (not self._options.allow_fragmented_control_frame and + common.is_control_opcode(frame.opcode)): + raise InvalidFrameException( + 'Control frames must not be fragmented') + + self._original_opcode = frame.opcode + self._received_fragments.append(frame.payload) + return None + + def _process_close_message(self, message): + """Processes close message. + + Args: + message: close message. + + Raises: + InvalidFrameException: when the message is invalid. + """ + + self._request.client_terminated = True + + # Status code is optional. We can have status reason only if we + # have status code. Status reason can be empty string. So, + # allowed cases are + # - no application data: no code no reason + # - 2 octet of application data: has code but no reason + # - 3 or more octet of application data: both code and reason + if len(message) == 0: + self._logger.debug('Received close frame (empty body)') + self._request.ws_close_code = ( + common.STATUS_NO_STATUS_RECEIVED) + elif len(message) == 1: + raise InvalidFrameException( + 'If a close frame has status code, the length of ' + 'status code must be 2 octet') + elif len(message) >= 2: + self._request.ws_close_code = struct.unpack( + '!H', message[0:2])[0] + self._request.ws_close_reason = message[2:].decode( + 'utf-8', 'replace') + self._logger.debug( + 'Received close frame (code=%d, reason=%r)', + self._request.ws_close_code, + self._request.ws_close_reason) + + # Drain junk data after the close frame if necessary. + self._drain_received_data() + + if self._request.server_terminated: + self._logger.debug( + 'Received ack for server-initiated closing handshake') + return + + self._logger.debug( + 'Received client-initiated closing handshake') + + code = common.STATUS_NORMAL_CLOSURE + reason = '' + if hasattr(self._request, '_dispatcher'): + dispatcher = self._request._dispatcher + code, reason = dispatcher.passive_closing_handshake( + self._request) + if code is None and reason is not None and len(reason) > 0: + self._logger.warning( + 'Handler specified reason despite code being None') + reason = '' + if reason is None: + reason = '' + self._send_closing_handshake(code, reason) + self._logger.debug( + 'Sent ack for client-initiated closing handshake ' + '(code=%r, reason=%r)', code, reason) + + def _process_ping_message(self, message): + """Processes ping message. + + Args: + message: ping message. + """ + + try: + handler = self._request.on_ping_handler + if handler: + handler(self._request, message) + return + except AttributeError, e: + pass + self._send_pong(message) + + def _process_pong_message(self, message): + """Processes pong message. + + Args: + message: pong message. + """ + + # TODO(tyoshino): Add ping timeout handling. + + inflight_pings = deque() + + while True: + try: + expected_body = self._ping_queue.popleft() + if expected_body == message: + # inflight_pings contains pings ignored by the + # other peer. Just forget them. + self._logger.debug( + 'Ping %r is acked (%d pings were ignored)', + expected_body, len(inflight_pings)) + break + else: + inflight_pings.append(expected_body) + except IndexError, e: + # The received pong was unsolicited pong. Keep the + # ping queue as is. + self._ping_queue = inflight_pings + self._logger.debug('Received a unsolicited pong') + break + + try: + handler = self._request.on_pong_handler + if handler: + handler(self._request, message) + except AttributeError, e: + pass + + def receive_message(self): + """Receive a WebSocket frame and return its payload as a text in + unicode or a binary in str. + + Returns: + payload data of the frame + - as unicode instance if received text frame + - as str instance if received binary frame + or None iff received closing handshake. + Raises: + BadOperationException: when called on a client-terminated + connection. + ConnectionTerminatedException: when read returns empty + string. + InvalidFrameException: when the frame contains invalid + data. + UnsupportedFrameException: when the received frame has + flags, opcode we cannot handle. You can ignore this + exception and continue receiving the next frame. + """ + + if self._request.client_terminated: + raise BadOperationException( + 'Requested receive_message after receiving a closing ' + 'handshake') + + while True: + # mp_conn.read will block if no bytes are available. + # Timeout is controlled by TimeOut directive of Apache. + + frame = self._receive_frame_as_frame_object() + + # Check the constraint on the payload size for control frames + # before extension processes the frame. + # See also http://tools.ietf.org/html/rfc6455#section-5.5 + if (common.is_control_opcode(frame.opcode) and + len(frame.payload) > 125): + raise InvalidFrameException( + 'Payload data size of control frames must be 125 bytes or ' + 'less') + + for frame_filter in self._options.incoming_frame_filters: + frame_filter.filter(frame) + + if frame.rsv1 or frame.rsv2 or frame.rsv3: + raise UnsupportedFrameException( + 'Unsupported flag is set (rsv = %d%d%d)' % + (frame.rsv1, frame.rsv2, frame.rsv3)) + + message = self._get_message_from_frame(frame) + if message is None: + continue + + for message_filter in self._options.incoming_message_filters: + message = message_filter.filter(message) + + if self._original_opcode == common.OPCODE_TEXT: + # The WebSocket protocol section 4.4 specifies that invalid + # characters must be replaced with U+fffd REPLACEMENT + # CHARACTER. + try: + return message.decode('utf-8') + except UnicodeDecodeError, e: + raise InvalidUTF8Exception(e) + elif self._original_opcode == common.OPCODE_BINARY: + return message + elif self._original_opcode == common.OPCODE_CLOSE: + self._process_close_message(message) + return None + elif self._original_opcode == common.OPCODE_PING: + self._process_ping_message(message) + elif self._original_opcode == common.OPCODE_PONG: + self._process_pong_message(message) + else: + raise UnsupportedFrameException( + 'Opcode %d is not supported' % self._original_opcode) + + def _send_closing_handshake(self, code, reason): + body = create_closing_handshake_body(code, reason) + frame = create_close_frame( + body, mask=self._options.mask_send, + frame_filters=self._options.outgoing_frame_filters) + + self._request.server_terminated = True + + self._write(frame) + + def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''): + """Closes a WebSocket connection. + + Args: + code: Status code for close frame. If code is None, a close + frame with empty body will be sent. + reason: string representing close reason. + Raises: + BadOperationException: when reason is specified with code None + or reason is not an instance of both str and unicode. + """ + + if self._request.server_terminated: + self._logger.debug( + 'Requested close_connection but server is already terminated') + return + + if code is None: + if reason is not None and len(reason) > 0: + raise BadOperationException( + 'close reason must not be specified if code is None') + reason = '' + else: + if not isinstance(reason, str) and not isinstance(reason, unicode): + raise BadOperationException( + 'close reason must be an instance of str or unicode') + + self._send_closing_handshake(code, reason) + self._logger.debug( + 'Sent server-initiated closing handshake (code=%r, reason=%r)', + code, reason) + + if (code == common.STATUS_GOING_AWAY or + code == common.STATUS_PROTOCOL_ERROR): + # It doesn't make sense to wait for a close frame if the reason is + # protocol error or that the server is going away. For some of + # other reasons, it might not make sense to wait for a close frame, + # but it's not clear, yet. + return + + # TODO(ukai): 2. wait until the /client terminated/ flag has been set, + # or until a server-defined timeout expires. + # + # For now, we expect receiving closing handshake right after sending + # out closing handshake. + message = self.receive_message() + if message is not None: + raise ConnectionTerminatedException( + 'Didn\'t receive valid ack for closing handshake') + # TODO: 3. close the WebSocket connection. + # note: mod_python Connection (mp_conn) doesn't have close method. + + def send_ping(self, body=''): + frame = create_ping_frame( + body, + self._options.mask_send, + self._options.outgoing_frame_filters) + self._write(frame) + + self._ping_queue.append(body) + + def _send_pong(self, body): + frame = create_pong_frame( + body, + self._options.mask_send, + self._options.outgoing_frame_filters) + self._write(frame) + + def get_last_received_opcode(self): + """Returns the opcode of the WebSocket message which the last received + frame belongs to. The return value is valid iff immediately after + receive_message call. + """ + + return self._original_opcode + + def _drain_received_data(self): + """Drains unread data in the receive buffer to avoid sending out TCP + RST packet. This is because when deflate-stream is enabled, some + DEFLATE block for flushing data may follow a close frame. If any data + remains in the receive buffer of a socket when the socket is closed, + it sends out TCP RST packet to the other peer. + + Since mod_python's mp_conn object doesn't support non-blocking read, + we perform this only when pywebsocket is running in standalone mode. + """ + + # If self._options.deflate_stream is true, self._request is + # DeflateRequest, so we can get wrapped request object by + # self._request._request. + # + # Only _StandaloneRequest has _drain_received_data method. + if (self._options.deflate_stream and + ('_drain_received_data' in dir(self._request._request))): + self._request._request._drain_received_data() + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/common.py b/pyload/lib/mod_pywebsocket/common.py new file mode 100644 index 000000000..2388379c0 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/common.py @@ -0,0 +1,307 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file must not depend on any module specific to the WebSocket protocol. +""" + + +from mod_pywebsocket import http_header_util + + +# Additional log level definitions. +LOGLEVEL_FINE = 9 + +# Constants indicating WebSocket protocol version. +VERSION_HIXIE75 = -1 +VERSION_HYBI00 = 0 +VERSION_HYBI01 = 1 +VERSION_HYBI02 = 2 +VERSION_HYBI03 = 2 +VERSION_HYBI04 = 4 +VERSION_HYBI05 = 5 +VERSION_HYBI06 = 6 +VERSION_HYBI07 = 7 +VERSION_HYBI08 = 8 +VERSION_HYBI09 = 8 +VERSION_HYBI10 = 8 +VERSION_HYBI11 = 8 +VERSION_HYBI12 = 8 +VERSION_HYBI13 = 13 +VERSION_HYBI14 = 13 +VERSION_HYBI15 = 13 +VERSION_HYBI16 = 13 +VERSION_HYBI17 = 13 + +# Constants indicating WebSocket protocol latest version. +VERSION_HYBI_LATEST = VERSION_HYBI13 + +# Port numbers +DEFAULT_WEB_SOCKET_PORT = 80 +DEFAULT_WEB_SOCKET_SECURE_PORT = 443 + +# Schemes +WEB_SOCKET_SCHEME = 'ws' +WEB_SOCKET_SECURE_SCHEME = 'wss' + +# Frame opcodes defined in the spec. +OPCODE_CONTINUATION = 0x0 +OPCODE_TEXT = 0x1 +OPCODE_BINARY = 0x2 +OPCODE_CLOSE = 0x8 +OPCODE_PING = 0x9 +OPCODE_PONG = 0xa + +# UUIDs used by HyBi 04 and later opening handshake and frame masking. +WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + +# Opening handshake header names and expected values. +UPGRADE_HEADER = 'Upgrade' +WEBSOCKET_UPGRADE_TYPE = 'websocket' +WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket' +CONNECTION_HEADER = 'Connection' +UPGRADE_CONNECTION_TYPE = 'Upgrade' +HOST_HEADER = 'Host' +ORIGIN_HEADER = 'Origin' +SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin' +SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key' +SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept' +SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version' +SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol' +SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions' +SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft' +SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1' +SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2' +SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location' + +# Extensions +DEFLATE_STREAM_EXTENSION = 'deflate-stream' +DEFLATE_FRAME_EXTENSION = 'deflate-frame' +PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress' +PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress' +X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame' +X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress' +MUX_EXTENSION = 'mux_DO_NOT_USE' + +# Status codes +# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and +# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases. +# Could not be used for codes in actual closing frames. +# Application level errors must use codes in the range +# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the +# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed +# by IANA. Usually application must define user protocol level errors in the +# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX. +STATUS_NORMAL_CLOSURE = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA = 1003 +STATUS_NO_STATUS_RECEIVED = 1005 +STATUS_ABNORMAL_CLOSURE = 1006 +STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_MANDATORY_EXTENSION = 1010 +STATUS_INTERNAL_ENDPOINT_ERROR = 1011 +STATUS_TLS_HANDSHAKE = 1015 +STATUS_USER_REGISTERED_BASE = 3000 +STATUS_USER_REGISTERED_MAX = 3999 +STATUS_USER_PRIVATE_BASE = 4000 +STATUS_USER_PRIVATE_MAX = 4999 +# Following definitions are aliases to keep compatibility. Applications must +# not use these obsoleted definitions anymore. +STATUS_NORMAL = STATUS_NORMAL_CLOSURE +STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA +STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED +STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE +STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA +STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION + +# HTTP status codes +HTTP_STATUS_BAD_REQUEST = 400 +HTTP_STATUS_FORBIDDEN = 403 +HTTP_STATUS_NOT_FOUND = 404 + + +def is_control_opcode(opcode): + return (opcode >> 3) == 1 + + +class ExtensionParameter(object): + """Holds information about an extension which is exchanged on extension + negotiation in opening handshake. + """ + + def __init__(self, name): + self._name = name + # TODO(tyoshino): Change the data structure to more efficient one such + # as dict when the spec changes to say like + # - Parameter names must be unique + # - The order of parameters is not significant + self._parameters = [] + + def name(self): + return self._name + + def add_parameter(self, name, value): + self._parameters.append((name, value)) + + def get_parameters(self): + return self._parameters + + def get_parameter_names(self): + return [name for name, unused_value in self._parameters] + + def has_parameter(self, name): + for param_name, param_value in self._parameters: + if param_name == name: + return True + return False + + def get_parameter_value(self, name): + for param_name, param_value in self._parameters: + if param_name == name: + return param_value + + +class ExtensionParsingException(Exception): + def __init__(self, name): + super(ExtensionParsingException, self).__init__(name) + + +def _parse_extension_param(state, definition, allow_quoted_string): + param_name = http_header_util.consume_token(state) + + if param_name is None: + raise ExtensionParsingException('No valid parameter name found') + + http_header_util.consume_lwses(state) + + if not http_header_util.consume_string(state, '='): + definition.add_parameter(param_name, None) + return + + http_header_util.consume_lwses(state) + + if allow_quoted_string: + # TODO(toyoshim): Add code to validate that parsed param_value is token + param_value = http_header_util.consume_token_or_quoted_string(state) + else: + param_value = http_header_util.consume_token(state) + if param_value is None: + raise ExtensionParsingException( + 'No valid parameter value found on the right-hand side of ' + 'parameter %r' % param_name) + + definition.add_parameter(param_name, param_value) + + +def _parse_extension(state, allow_quoted_string): + extension_token = http_header_util.consume_token(state) + if extension_token is None: + return None + + extension = ExtensionParameter(extension_token) + + while True: + http_header_util.consume_lwses(state) + + if not http_header_util.consume_string(state, ';'): + break + + http_header_util.consume_lwses(state) + + try: + _parse_extension_param(state, extension, allow_quoted_string) + except ExtensionParsingException, e: + raise ExtensionParsingException( + 'Failed to parse parameter for %r (%r)' % + (extension_token, e)) + + return extension + + +def parse_extensions(data, allow_quoted_string=False): + """Parses Sec-WebSocket-Extensions header value returns a list of + ExtensionParameter objects. + + Leading LWSes must be trimmed. + """ + + state = http_header_util.ParsingState(data) + + extension_list = [] + while True: + extension = _parse_extension(state, allow_quoted_string) + if extension is not None: + extension_list.append(extension) + + http_header_util.consume_lwses(state) + + if http_header_util.peek(state) is None: + break + + if not http_header_util.consume_string(state, ','): + raise ExtensionParsingException( + 'Failed to parse Sec-WebSocket-Extensions header: ' + 'Expected a comma but found %r' % + http_header_util.peek(state)) + + http_header_util.consume_lwses(state) + + if len(extension_list) == 0: + raise ExtensionParsingException( + 'No valid extension entry found') + + return extension_list + + +def format_extension(extension): + """Formats an ExtensionParameter object.""" + + formatted_params = [extension.name()] + for param_name, param_value in extension.get_parameters(): + if param_value is None: + formatted_params.append(param_name) + else: + quoted_value = http_header_util.quote_if_necessary(param_value) + formatted_params.append('%s=%s' % (param_name, quoted_value)) + return '; '.join(formatted_params) + + +def format_extensions(extension_list): + """Formats a list of ExtensionParameter objects.""" + + formatted_extension_list = [] + for extension in extension_list: + formatted_extension_list.append(format_extension(extension)) + return ', '.join(formatted_extension_list) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/dispatch.py b/pyload/lib/mod_pywebsocket/dispatch.py new file mode 100644 index 000000000..25905f180 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/dispatch.py @@ -0,0 +1,387 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Dispatch WebSocket request. +""" + + +import logging +import os +import re + +from mod_pywebsocket import common +from mod_pywebsocket import handshake +from mod_pywebsocket import msgutil +from mod_pywebsocket import mux +from mod_pywebsocket import stream +from mod_pywebsocket import util + + +_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') +_SOURCE_SUFFIX = '_wsh.py' +_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' +_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' +_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = ( + 'web_socket_passive_closing_handshake') + + +class DispatchException(Exception): + """Exception in dispatching WebSocket request.""" + + def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND): + super(DispatchException, self).__init__(name) + self.status = status + + +def _default_passive_closing_handshake_handler(request): + """Default web_socket_passive_closing_handshake handler.""" + + return common.STATUS_NORMAL_CLOSURE, '' + + +def _normalize_path(path): + """Normalize path. + + Args: + path: the path to normalize. + + Path is converted to the absolute path. + The input path can use either '\\' or '/' as the separator. + The normalized path always uses '/' regardless of the platform. + """ + + path = path.replace('\\', os.path.sep) + path = os.path.realpath(path) + path = path.replace('\\', '/') + return path + + +def _create_path_to_resource_converter(base_dir): + """Returns a function that converts the path of a WebSocket handler source + file to a resource string by removing the path to the base directory from + its head, removing _SOURCE_SUFFIX from its tail, and replacing path + separators in it with '/'. + + Args: + base_dir: the path to the base directory. + """ + + base_dir = _normalize_path(base_dir) + + base_len = len(base_dir) + suffix_len = len(_SOURCE_SUFFIX) + + def converter(path): + if not path.endswith(_SOURCE_SUFFIX): + return None + # _normalize_path must not be used because resolving symlink breaks + # following path check. + path = path.replace('\\', '/') + if not path.startswith(base_dir): + return None + return path[base_len:-suffix_len] + + return converter + + +def _enumerate_handler_file_paths(directory): + """Returns a generator that enumerates WebSocket Handler source file names + in the given directory. + """ + + for root, unused_dirs, files in os.walk(directory): + for base in files: + path = os.path.join(root, base) + if _SOURCE_PATH_PATTERN.search(path): + yield path + + +class _HandlerSuite(object): + """A handler suite holder class.""" + + def __init__(self, do_extra_handshake, transfer_data, + passive_closing_handshake): + self.do_extra_handshake = do_extra_handshake + self.transfer_data = transfer_data + self.passive_closing_handshake = passive_closing_handshake + + +def _source_handler_file(handler_definition): + """Source a handler definition string. + + Args: + handler_definition: a string containing Python statements that define + handler functions. + """ + + global_dic = {} + try: + exec handler_definition in global_dic + except Exception: + raise DispatchException('Error in sourcing handler:' + + util.get_stack_trace()) + passive_closing_handshake_handler = None + try: + passive_closing_handshake_handler = _extract_handler( + global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME) + except Exception: + passive_closing_handshake_handler = ( + _default_passive_closing_handshake_handler) + return _HandlerSuite( + _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), + _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME), + passive_closing_handshake_handler) + + +def _extract_handler(dic, name): + """Extracts a callable with the specified name from the given dictionary + dic. + """ + + if name not in dic: + raise DispatchException('%s is not defined.' % name) + handler = dic[name] + if not callable(handler): + raise DispatchException('%s is not callable.' % name) + return handler + + +class Dispatcher(object): + """Dispatches WebSocket requests. + + This class maintains a map from resource name to handlers. + """ + + def __init__( + self, root_dir, scan_dir=None, + allow_handlers_outside_root_dir=True): + """Construct an instance. + + Args: + root_dir: The directory where handler definition files are + placed. + scan_dir: The directory where handler definition files are + searched. scan_dir must be a directory under root_dir, + including root_dir itself. If scan_dir is None, + root_dir is used as scan_dir. scan_dir can be useful + in saving scan time when root_dir contains many + subdirectories. + allow_handlers_outside_root_dir: Scans handler files even if their + canonical path is not under root_dir. + """ + + self._logger = util.get_class_logger(self) + + self._handler_suite_map = {} + self._source_warnings = [] + if scan_dir is None: + scan_dir = root_dir + if not os.path.realpath(scan_dir).startswith( + os.path.realpath(root_dir)): + raise DispatchException('scan_dir:%s must be a directory under ' + 'root_dir:%s.' % (scan_dir, root_dir)) + self._source_handler_files_in_dir( + root_dir, scan_dir, allow_handlers_outside_root_dir) + + def add_resource_path_alias(self, + alias_resource_path, existing_resource_path): + """Add resource path alias. + + Once added, request to alias_resource_path would be handled by + handler registered for existing_resource_path. + + Args: + alias_resource_path: alias resource path + existing_resource_path: existing resource path + """ + try: + handler_suite = self._handler_suite_map[existing_resource_path] + self._handler_suite_map[alias_resource_path] = handler_suite + except KeyError: + raise DispatchException('No handler for: %r' % + existing_resource_path) + + def source_warnings(self): + """Return warnings in sourcing handlers.""" + + return self._source_warnings + + def do_extra_handshake(self, request): + """Do extra checking in WebSocket handshake. + + Select a handler based on request.uri and call its + web_socket_do_extra_handshake function. + + Args: + request: mod_python request. + + Raises: + DispatchException: when handler was not found + AbortedByUserException: when user handler abort connection + HandshakeException: when opening handshake failed + """ + + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + raise DispatchException('No handler for: %r' % request.ws_resource) + do_extra_handshake_ = handler_suite.do_extra_handshake + try: + do_extra_handshake_(request) + except handshake.AbortedByUserException, e: + raise + except Exception, e: + util.prepend_message_to_exception( + '%s raised exception for %s: ' % ( + _DO_EXTRA_HANDSHAKE_HANDLER_NAME, + request.ws_resource), + e) + raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN) + + def transfer_data(self, request): + """Let a handler transfer_data with a WebSocket client. + + Select a handler based on request.ws_resource and call its + web_socket_transfer_data function. + + Args: + request: mod_python request. + + Raises: + DispatchException: when handler was not found + AbortedByUserException: when user handler abort connection + """ + + # TODO(tyoshino): Terminate underlying TCP connection if possible. + try: + if mux.use_mux(request): + mux.start(request, self) + else: + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + raise DispatchException('No handler for: %r' % + request.ws_resource) + transfer_data_ = handler_suite.transfer_data + transfer_data_(request) + + if not request.server_terminated: + request.ws_stream.close_connection() + # Catch non-critical exceptions the handler didn't handle. + except handshake.AbortedByUserException, e: + self._logger.debug('%s', e) + raise + except msgutil.BadOperationException, e: + self._logger.debug('%s', e) + request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE) + except msgutil.InvalidFrameException, e: + # InvalidFrameException must be caught before + # ConnectionTerminatedException that catches InvalidFrameException. + self._logger.debug('%s', e) + request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR) + except msgutil.UnsupportedFrameException, e: + self._logger.debug('%s', e) + request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA) + except stream.InvalidUTF8Exception, e: + self._logger.debug('%s', e) + request.ws_stream.close_connection( + common.STATUS_INVALID_FRAME_PAYLOAD_DATA) + except msgutil.ConnectionTerminatedException, e: + self._logger.debug('%s', e) + except Exception, e: + util.prepend_message_to_exception( + '%s raised exception for %s: ' % ( + _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), + e) + raise + + def passive_closing_handshake(self, request): + """Prepare code and reason for responding client initiated closing + handshake. + """ + + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + return _default_passive_closing_handshake_handler(request) + return handler_suite.passive_closing_handshake(request) + + def get_handler_suite(self, resource): + """Retrieves two handlers (one for extra handshake processing, and one + for data transfer) for the given request as a HandlerSuite object. + """ + + fragment = None + if '#' in resource: + resource, fragment = resource.split('#', 1) + if '?' in resource: + resource = resource.split('?', 1)[0] + handler_suite = self._handler_suite_map.get(resource) + if handler_suite and fragment: + raise DispatchException('Fragment identifiers MUST NOT be used on ' + 'WebSocket URIs', + common.HTTP_STATUS_BAD_REQUEST) + return handler_suite + + def _source_handler_files_in_dir( + self, root_dir, scan_dir, allow_handlers_outside_root_dir): + """Source all the handler source files in the scan_dir directory. + + The resource path is determined relative to root_dir. + """ + + # We build a map from resource to handler code assuming that there's + # only one path from root_dir to scan_dir and it can be obtained by + # comparing realpath of them. + + # Here we cannot use abspath. See + # https://bugs.webkit.org/show_bug.cgi?id=31603 + + convert = _create_path_to_resource_converter(root_dir) + scan_realpath = os.path.realpath(scan_dir) + root_realpath = os.path.realpath(root_dir) + for path in _enumerate_handler_file_paths(scan_realpath): + if (not allow_handlers_outside_root_dir and + (not os.path.realpath(path).startswith(root_realpath))): + self._logger.debug( + 'Canonical path of %s is not under root directory' % + path) + continue + try: + handler_suite = _source_handler_file(open(path).read()) + except DispatchException, e: + self._source_warnings.append('%s: %s' % (path, e)) + continue + resource = convert(path) + if resource is None: + self._logger.debug( + 'Path to resource conversion on %s failed' % path) + else: + self._handler_suite_map[convert(path)] = handler_suite + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/extensions.py b/pyload/lib/mod_pywebsocket/extensions.py new file mode 100644 index 000000000..03dbf9ee1 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/extensions.py @@ -0,0 +1,727 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from mod_pywebsocket import common +from mod_pywebsocket import util +from mod_pywebsocket.http_header_util import quote_if_necessary + + +_available_processors = {} + + +class ExtensionProcessorInterface(object): + + def name(self): + return None + + def get_extension_response(self): + return None + + def setup_stream_options(self, stream_options): + pass + + +class DeflateStreamExtensionProcessor(ExtensionProcessorInterface): + """WebSocket DEFLATE stream extension processor. + + Specification: + Section 9.2.1 in + http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 + """ + + def __init__(self, request): + self._logger = util.get_class_logger(self) + + self._request = request + + def name(self): + return common.DEFLATE_STREAM_EXTENSION + + def get_extension_response(self): + if len(self._request.get_parameter_names()) != 0: + return None + + self._logger.debug( + 'Enable %s extension', common.DEFLATE_STREAM_EXTENSION) + + return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION) + + def setup_stream_options(self, stream_options): + stream_options.deflate_stream = True + + +_available_processors[common.DEFLATE_STREAM_EXTENSION] = ( + DeflateStreamExtensionProcessor) + + +def _log_compression_ratio(logger, original_bytes, total_original_bytes, + filtered_bytes, total_filtered_bytes): + # Print inf when ratio is not available. + ratio = float('inf') + average_ratio = float('inf') + if original_bytes != 0: + ratio = float(filtered_bytes) / original_bytes + if total_original_bytes != 0: + average_ratio = ( + float(total_filtered_bytes) / total_original_bytes) + logger.debug('Outgoing compress ratio: %f (average: %f)' % + (ratio, average_ratio)) + + +def _log_decompression_ratio(logger, received_bytes, total_received_bytes, + filtered_bytes, total_filtered_bytes): + # Print inf when ratio is not available. + ratio = float('inf') + average_ratio = float('inf') + if received_bytes != 0: + ratio = float(received_bytes) / filtered_bytes + if total_filtered_bytes != 0: + average_ratio = ( + float(total_received_bytes) / total_filtered_bytes) + logger.debug('Incoming compress ratio: %f (average: %f)' % + (ratio, average_ratio)) + + +class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): + """WebSocket Per-frame DEFLATE extension processor. + + Specification: + http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate + """ + + _WINDOW_BITS_PARAM = 'max_window_bits' + _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' + + def __init__(self, request): + self._logger = util.get_class_logger(self) + + self._request = request + + self._response_window_bits = None + self._response_no_context_takeover = False + self._bfinal = False + + # Counters for statistics. + + # Total number of outgoing bytes supplied to this filter. + self._total_outgoing_payload_bytes = 0 + # Total number of bytes sent to the network after applying this filter. + self._total_filtered_outgoing_payload_bytes = 0 + + # Total number of bytes received from the network. + self._total_incoming_payload_bytes = 0 + # Total number of incoming bytes obtained after applying this filter. + self._total_filtered_incoming_payload_bytes = 0 + + def name(self): + return common.DEFLATE_FRAME_EXTENSION + + def get_extension_response(self): + # Any unknown parameter will be just ignored. + + window_bits = self._request.get_parameter_value( + self._WINDOW_BITS_PARAM) + no_context_takeover = self._request.has_parameter( + self._NO_CONTEXT_TAKEOVER_PARAM) + if (no_context_takeover and + self._request.get_parameter_value( + self._NO_CONTEXT_TAKEOVER_PARAM) is not None): + return None + + if window_bits is not None: + try: + window_bits = int(window_bits) + except ValueError, e: + return None + if window_bits < 8 or window_bits > 15: + return None + + self._deflater = util._RFC1979Deflater( + window_bits, no_context_takeover) + + self._inflater = util._RFC1979Inflater() + + self._compress_outgoing = True + + response = common.ExtensionParameter(self._request.name()) + + if self._response_window_bits is not None: + response.add_parameter( + self._WINDOW_BITS_PARAM, str(self._response_window_bits)) + if self._response_no_context_takeover: + response.add_parameter( + self._NO_CONTEXT_TAKEOVER_PARAM, None) + + self._logger.debug( + 'Enable %s extension (' + 'request: window_bits=%s; no_context_takeover=%r, ' + 'response: window_wbits=%s; no_context_takeover=%r)' % + (self._request.name(), + window_bits, + no_context_takeover, + self._response_window_bits, + self._response_no_context_takeover)) + + return response + + def setup_stream_options(self, stream_options): + + class _OutgoingFilter(object): + + def __init__(self, parent): + self._parent = parent + + def filter(self, frame): + self._parent._outgoing_filter(frame) + + class _IncomingFilter(object): + + def __init__(self, parent): + self._parent = parent + + def filter(self, frame): + self._parent._incoming_filter(frame) + + stream_options.outgoing_frame_filters.append( + _OutgoingFilter(self)) + stream_options.incoming_frame_filters.insert( + 0, _IncomingFilter(self)) + + def set_response_window_bits(self, value): + self._response_window_bits = value + + def set_response_no_context_takeover(self, value): + self._response_no_context_takeover = value + + def set_bfinal(self, value): + self._bfinal = value + + def enable_outgoing_compression(self): + self._compress_outgoing = True + + def disable_outgoing_compression(self): + self._compress_outgoing = False + + def _outgoing_filter(self, frame): + """Transform outgoing frames. This method is called only by + an _OutgoingFilter instance. + """ + + original_payload_size = len(frame.payload) + self._total_outgoing_payload_bytes += original_payload_size + + if (not self._compress_outgoing or + common.is_control_opcode(frame.opcode)): + self._total_filtered_outgoing_payload_bytes += ( + original_payload_size) + return + + frame.payload = self._deflater.filter( + frame.payload, bfinal=self._bfinal) + frame.rsv1 = 1 + + filtered_payload_size = len(frame.payload) + self._total_filtered_outgoing_payload_bytes += filtered_payload_size + + _log_compression_ratio(self._logger, original_payload_size, + self._total_outgoing_payload_bytes, + filtered_payload_size, + self._total_filtered_outgoing_payload_bytes) + + def _incoming_filter(self, frame): + """Transform incoming frames. This method is called only by + an _IncomingFilter instance. + """ + + received_payload_size = len(frame.payload) + self._total_incoming_payload_bytes += received_payload_size + + if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): + self._total_filtered_incoming_payload_bytes += ( + received_payload_size) + return + + frame.payload = self._inflater.filter(frame.payload) + frame.rsv1 = 0 + + filtered_payload_size = len(frame.payload) + self._total_filtered_incoming_payload_bytes += filtered_payload_size + + _log_decompression_ratio(self._logger, received_payload_size, + self._total_incoming_payload_bytes, + filtered_payload_size, + self._total_filtered_incoming_payload_bytes) + + +_available_processors[common.DEFLATE_FRAME_EXTENSION] = ( + DeflateFrameExtensionProcessor) + + +# Adding vendor-prefixed deflate-frame extension. +# TODO(bashi): Remove this after WebKit stops using vendor prefix. +_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( + DeflateFrameExtensionProcessor) + + +def _parse_compression_method(data): + """Parses the value of "method" extension parameter.""" + + return common.parse_extensions(data, allow_quoted_string=True) + + +def _create_accepted_method_desc(method_name, method_params): + """Creates accepted-method-desc from given method name and parameters""" + + extension = common.ExtensionParameter(method_name) + for name, value in method_params: + extension.add_parameter(name, value) + return common.format_extension(extension) + + +class CompressionExtensionProcessorBase(ExtensionProcessorInterface): + """Base class for Per-frame and Per-message compression extension.""" + + _METHOD_PARAM = 'method' + + def __init__(self, request): + self._logger = util.get_class_logger(self) + self._request = request + self._compression_method_name = None + self._compression_processor = None + self._compression_processor_hook = None + + def name(self): + return '' + + def _lookup_compression_processor(self, method_desc): + return None + + def _get_compression_processor_response(self): + """Looks up the compression processor based on the self._request and + returns the compression processor's response. + """ + + method_list = self._request.get_parameter_value(self._METHOD_PARAM) + if method_list is None: + return None + methods = _parse_compression_method(method_list) + if methods is None: + return None + comression_processor = None + # The current implementation tries only the first method that matches + # supported algorithm. Following methods aren't tried even if the + # first one is rejected. + # TODO(bashi): Need to clarify this behavior. + for method_desc in methods: + compression_processor = self._lookup_compression_processor( + method_desc) + if compression_processor is not None: + self._compression_method_name = method_desc.name() + break + if compression_processor is None: + return None + + if self._compression_processor_hook: + self._compression_processor_hook(compression_processor) + + processor_response = compression_processor.get_extension_response() + if processor_response is None: + return None + self._compression_processor = compression_processor + return processor_response + + def get_extension_response(self): + processor_response = self._get_compression_processor_response() + if processor_response is None: + return None + + response = common.ExtensionParameter(self._request.name()) + accepted_method_desc = _create_accepted_method_desc( + self._compression_method_name, + processor_response.get_parameters()) + response.add_parameter(self._METHOD_PARAM, accepted_method_desc) + self._logger.debug( + 'Enable %s extension (method: %s)' % + (self._request.name(), self._compression_method_name)) + return response + + def setup_stream_options(self, stream_options): + if self._compression_processor is None: + return + self._compression_processor.setup_stream_options(stream_options) + + def set_compression_processor_hook(self, hook): + self._compression_processor_hook = hook + + def get_compression_processor(self): + return self._compression_processor + + +class PerFrameCompressionExtensionProcessor(CompressionExtensionProcessorBase): + """WebSocket Per-frame compression extension processor. + + Specification: + http://tools.ietf.org/html/draft-ietf-hybi-websocket-perframe-compression + """ + + _DEFLATE_METHOD = 'deflate' + + def __init__(self, request): + CompressionExtensionProcessorBase.__init__(self, request) + + def name(self): + return common.PERFRAME_COMPRESSION_EXTENSION + + def _lookup_compression_processor(self, method_desc): + if method_desc.name() == self._DEFLATE_METHOD: + return DeflateFrameExtensionProcessor(method_desc) + return None + + +_available_processors[common.PERFRAME_COMPRESSION_EXTENSION] = ( + PerFrameCompressionExtensionProcessor) + + +class DeflateMessageProcessor(ExtensionProcessorInterface): + """Per-message deflate processor.""" + + _S2C_MAX_WINDOW_BITS_PARAM = 's2c_max_window_bits' + _S2C_NO_CONTEXT_TAKEOVER_PARAM = 's2c_no_context_takeover' + _C2S_MAX_WINDOW_BITS_PARAM = 'c2s_max_window_bits' + _C2S_NO_CONTEXT_TAKEOVER_PARAM = 'c2s_no_context_takeover' + + def __init__(self, request): + self._request = request + self._logger = util.get_class_logger(self) + + self._c2s_max_window_bits = None + self._c2s_no_context_takeover = False + self._bfinal = False + + self._compress_outgoing_enabled = False + + # True if a message is fragmented and compression is ongoing. + self._compress_ongoing = False + + # Counters for statistics. + + # Total number of outgoing bytes supplied to this filter. + self._total_outgoing_payload_bytes = 0 + # Total number of bytes sent to the network after applying this filter. + self._total_filtered_outgoing_payload_bytes = 0 + + # Total number of bytes received from the network. + self._total_incoming_payload_bytes = 0 + # Total number of incoming bytes obtained after applying this filter. + self._total_filtered_incoming_payload_bytes = 0 + + def name(self): + return 'deflate' + + def get_extension_response(self): + # Any unknown parameter will be just ignored. + + s2c_max_window_bits = self._request.get_parameter_value( + self._S2C_MAX_WINDOW_BITS_PARAM) + if s2c_max_window_bits is not None: + try: + s2c_max_window_bits = int(s2c_max_window_bits) + except ValueError, e: + return None + if s2c_max_window_bits < 8 or s2c_max_window_bits > 15: + return None + + s2c_no_context_takeover = self._request.has_parameter( + self._S2C_NO_CONTEXT_TAKEOVER_PARAM) + if (s2c_no_context_takeover and + self._request.get_parameter_value( + self._S2C_NO_CONTEXT_TAKEOVER_PARAM) is not None): + return None + + self._deflater = util._RFC1979Deflater( + s2c_max_window_bits, s2c_no_context_takeover) + + self._inflater = util._RFC1979Inflater() + + self._compress_outgoing_enabled = True + + response = common.ExtensionParameter(self._request.name()) + + if s2c_max_window_bits is not None: + response.add_parameter( + self._S2C_MAX_WINDOW_BITS_PARAM, str(s2c_max_window_bits)) + + if s2c_no_context_takeover: + response.add_parameter( + self._S2C_NO_CONTEXT_TAKEOVER_PARAM, None) + + if self._c2s_max_window_bits is not None: + response.add_parameter( + self._C2S_MAX_WINDOW_BITS_PARAM, + str(self._c2s_max_window_bits)) + if self._c2s_no_context_takeover: + response.add_parameter( + self._C2S_NO_CONTEXT_TAKEOVER_PARAM, None) + + self._logger.debug( + 'Enable %s extension (' + 'request: s2c_max_window_bits=%s; s2c_no_context_takeover=%r, ' + 'response: c2s_max_window_bits=%s; c2s_no_context_takeover=%r)' % + (self._request.name(), + s2c_max_window_bits, + s2c_no_context_takeover, + self._c2s_max_window_bits, + self._c2s_no_context_takeover)) + + return response + + def setup_stream_options(self, stream_options): + class _OutgoingMessageFilter(object): + + def __init__(self, parent): + self._parent = parent + + def filter(self, message, end=True, binary=False): + return self._parent._process_outgoing_message( + message, end, binary) + + class _IncomingMessageFilter(object): + + def __init__(self, parent): + self._parent = parent + self._decompress_next_message = False + + def decompress_next_message(self): + self._decompress_next_message = True + + def filter(self, message): + message = self._parent._process_incoming_message( + message, self._decompress_next_message) + self._decompress_next_message = False + return message + + self._outgoing_message_filter = _OutgoingMessageFilter(self) + self._incoming_message_filter = _IncomingMessageFilter(self) + stream_options.outgoing_message_filters.append( + self._outgoing_message_filter) + stream_options.incoming_message_filters.append( + self._incoming_message_filter) + + class _OutgoingFrameFilter(object): + + def __init__(self, parent): + self._parent = parent + self._set_compression_bit = False + + def set_compression_bit(self): + self._set_compression_bit = True + + def filter(self, frame): + self._parent._process_outgoing_frame( + frame, self._set_compression_bit) + self._set_compression_bit = False + + class _IncomingFrameFilter(object): + + def __init__(self, parent): + self._parent = parent + + def filter(self, frame): + self._parent._process_incoming_frame(frame) + + self._outgoing_frame_filter = _OutgoingFrameFilter(self) + self._incoming_frame_filter = _IncomingFrameFilter(self) + stream_options.outgoing_frame_filters.append( + self._outgoing_frame_filter) + stream_options.incoming_frame_filters.append( + self._incoming_frame_filter) + + stream_options.encode_text_message_to_utf8 = False + + def set_c2s_max_window_bits(self, value): + self._c2s_max_window_bits = value + + def set_c2s_no_context_takeover(self, value): + self._c2s_no_context_takeover = value + + def set_bfinal(self, value): + self._bfinal = value + + def enable_outgoing_compression(self): + self._compress_outgoing_enabled = True + + def disable_outgoing_compression(self): + self._compress_outgoing_enabled = False + + def _process_incoming_message(self, message, decompress): + if not decompress: + return message + + received_payload_size = len(message) + self._total_incoming_payload_bytes += received_payload_size + + message = self._inflater.filter(message) + + filtered_payload_size = len(message) + self._total_filtered_incoming_payload_bytes += filtered_payload_size + + _log_decompression_ratio(self._logger, received_payload_size, + self._total_incoming_payload_bytes, + filtered_payload_size, + self._total_filtered_incoming_payload_bytes) + + return message + + def _process_outgoing_message(self, message, end, binary): + if not binary: + message = message.encode('utf-8') + + if not self._compress_outgoing_enabled: + return message + + original_payload_size = len(message) + self._total_outgoing_payload_bytes += original_payload_size + + message = self._deflater.filter( + message, flush=end, bfinal=self._bfinal) + + filtered_payload_size = len(message) + self._total_filtered_outgoing_payload_bytes += filtered_payload_size + + _log_compression_ratio(self._logger, original_payload_size, + self._total_outgoing_payload_bytes, + filtered_payload_size, + self._total_filtered_outgoing_payload_bytes) + + if not self._compress_ongoing: + self._outgoing_frame_filter.set_compression_bit() + self._compress_ongoing = not end + return message + + def _process_incoming_frame(self, frame): + if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): + self._incoming_message_filter.decompress_next_message() + frame.rsv1 = 0 + + def _process_outgoing_frame(self, frame, compression_bit): + if (not compression_bit or + common.is_control_opcode(frame.opcode)): + return + + frame.rsv1 = 1 + + +class PerMessageCompressionExtensionProcessor( + CompressionExtensionProcessorBase): + """WebSocket Per-message compression extension processor. + + Specification: + http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression + """ + + _DEFLATE_METHOD = 'deflate' + + def __init__(self, request): + CompressionExtensionProcessorBase.__init__(self, request) + + def name(self): + return common.PERMESSAGE_COMPRESSION_EXTENSION + + def _lookup_compression_processor(self, method_desc): + if method_desc.name() == self._DEFLATE_METHOD: + return DeflateMessageProcessor(method_desc) + return None + + +_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = ( + PerMessageCompressionExtensionProcessor) + + +# Adding vendor-prefixed permessage-compress extension. +# TODO(bashi): Remove this after WebKit stops using vendor prefix. +_available_processors[common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION] = ( + PerMessageCompressionExtensionProcessor) + + +class MuxExtensionProcessor(ExtensionProcessorInterface): + """WebSocket multiplexing extension processor.""" + + _QUOTA_PARAM = 'quota' + + def __init__(self, request): + self._request = request + + def name(self): + return common.MUX_EXTENSION + + def get_extension_response(self, ws_request, + logical_channel_extensions): + # Mux extension cannot be used after extensions that depend on + # frame boundary, extension data field, or any reserved bits + # which are attributed to each frame. + for extension in logical_channel_extensions: + name = extension.name() + if (name == common.PERFRAME_COMPRESSION_EXTENSION or + name == common.DEFLATE_FRAME_EXTENSION or + name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): + return None + + quota = self._request.get_parameter_value(self._QUOTA_PARAM) + if quota is None: + ws_request.mux_quota = 0 + else: + try: + quota = int(quota) + except ValueError, e: + return None + if quota < 0 or quota >= 2 ** 32: + return None + ws_request.mux_quota = quota + + ws_request.mux = True + ws_request.mux_extensions = logical_channel_extensions + return common.ExtensionParameter(common.MUX_EXTENSION) + + def setup_stream_options(self, stream_options): + pass + + +_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor + + +def get_extension_processor(extension_request): + global _available_processors + processor_class = _available_processors.get(extension_request.name()) + if processor_class is None: + return None + return processor_class(extension_request) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/handshake/__init__.py b/pyload/lib/mod_pywebsocket/handshake/__init__.py new file mode 100644 index 000000000..194f6b395 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/handshake/__init__.py @@ -0,0 +1,110 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""WebSocket opening handshake processor. This class try to apply available +opening handshake processors for each protocol version until a connection is +successfully established. +""" + + +import logging + +from mod_pywebsocket import common +from mod_pywebsocket.handshake import hybi00 +from mod_pywebsocket.handshake import hybi +# Export AbortedByUserException, HandshakeException, and VersionException +# symbol from this module. +from mod_pywebsocket.handshake._base import AbortedByUserException +from mod_pywebsocket.handshake._base import HandshakeException +from mod_pywebsocket.handshake._base import VersionException + + +_LOGGER = logging.getLogger(__name__) + + +def do_handshake(request, dispatcher, allowDraft75=False, strict=False): + """Performs WebSocket handshake. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + allowDraft75: obsolete argument. ignored. + strict: obsolete argument. ignored. + + Handshaker will add attributes such as ws_resource in performing + handshake. + """ + + _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri) + # To print mimetools.Message as escaped one-line string, we converts + # headers_in to dict object. Without conversion, if we use %r, it just + # prints the type and address, and if we use %s, it prints the original + # header string as multiple lines. + # + # Both mimetools.Message and MpTable_Type of mod_python can be + # converted to dict. + # + # mimetools.Message.__str__ returns the original header string. + # dict(mimetools.Message object) returns the map from header names to + # header values. While MpTable_Type doesn't have such __str__ but just + # __repr__ which formats itself as well as dictionary object. + _LOGGER.debug( + 'Client\'s opening handshake headers: %r', dict(request.headers_in)) + + handshakers = [] + handshakers.append( + ('RFC 6455', hybi.Handshaker(request, dispatcher))) + handshakers.append( + ('HyBi 00', hybi00.Handshaker(request, dispatcher))) + + for name, handshaker in handshakers: + _LOGGER.debug('Trying protocol version %s', name) + try: + handshaker.do_handshake() + _LOGGER.info('Established (%s protocol)', name) + return + except HandshakeException, e: + _LOGGER.debug( + 'Failed to complete opening handshake as %s protocol: %r', + name, e) + if e.status: + raise e + except AbortedByUserException, e: + raise + except VersionException, e: + raise + + # TODO(toyoshim): Add a test to cover the case all handshakers fail. + raise HandshakeException( + 'Failed to complete opening handshake for all available protocols', + status=common.HTTP_STATUS_BAD_REQUEST) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/handshake/_base.py b/pyload/lib/mod_pywebsocket/handshake/_base.py new file mode 100644 index 000000000..e5c94ca90 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/handshake/_base.py @@ -0,0 +1,226 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Common functions and exceptions used by WebSocket opening handshake +processors. +""" + + +from mod_pywebsocket import common +from mod_pywebsocket import http_header_util + + +class AbortedByUserException(Exception): + """Exception for aborting a connection intentionally. + + If this exception is raised in do_extra_handshake handler, the connection + will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. + + If this exception is raised in transfer_data_handler, the connection will + be closed without closing handshake. No other WebSocket or HTTP(S) handler + will be invoked. + """ + + pass + + +class HandshakeException(Exception): + """This exception will be raised when an error occurred while processing + WebSocket initial handshake. + """ + + def __init__(self, name, status=None): + super(HandshakeException, self).__init__(name) + self.status = status + + +class VersionException(Exception): + """This exception will be raised when a version of client request does not + match with version the server supports. + """ + + def __init__(self, name, supported_versions=''): + """Construct an instance. + + Args: + supported_version: a str object to show supported hybi versions. + (e.g. '8, 13') + """ + super(VersionException, self).__init__(name) + self.supported_versions = supported_versions + + +def get_default_port(is_secure): + if is_secure: + return common.DEFAULT_WEB_SOCKET_SECURE_PORT + else: + return common.DEFAULT_WEB_SOCKET_PORT + + +def validate_subprotocol(subprotocol, hixie): + """Validate a value in the Sec-WebSocket-Protocol field. + + See + - RFC 6455: Section 4.1., 4.2.2., and 4.3. + - HyBi 00: Section 4.1. Opening handshake + + Args: + hixie: if True, checks if characters in subprotocol are in range + between U+0020 and U+007E. It's required by HyBi 00 but not by + RFC 6455. + """ + + if not subprotocol: + raise HandshakeException('Invalid subprotocol name: empty') + if hixie: + # Parameter should be in the range U+0020 to U+007E. + for c in subprotocol: + if not 0x20 <= ord(c) <= 0x7e: + raise HandshakeException( + 'Illegal character in subprotocol name: %r' % c) + else: + # Parameter should be encoded HTTP token. + state = http_header_util.ParsingState(subprotocol) + token = http_header_util.consume_token(state) + rest = http_header_util.peek(state) + # If |rest| is not None, |subprotocol| is not one token or invalid. If + # |rest| is None, |token| must not be None because |subprotocol| is + # concatenation of |token| and |rest| and is not None. + if rest is not None: + raise HandshakeException('Invalid non-token string in subprotocol ' + 'name: %r' % rest) + + +def parse_host_header(request): + fields = request.headers_in['Host'].split(':', 1) + if len(fields) == 1: + return fields[0], get_default_port(request.is_https()) + try: + return fields[0], int(fields[1]) + except ValueError, e: + raise HandshakeException('Invalid port number format: %r' % e) + + +def format_header(name, value): + return '%s: %s\r\n' % (name, value) + + +def build_location(request): + """Build WebSocket location for request.""" + location_parts = [] + if request.is_https(): + location_parts.append(common.WEB_SOCKET_SECURE_SCHEME) + else: + location_parts.append(common.WEB_SOCKET_SCHEME) + location_parts.append('://') + host, port = parse_host_header(request) + connection_port = request.connection.local_addr[1] + if port != connection_port: + raise HandshakeException('Header/connection port mismatch: %d/%d' % + (port, connection_port)) + location_parts.append(host) + if (port != get_default_port(request.is_https())): + location_parts.append(':') + location_parts.append(str(port)) + location_parts.append(request.uri) + return ''.join(location_parts) + + +def get_mandatory_header(request, key): + value = request.headers_in.get(key) + if value is None: + raise HandshakeException('Header %s is not defined' % key) + return value + + +def validate_mandatory_header(request, key, expected_value, fail_status=None): + value = get_mandatory_header(request, key) + + if value.lower() != expected_value.lower(): + raise HandshakeException( + 'Expected %r for header %s but found %r (case-insensitive)' % + (expected_value, key, value), status=fail_status) + + +def check_request_line(request): + # 5.1 1. The three character UTF-8 string "GET". + # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). + if request.method != 'GET': + raise HandshakeException('Method is not GET: %r' % request.method) + + if request.protocol != 'HTTP/1.1': + raise HandshakeException('Version is not HTTP/1.1: %r' % + request.protocol) + + +def check_header_lines(request, mandatory_headers): + check_request_line(request) + + # The expected field names, and the meaning of their corresponding + # values, are as follows. + # |Upgrade| and |Connection| + for key, expected_value in mandatory_headers: + validate_mandatory_header(request, key, expected_value) + + +def parse_token_list(data): + """Parses a header value which follows 1#token and returns parsed elements + as a list of strings. + + Leading LWSes must be trimmed. + """ + + state = http_header_util.ParsingState(data) + + token_list = [] + + while True: + token = http_header_util.consume_token(state) + if token is not None: + token_list.append(token) + + http_header_util.consume_lwses(state) + + if http_header_util.peek(state) is None: + break + + if not http_header_util.consume_string(state, ','): + raise HandshakeException( + 'Expected a comma but found %r' % http_header_util.peek(state)) + + http_header_util.consume_lwses(state) + + if len(token_list) == 0: + raise HandshakeException('No valid token found') + + return token_list + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/handshake/hybi.py b/pyload/lib/mod_pywebsocket/handshake/hybi.py new file mode 100644 index 000000000..fc0e2a096 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/handshake/hybi.py @@ -0,0 +1,404 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file provides the opening handshake processor for the WebSocket +protocol (RFC 6455). + +Specification: +http://tools.ietf.org/html/rfc6455 +""" + + +# Note: request.connection.write is used in this module, even though mod_python +# document says that it should be used only in connection handlers. +# Unfortunately, we have no other options. For example, request.write is not +# suitable because it doesn't allow direct raw bytes writing. + + +import base64 +import logging +import os +import re + +from mod_pywebsocket import common +from mod_pywebsocket.extensions import get_extension_processor +from mod_pywebsocket.handshake._base import check_request_line +from mod_pywebsocket.handshake._base import format_header +from mod_pywebsocket.handshake._base import get_mandatory_header +from mod_pywebsocket.handshake._base import HandshakeException +from mod_pywebsocket.handshake._base import parse_token_list +from mod_pywebsocket.handshake._base import validate_mandatory_header +from mod_pywebsocket.handshake._base import validate_subprotocol +from mod_pywebsocket.handshake._base import VersionException +from mod_pywebsocket.stream import Stream +from mod_pywebsocket.stream import StreamOptions +from mod_pywebsocket import util + + +# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648 +# disallows non-zero padding, so the character right before == must be any of +# A, Q, g and w. +_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$') + +# Defining aliases for values used frequently. +_VERSION_HYBI08 = common.VERSION_HYBI08 +_VERSION_HYBI08_STRING = str(_VERSION_HYBI08) +_VERSION_LATEST = common.VERSION_HYBI_LATEST +_VERSION_LATEST_STRING = str(_VERSION_LATEST) +_SUPPORTED_VERSIONS = [ + _VERSION_LATEST, + _VERSION_HYBI08, +] + + +def compute_accept(key): + """Computes value for the Sec-WebSocket-Accept header from value of the + Sec-WebSocket-Key header. + """ + + accept_binary = util.sha1_hash( + key + common.WEBSOCKET_ACCEPT_UUID).digest() + accept = base64.b64encode(accept_binary) + + return (accept, accept_binary) + + +class Handshaker(object): + """Opening handshake processor for the WebSocket protocol (RFC 6455).""" + + def __init__(self, request, dispatcher): + """Construct an instance. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + + Handshaker will add attributes such as ws_resource during handshake. + """ + + self._logger = util.get_class_logger(self) + + self._request = request + self._dispatcher = dispatcher + + def _validate_connection_header(self): + connection = get_mandatory_header( + self._request, common.CONNECTION_HEADER) + + try: + connection_tokens = parse_token_list(connection) + except HandshakeException, e: + raise HandshakeException( + 'Failed to parse %s: %s' % (common.CONNECTION_HEADER, e)) + + connection_is_valid = False + for token in connection_tokens: + if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower(): + connection_is_valid = True + break + if not connection_is_valid: + raise HandshakeException( + '%s header doesn\'t contain "%s"' % + (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) + + def do_handshake(self): + self._request.ws_close_code = None + self._request.ws_close_reason = None + + # Parsing. + + check_request_line(self._request) + + validate_mandatory_header( + self._request, + common.UPGRADE_HEADER, + common.WEBSOCKET_UPGRADE_TYPE) + + self._validate_connection_header() + + self._request.ws_resource = self._request.uri + + unused_host = get_mandatory_header(self._request, common.HOST_HEADER) + + self._request.ws_version = self._check_version() + + # This handshake must be based on latest hybi. We are responsible to + # fallback to HTTP on handshake failure as latest hybi handshake + # specifies. + try: + self._get_origin() + self._set_protocol() + self._parse_extensions() + + # Key validation, response generation. + + key = self._get_key() + (accept, accept_binary) = compute_accept(key) + self._logger.debug( + '%s: %r (%s)', + common.SEC_WEBSOCKET_ACCEPT_HEADER, + accept, + util.hexify(accept_binary)) + + self._logger.debug('Protocol version is RFC 6455') + + # Setup extension processors. + + processors = [] + if self._request.ws_requested_extensions is not None: + for extension_request in self._request.ws_requested_extensions: + processor = get_extension_processor(extension_request) + # Unknown extension requests are just ignored. + if processor is not None: + processors.append(processor) + self._request.ws_extension_processors = processors + + # Extra handshake handler may modify/remove processors. + self._dispatcher.do_extra_handshake(self._request) + processors = filter(lambda processor: processor is not None, + self._request.ws_extension_processors) + + accepted_extensions = [] + + # We need to take care of mux extension here. Extensions that + # are placed before mux should be applied to logical channels. + mux_index = -1 + for i, processor in enumerate(processors): + if processor.name() == common.MUX_EXTENSION: + mux_index = i + break + if mux_index >= 0: + mux_processor = processors[mux_index] + logical_channel_processors = processors[:mux_index] + processors = processors[mux_index+1:] + + for processor in logical_channel_processors: + extension_response = processor.get_extension_response() + if extension_response is None: + # Rejected. + continue + accepted_extensions.append(extension_response) + # Pass a shallow copy of accepted_extensions as extensions for + # logical channels. + mux_response = mux_processor.get_extension_response( + self._request, accepted_extensions[:]) + if mux_response is not None: + accepted_extensions.append(mux_response) + + stream_options = StreamOptions() + + # When there is mux extension, here, |processors| contain only + # prosessors for extensions placed after mux. + for processor in processors: + + extension_response = processor.get_extension_response() + if extension_response is None: + # Rejected. + continue + + accepted_extensions.append(extension_response) + + processor.setup_stream_options(stream_options) + + if len(accepted_extensions) > 0: + self._request.ws_extensions = accepted_extensions + self._logger.debug( + 'Extensions accepted: %r', + map(common.ExtensionParameter.name, accepted_extensions)) + else: + self._request.ws_extensions = None + + self._request.ws_stream = self._create_stream(stream_options) + + if self._request.ws_requested_protocols is not None: + if self._request.ws_protocol is None: + raise HandshakeException( + 'do_extra_handshake must choose one subprotocol from ' + 'ws_requested_protocols and set it to ws_protocol') + validate_subprotocol(self._request.ws_protocol, hixie=False) + + self._logger.debug( + 'Subprotocol accepted: %r', + self._request.ws_protocol) + else: + if self._request.ws_protocol is not None: + raise HandshakeException( + 'ws_protocol must be None when the client didn\'t ' + 'request any subprotocol') + + self._send_handshake(accept) + except HandshakeException, e: + if not e.status: + # Fallback to 400 bad request by default. + e.status = common.HTTP_STATUS_BAD_REQUEST + raise e + + def _get_origin(self): + if self._request.ws_version is _VERSION_HYBI08: + origin_header = common.SEC_WEBSOCKET_ORIGIN_HEADER + else: + origin_header = common.ORIGIN_HEADER + origin = self._request.headers_in.get(origin_header) + if origin is None: + self._logger.debug('Client request does not have origin header') + self._request.ws_origin = origin + + def _check_version(self): + version = get_mandatory_header(self._request, + common.SEC_WEBSOCKET_VERSION_HEADER) + if version == _VERSION_HYBI08_STRING: + return _VERSION_HYBI08 + if version == _VERSION_LATEST_STRING: + return _VERSION_LATEST + + if version.find(',') >= 0: + raise HandshakeException( + 'Multiple versions (%r) are not allowed for header %s' % + (version, common.SEC_WEBSOCKET_VERSION_HEADER), + status=common.HTTP_STATUS_BAD_REQUEST) + raise VersionException( + 'Unsupported version %r for header %s' % + (version, common.SEC_WEBSOCKET_VERSION_HEADER), + supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS))) + + def _set_protocol(self): + self._request.ws_protocol = None + + protocol_header = self._request.headers_in.get( + common.SEC_WEBSOCKET_PROTOCOL_HEADER) + + if protocol_header is None: + self._request.ws_requested_protocols = None + return + + self._request.ws_requested_protocols = parse_token_list( + protocol_header) + self._logger.debug('Subprotocols requested: %r', + self._request.ws_requested_protocols) + + def _parse_extensions(self): + extensions_header = self._request.headers_in.get( + common.SEC_WEBSOCKET_EXTENSIONS_HEADER) + if not extensions_header: + self._request.ws_requested_extensions = None + return + + if self._request.ws_version is common.VERSION_HYBI08: + allow_quoted_string=False + else: + allow_quoted_string=True + try: + self._request.ws_requested_extensions = common.parse_extensions( + extensions_header, allow_quoted_string=allow_quoted_string) + except common.ExtensionParsingException, e: + raise HandshakeException( + 'Failed to parse Sec-WebSocket-Extensions header: %r' % e) + + self._logger.debug( + 'Extensions requested: %r', + map(common.ExtensionParameter.name, + self._request.ws_requested_extensions)) + + def _validate_key(self, key): + if key.find(',') >= 0: + raise HandshakeException('Request has multiple %s header lines or ' + 'contains illegal character \',\': %r' % + (common.SEC_WEBSOCKET_KEY_HEADER, key)) + + # Validate + key_is_valid = False + try: + # Validate key by quick regex match before parsing by base64 + # module. Because base64 module skips invalid characters, we have + # to do this in advance to make this server strictly reject illegal + # keys. + if _SEC_WEBSOCKET_KEY_REGEX.match(key): + decoded_key = base64.b64decode(key) + if len(decoded_key) == 16: + key_is_valid = True + except TypeError, e: + pass + + if not key_is_valid: + raise HandshakeException( + 'Illegal value for header %s: %r' % + (common.SEC_WEBSOCKET_KEY_HEADER, key)) + + return decoded_key + + def _get_key(self): + key = get_mandatory_header( + self._request, common.SEC_WEBSOCKET_KEY_HEADER) + + decoded_key = self._validate_key(key) + + self._logger.debug( + '%s: %r (%s)', + common.SEC_WEBSOCKET_KEY_HEADER, + key, + util.hexify(decoded_key)) + + return key + + def _create_stream(self, stream_options): + return Stream(self._request, stream_options) + + def _create_handshake_response(self, accept): + response = [] + + response.append('HTTP/1.1 101 Switching Protocols\r\n') + + response.append(format_header( + common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE)) + response.append(format_header( + common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) + response.append(format_header( + common.SEC_WEBSOCKET_ACCEPT_HEADER, accept)) + if self._request.ws_protocol is not None: + response.append(format_header( + common.SEC_WEBSOCKET_PROTOCOL_HEADER, + self._request.ws_protocol)) + if (self._request.ws_extensions is not None and + len(self._request.ws_extensions) != 0): + response.append(format_header( + common.SEC_WEBSOCKET_EXTENSIONS_HEADER, + common.format_extensions(self._request.ws_extensions))) + response.append('\r\n') + + return ''.join(response) + + def _send_handshake(self, accept): + raw_response = self._create_handshake_response(accept) + self._request.connection.write(raw_response) + self._logger.debug('Sent server\'s opening handshake: %r', + raw_response) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/handshake/hybi00.py b/pyload/lib/mod_pywebsocket/handshake/hybi00.py new file mode 100644 index 000000000..cc6f8dc43 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/handshake/hybi00.py @@ -0,0 +1,242 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file provides the opening handshake processor for the WebSocket +protocol version HyBi 00. + +Specification: +http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 +""" + + +# Note: request.connection.write/read are used in this module, even though +# mod_python document says that they should be used only in connection +# handlers. Unfortunately, we have no other options. For example, +# request.write/read are not suitable because they don't allow direct raw bytes +# writing/reading. + + +import logging +import re +import struct + +from mod_pywebsocket import common +from mod_pywebsocket.stream import StreamHixie75 +from mod_pywebsocket import util +from mod_pywebsocket.handshake._base import HandshakeException +from mod_pywebsocket.handshake._base import build_location +from mod_pywebsocket.handshake._base import check_header_lines +from mod_pywebsocket.handshake._base import format_header +from mod_pywebsocket.handshake._base import get_mandatory_header +from mod_pywebsocket.handshake._base import validate_subprotocol + + +_MANDATORY_HEADERS = [ + # key, expected value or None + [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75], + [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE], +] + + +class Handshaker(object): + """Opening handshake processor for the WebSocket protocol version HyBi 00. + """ + + def __init__(self, request, dispatcher): + """Construct an instance. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + + Handshaker will add attributes such as ws_resource in performing + handshake. + """ + + self._logger = util.get_class_logger(self) + + self._request = request + self._dispatcher = dispatcher + + def do_handshake(self): + """Perform WebSocket Handshake. + + On _request, we set + ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge, + ws_challenge_md5: WebSocket handshake information. + ws_stream: Frame generation/parsing class. + ws_version: Protocol version. + + Raises: + HandshakeException: when any error happened in parsing the opening + handshake request. + """ + + # 5.1 Reading the client's opening handshake. + # dispatcher sets it in self._request. + check_header_lines(self._request, _MANDATORY_HEADERS) + self._set_resource() + self._set_subprotocol() + self._set_location() + self._set_origin() + self._set_challenge_response() + self._set_protocol_version() + + self._dispatcher.do_extra_handshake(self._request) + + self._send_handshake() + + def _set_resource(self): + self._request.ws_resource = self._request.uri + + def _set_subprotocol(self): + # |Sec-WebSocket-Protocol| + subprotocol = self._request.headers_in.get( + common.SEC_WEBSOCKET_PROTOCOL_HEADER) + if subprotocol is not None: + validate_subprotocol(subprotocol, hixie=True) + self._request.ws_protocol = subprotocol + + def _set_location(self): + # |Host| + host = self._request.headers_in.get(common.HOST_HEADER) + if host is not None: + self._request.ws_location = build_location(self._request) + # TODO(ukai): check host is this host. + + def _set_origin(self): + # |Origin| + origin = self._request.headers_in.get(common.ORIGIN_HEADER) + if origin is not None: + self._request.ws_origin = origin + + def _set_protocol_version(self): + # |Sec-WebSocket-Draft| + draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER) + if draft is not None and draft != '0': + raise HandshakeException('Illegal value for %s: %s' % + (common.SEC_WEBSOCKET_DRAFT_HEADER, + draft)) + + self._logger.debug('Protocol version is HyBi 00') + self._request.ws_version = common.VERSION_HYBI00 + self._request.ws_stream = StreamHixie75(self._request, True) + + def _set_challenge_response(self): + # 5.2 4-8. + self._request.ws_challenge = self._get_challenge() + # 5.2 9. let /response/ be the MD5 finterprint of /challenge/ + self._request.ws_challenge_md5 = util.md5_hash( + self._request.ws_challenge).digest() + self._logger.debug( + 'Challenge: %r (%s)', + self._request.ws_challenge, + util.hexify(self._request.ws_challenge)) + self._logger.debug( + 'Challenge response: %r (%s)', + self._request.ws_challenge_md5, + util.hexify(self._request.ws_challenge_md5)) + + def _get_key_value(self, key_field): + key_value = get_mandatory_header(self._request, key_field) + + self._logger.debug('%s: %r', key_field, key_value) + + # 5.2 4. let /key-number_n/ be the digits (characters in the range + # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/, + # interpreted as a base ten integer, ignoring all other characters + # in /key_n/. + try: + key_number = int(re.sub("\\D", "", key_value)) + except: + raise HandshakeException('%s field contains no digit' % key_field) + # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters + # in /key_n/. + spaces = re.subn(" ", "", key_value)[1] + if spaces == 0: + raise HandshakeException('%s field contains no space' % key_field) + + self._logger.debug( + '%s: Key-number is %d and number of spaces is %d', + key_field, key_number, spaces) + + # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/ + # then abort the WebSocket connection. + if key_number % spaces != 0: + raise HandshakeException( + '%s: Key-number (%d) is not an integral multiple of spaces ' + '(%d)' % (key_field, key_number, spaces)) + # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/. + part = key_number / spaces + self._logger.debug('%s: Part is %d', key_field, part) + return part + + def _get_challenge(self): + # 5.2 4-7. + key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER) + key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER) + # 5.2 8. let /challenge/ be the concatenation of /part_1/, + challenge = '' + challenge += struct.pack('!I', key1) # network byteorder int + challenge += struct.pack('!I', key2) # network byteorder int + challenge += self._request.connection.read(8) + return challenge + + def _send_handshake(self): + response = [] + + # 5.2 10. send the following line. + response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n') + + # 5.2 11. send the following fields to the client. + response.append(format_header( + common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75)) + response.append(format_header( + common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) + response.append(format_header( + common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location)) + response.append(format_header( + common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin)) + if self._request.ws_protocol: + response.append(format_header( + common.SEC_WEBSOCKET_PROTOCOL_HEADER, + self._request.ws_protocol)) + # 5.2 12. send two bytes 0x0D 0x0A. + response.append('\r\n') + # 5.2 13. send /response/ + response.append(self._request.ws_challenge_md5) + + raw_response = ''.join(response) + self._request.connection.write(raw_response) + self._logger.debug('Sent server\'s opening handshake: %r', + raw_response) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/headerparserhandler.py b/pyload/lib/mod_pywebsocket/headerparserhandler.py new file mode 100644 index 000000000..2cc62de04 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/headerparserhandler.py @@ -0,0 +1,244 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""PythonHeaderParserHandler for mod_pywebsocket. + +Apache HTTP Server and mod_python must be configured such that this +function is called to handle WebSocket request. +""" + + +import logging + +from mod_python import apache + +from mod_pywebsocket import common +from mod_pywebsocket import dispatch +from mod_pywebsocket import handshake +from mod_pywebsocket import util + + +# PythonOption to specify the handler root directory. +_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' + +# PythonOption to specify the handler scan directory. +# This must be a directory under the root directory. +# The default is the root directory. +_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' + +# PythonOption to allow handlers whose canonical path is +# not under the root directory. It's disallowed by default. +# Set this option with value of 'yes' to allow. +_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = ( + 'mod_pywebsocket.allow_handlers_outside_root_dir') +# Map from values to their meanings. 'Yes' and 'No' are allowed just for +# compatibility. +_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = { + 'off': False, 'no': False, 'on': True, 'yes': True} + +# (Obsolete option. Ignored.) +# PythonOption to specify to allow handshake defined in Hixie 75 version +# protocol. The default is None (Off) +_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' +# Map from values to their meanings. +_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True} + + +class ApacheLogHandler(logging.Handler): + """Wrapper logging.Handler to emit log message to apache's error.log.""" + + _LEVELS = { + logging.DEBUG: apache.APLOG_DEBUG, + logging.INFO: apache.APLOG_INFO, + logging.WARNING: apache.APLOG_WARNING, + logging.ERROR: apache.APLOG_ERR, + logging.CRITICAL: apache.APLOG_CRIT, + } + + def __init__(self, request=None): + logging.Handler.__init__(self) + self._log_error = apache.log_error + if request is not None: + self._log_error = request.log_error + + # Time and level will be printed by Apache. + self._formatter = logging.Formatter('%(name)s: %(message)s') + + def emit(self, record): + apache_level = apache.APLOG_DEBUG + if record.levelno in ApacheLogHandler._LEVELS: + apache_level = ApacheLogHandler._LEVELS[record.levelno] + + msg = self._formatter.format(record) + + # "server" parameter must be passed to have "level" parameter work. + # If only "level" parameter is passed, nothing shows up on Apache's + # log. However, at this point, we cannot get the server object of the + # virtual host which will process WebSocket requests. The only server + # object we can get here is apache.main_server. But Wherever (server + # configuration context or virtual host context) we put + # PythonHeaderParserHandler directive, apache.main_server just points + # the main server instance (not any of virtual server instance). Then, + # Apache follows LogLevel directive in the server configuration context + # to filter logs. So, we need to specify LogLevel in the server + # configuration context. Even if we specify "LogLevel debug" in the + # virtual host context which actually handles WebSocket connections, + # DEBUG level logs never show up unless "LogLevel debug" is specified + # in the server configuration context. + # + # TODO(tyoshino): Provide logging methods on request object. When + # request is mp_request object (when used together with Apache), the + # methods call request.log_error indirectly. When request is + # _StandaloneRequest, the methods call Python's logging facility which + # we create in standalone.py. + self._log_error(msg, apache_level, apache.main_server) + + +def _configure_logging(): + logger = logging.getLogger() + # Logs are filtered by Apache based on LogLevel directive in Apache + # configuration file. We must just pass logs for all levels to + # ApacheLogHandler. + logger.setLevel(logging.DEBUG) + logger.addHandler(ApacheLogHandler()) + + +_configure_logging() + +_LOGGER = logging.getLogger(__name__) + + +def _parse_option(name, value, definition): + if value is None: + return False + + meaning = definition.get(value.lower()) + if meaning is None: + raise Exception('Invalid value for PythonOption %s: %r' % + (name, value)) + return meaning + + +def _create_dispatcher(): + _LOGGER.info('Initializing Dispatcher') + + options = apache.main_server.get_options() + + handler_root = options.get(_PYOPT_HANDLER_ROOT, None) + if not handler_root: + raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, + apache.APLOG_ERR) + + handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) + + allow_handlers_outside_root = _parse_option( + _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, + options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), + _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) + + dispatcher = dispatch.Dispatcher( + handler_root, handler_scan, allow_handlers_outside_root) + + for warning in dispatcher.source_warnings(): + apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) + + return dispatcher + + +# Initialize +_dispatcher = _create_dispatcher() + + +def headerparserhandler(request): + """Handle request. + + Args: + request: mod_python request. + + This function is named headerparserhandler because it is the default + name for a PythonHeaderParserHandler. + """ + + handshake_is_done = False + try: + # Fallback to default http handler for request paths for which + # we don't have request handlers. + if not _dispatcher.get_handler_suite(request.uri): + request.log_error('No handler for resource: %r' % request.uri, + apache.APLOG_INFO) + request.log_error('Fallback to Apache', apache.APLOG_INFO) + return apache.DECLINED + except dispatch.DispatchException, e: + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) + if not handshake_is_done: + return e.status + + try: + allow_draft75 = _parse_option( + _PYOPT_ALLOW_DRAFT75, + apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75), + _PYOPT_ALLOW_DRAFT75_DEFINITION) + + try: + handshake.do_handshake( + request, _dispatcher, allowDraft75=allow_draft75) + except handshake.VersionException, e: + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) + request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER, + e.supported_versions) + return apache.HTTP_BAD_REQUEST + except handshake.HandshakeException, e: + # Handshake for ws/wss failed. + # Send http response with error status. + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) + return e.status + + handshake_is_done = True + request._dispatcher = _dispatcher + _dispatcher.transfer_data(request) + except handshake.AbortedByUserException, e: + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) + except Exception, e: + # DispatchException can also be thrown if something is wrong in + # pywebsocket code. It's caught here, then. + + request.log_error('mod_pywebsocket: %s\n%s' % + (e, util.get_stack_trace()), + apache.APLOG_ERR) + # Unknown exceptions before handshake mean Apache must handle its + # request with another handler. + if not handshake_is_done: + return apache.DECLINED + # Set assbackwards to suppress response header generation by Apache. + request.assbackwards = 1 + return apache.DONE # Return DONE such that no other handlers are invoked. + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/http_header_util.py b/pyload/lib/mod_pywebsocket/http_header_util.py new file mode 100644 index 000000000..b77465393 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/http_header_util.py @@ -0,0 +1,263 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Utilities for parsing and formatting headers that follow the grammar defined +in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt. +""" + + +import urlparse + + +_SEPARATORS = '()<>@,;:\\"/[]?={} \t' + + +def _is_char(c): + """Returns true iff c is in CHAR as specified in HTTP RFC.""" + + return ord(c) <= 127 + + +def _is_ctl(c): + """Returns true iff c is in CTL as specified in HTTP RFC.""" + + return ord(c) <= 31 or ord(c) == 127 + + +class ParsingState(object): + + def __init__(self, data): + self.data = data + self.head = 0 + + +def peek(state, pos=0): + """Peeks the character at pos from the head of data.""" + + if state.head + pos >= len(state.data): + return None + + return state.data[state.head + pos] + + +def consume(state, amount=1): + """Consumes specified amount of bytes from the head and returns the + consumed bytes. If there's not enough bytes to consume, returns None. + """ + + if state.head + amount > len(state.data): + return None + + result = state.data[state.head:state.head + amount] + state.head = state.head + amount + return result + + +def consume_string(state, expected): + """Given a parsing state and a expected string, consumes the string from + the head. Returns True if consumed successfully. Otherwise, returns + False. + """ + + pos = 0 + + for c in expected: + if c != peek(state, pos): + return False + pos += 1 + + consume(state, pos) + return True + + +def consume_lws(state): + """Consumes a LWS from the head. Returns True if any LWS is consumed. + Otherwise, returns False. + + LWS = [CRLF] 1*( SP | HT ) + """ + + original_head = state.head + + consume_string(state, '\r\n') + + pos = 0 + + while True: + c = peek(state, pos) + if c == ' ' or c == '\t': + pos += 1 + else: + if pos == 0: + state.head = original_head + return False + else: + consume(state, pos) + return True + + +def consume_lwses(state): + """Consumes *LWS from the head.""" + + while consume_lws(state): + pass + + +def consume_token(state): + """Consumes a token from the head. Returns the token or None if no token + was found. + """ + + pos = 0 + + while True: + c = peek(state, pos) + if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): + if pos == 0: + return None + + return consume(state, pos) + else: + pos += 1 + + +def consume_token_or_quoted_string(state): + """Consumes a token or a quoted-string, and returns the token or unquoted + string. If no token or quoted-string was found, returns None. + """ + + original_head = state.head + + if not consume_string(state, '"'): + return consume_token(state) + + result = [] + + expect_quoted_pair = False + + while True: + if not expect_quoted_pair and consume_lws(state): + result.append(' ') + continue + + c = consume(state) + if c is None: + # quoted-string is not enclosed with double quotation + state.head = original_head + return None + elif expect_quoted_pair: + expect_quoted_pair = False + if _is_char(c): + result.append(c) + else: + # Non CHAR character found in quoted-pair + state.head = original_head + return None + elif c == '\\': + expect_quoted_pair = True + elif c == '"': + return ''.join(result) + elif _is_ctl(c): + # Invalid character %r found in qdtext + state.head = original_head + return None + else: + result.append(c) + + +def quote_if_necessary(s): + """Quotes arbitrary string into quoted-string.""" + + quote = False + if s == '': + return '""' + + result = [] + for c in s: + if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): + quote = True + + if c == '"' or _is_ctl(c): + result.append('\\' + c) + else: + result.append(c) + + if quote: + return '"' + ''.join(result) + '"' + else: + return ''.join(result) + + +def parse_uri(uri): + """Parse absolute URI then return host, port and resource.""" + + parsed = urlparse.urlsplit(uri) + if parsed.scheme != 'wss' and parsed.scheme != 'ws': + # |uri| must be a relative URI. + # TODO(toyoshim): Should validate |uri|. + return None, None, uri + + if parsed.hostname is None: + return None, None, None + + port = None + try: + port = parsed.port + except ValueError, e: + # port property cause ValueError on invalid null port description like + # 'ws://host:/path'. + return None, None, None + + if port is None: + if parsed.scheme == 'ws': + port = 80 + else: + port = 443 + + path = parsed.path + if not path: + path += '/' + if parsed.query: + path += '?' + parsed.query + if parsed.fragment: + path += '#' + parsed.fragment + + return parsed.hostname, port, path + + +try: + urlparse.uses_netloc.index('ws') +except ValueError, e: + # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries. + urlparse.uses_netloc.append('ws') + urlparse.uses_netloc.append('wss') + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/memorizingfile.py b/pyload/lib/mod_pywebsocket/memorizingfile.py new file mode 100644 index 000000000..4d4cd9585 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/memorizingfile.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Memorizing file. + +A memorizing file wraps a file and memorizes lines read by readline. +""" + + +import sys + + +class MemorizingFile(object): + """MemorizingFile wraps a file and memorizes lines read by readline. + + Note that data read by other methods are not memorized. This behavior + is good enough for memorizing lines SimpleHTTPServer reads before + the control reaches WebSocketRequestHandler. + """ + + def __init__(self, file_, max_memorized_lines=sys.maxint): + """Construct an instance. + + Args: + file_: the file object to wrap. + max_memorized_lines: the maximum number of lines to memorize. + Only the first max_memorized_lines are memorized. + Default: sys.maxint. + """ + + self._file = file_ + self._memorized_lines = [] + self._max_memorized_lines = max_memorized_lines + self._buffered = False + self._buffered_line = None + + def __getattribute__(self, name): + if name in ('_file', '_memorized_lines', '_max_memorized_lines', + '_buffered', '_buffered_line', 'readline', + 'get_memorized_lines'): + return object.__getattribute__(self, name) + return self._file.__getattribute__(name) + + def readline(self, size=-1): + """Override file.readline and memorize the line read. + + Note that even if size is specified and smaller than actual size, + the whole line will be read out from underlying file object by + subsequent readline calls. + """ + + if self._buffered: + line = self._buffered_line + self._buffered = False + else: + line = self._file.readline() + if line and len(self._memorized_lines) < self._max_memorized_lines: + self._memorized_lines.append(line) + if size >= 0 and size < len(line): + self._buffered = True + self._buffered_line = line[size:] + return line[:size] + return line + + def get_memorized_lines(self): + """Get lines memorized so far.""" + return self._memorized_lines + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/msgutil.py b/pyload/lib/mod_pywebsocket/msgutil.py new file mode 100644 index 000000000..4c1a0114b --- /dev/null +++ b/pyload/lib/mod_pywebsocket/msgutil.py @@ -0,0 +1,219 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Message related utilities. + +Note: request.connection.write/read are used in this module, even though +mod_python document says that they should be used only in connection +handlers. Unfortunately, we have no other options. For example, +request.write/read are not suitable because they don't allow direct raw +bytes writing/reading. +""" + + +import Queue +import threading + + +# Export Exception symbols from msgutil for backward compatibility +from mod_pywebsocket._stream_base import ConnectionTerminatedException +from mod_pywebsocket._stream_base import InvalidFrameException +from mod_pywebsocket._stream_base import BadOperationException +from mod_pywebsocket._stream_base import UnsupportedFrameException + + +# An API for handler to send/receive WebSocket messages. +def close_connection(request): + """Close connection. + + Args: + request: mod_python request. + """ + request.ws_stream.close_connection() + + +def send_message(request, payload_data, end=True, binary=False): + """Send a message (or part of a message). + + Args: + request: mod_python request. + payload_data: unicode text or str binary to send. + end: True to terminate a message. + False to send payload_data as part of a message that is to be + terminated by next or later send_message call with end=True. + binary: send payload_data as binary frame(s). + Raises: + BadOperationException: when server already terminated. + """ + request.ws_stream.send_message(payload_data, end, binary) + + +def receive_message(request): + """Receive a WebSocket frame and return its payload as a text in + unicode or a binary in str. + + Args: + request: mod_python request. + Raises: + InvalidFrameException: when client send invalid frame. + UnsupportedFrameException: when client send unsupported frame e.g. some + of reserved bit is set but no extension can + recognize it. + InvalidUTF8Exception: when client send a text frame containing any + invalid UTF-8 string. + ConnectionTerminatedException: when the connection is closed + unexpectedly. + BadOperationException: when client already terminated. + """ + return request.ws_stream.receive_message() + + +def send_ping(request, body=''): + request.ws_stream.send_ping(body) + + +class MessageReceiver(threading.Thread): + """This class receives messages from the client. + + This class provides three ways to receive messages: blocking, + non-blocking, and via callback. Callback has the highest precedence. + + Note: This class should not be used with the standalone server for wss + because pyOpenSSL used by the server raises a fatal error if the socket + is accessed from multiple threads. + """ + + def __init__(self, request, onmessage=None): + """Construct an instance. + + Args: + request: mod_python request. + onmessage: a function to be called when a message is received. + May be None. If not None, the function is called on + another thread. In that case, MessageReceiver.receive + and MessageReceiver.receive_nowait are useless + because they will never return any messages. + """ + + threading.Thread.__init__(self) + self._request = request + self._queue = Queue.Queue() + self._onmessage = onmessage + self._stop_requested = False + self.setDaemon(True) + self.start() + + def run(self): + try: + while not self._stop_requested: + message = receive_message(self._request) + if self._onmessage: + self._onmessage(message) + else: + self._queue.put(message) + finally: + close_connection(self._request) + + def receive(self): + """ Receive a message from the channel, blocking. + + Returns: + message as a unicode string. + """ + return self._queue.get() + + def receive_nowait(self): + """ Receive a message from the channel, non-blocking. + + Returns: + message as a unicode string if available. None otherwise. + """ + try: + message = self._queue.get_nowait() + except Queue.Empty: + message = None + return message + + def stop(self): + """Request to stop this instance. + + The instance will be stopped after receiving the next message. + This method may not be very useful, but there is no clean way + in Python to forcefully stop a running thread. + """ + self._stop_requested = True + + +class MessageSender(threading.Thread): + """This class sends messages to the client. + + This class provides both synchronous and asynchronous ways to send + messages. + + Note: This class should not be used with the standalone server for wss + because pyOpenSSL used by the server raises a fatal error if the socket + is accessed from multiple threads. + """ + + def __init__(self, request): + """Construct an instance. + + Args: + request: mod_python request. + """ + threading.Thread.__init__(self) + self._request = request + self._queue = Queue.Queue() + self.setDaemon(True) + self.start() + + def run(self): + while True: + message, condition = self._queue.get() + condition.acquire() + send_message(self._request, message) + condition.notify() + condition.release() + + def send(self, message): + """Send a message, blocking.""" + + condition = threading.Condition() + condition.acquire() + self._queue.put((message, condition)) + condition.wait() + + def send_nowait(self, message): + """Send a message, non-blocking.""" + + self._queue.put((message, threading.Condition())) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/mux.py b/pyload/lib/mod_pywebsocket/mux.py new file mode 100644 index 000000000..f0bdd2461 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/mux.py @@ -0,0 +1,1636 @@ +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file provides classes and helper functions for multiplexing extension. + +Specification: +http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06 +""" + + +import collections +import copy +import email +import email.parser +import logging +import math +import struct +import threading +import traceback + +from mod_pywebsocket import common +from mod_pywebsocket import handshake +from mod_pywebsocket import util +from mod_pywebsocket._stream_base import BadOperationException +from mod_pywebsocket._stream_base import ConnectionTerminatedException +from mod_pywebsocket._stream_hybi import Frame +from mod_pywebsocket._stream_hybi import Stream +from mod_pywebsocket._stream_hybi import StreamOptions +from mod_pywebsocket._stream_hybi import create_binary_frame +from mod_pywebsocket._stream_hybi import create_closing_handshake_body +from mod_pywebsocket._stream_hybi import create_header +from mod_pywebsocket._stream_hybi import create_length_header +from mod_pywebsocket._stream_hybi import parse_frame +from mod_pywebsocket.handshake import hybi + + +_CONTROL_CHANNEL_ID = 0 +_DEFAULT_CHANNEL_ID = 1 + +_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0 +_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1 +_MUX_OPCODE_FLOW_CONTROL = 2 +_MUX_OPCODE_DROP_CHANNEL = 3 +_MUX_OPCODE_NEW_CHANNEL_SLOT = 4 + +_MAX_CHANNEL_ID = 2 ** 29 - 1 + +_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64 +_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024 + +_HANDSHAKE_ENCODING_IDENTITY = 0 +_HANDSHAKE_ENCODING_DELTA = 1 + +# We need only these status code for now. +_HTTP_BAD_RESPONSE_MESSAGES = { + common.HTTP_STATUS_BAD_REQUEST: 'Bad Request', +} + +# DropChannel reason code +# TODO(bashi): Define all reason code defined in -05 draft. +_DROP_CODE_NORMAL_CLOSURE = 1000 + +_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001 +_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002 +_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003 +_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004 +_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005 +_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006 +_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007 + +_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 3002 +_DROP_CODE_SEND_QUOTA_VIOLATION = 3005 +_DROP_CODE_ACKNOWLEDGED = 3008 + + +class MuxUnexpectedException(Exception): + """Exception in handling multiplexing extension.""" + pass + + +# Temporary +class MuxNotImplementedException(Exception): + """Raised when a flow enters unimplemented code path.""" + pass + + +class LogicalConnectionClosedException(Exception): + """Raised when logical connection is gracefully closed.""" + pass + + +class PhysicalConnectionError(Exception): + """Raised when there is a physical connection error.""" + def __init__(self, drop_code, message=''): + super(PhysicalConnectionError, self).__init__( + 'code=%d, message=%r' % (drop_code, message)) + self.drop_code = drop_code + self.message = message + + +class LogicalChannelError(Exception): + """Raised when there is a logical channel error.""" + def __init__(self, channel_id, drop_code, message=''): + super(LogicalChannelError, self).__init__( + 'channel_id=%d, code=%d, message=%r' % ( + channel_id, drop_code, message)) + self.channel_id = channel_id + self.drop_code = drop_code + self.message = message + + +def _encode_channel_id(channel_id): + if channel_id < 0: + raise ValueError('Channel id %d must not be negative' % channel_id) + + if channel_id < 2 ** 7: + return chr(channel_id) + if channel_id < 2 ** 14: + return struct.pack('!H', 0x8000 + channel_id) + if channel_id < 2 ** 21: + first = chr(0xc0 + (channel_id >> 16)) + return first + struct.pack('!H', channel_id & 0xffff) + if channel_id < 2 ** 29: + return struct.pack('!L', 0xe0000000 + channel_id) + + raise ValueError('Channel id %d is too large' % channel_id) + + +def _encode_number(number): + return create_length_header(number, False) + + +def _create_add_channel_response(channel_id, encoded_handshake, + encoding=0, rejected=False, + outer_frame_mask=False): + if encoding != 0 and encoding != 1: + raise ValueError('Invalid encoding %d' % encoding) + + first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) | + (rejected << 4) | encoding) + block = (chr(first_byte) + + _encode_channel_id(channel_id) + + _encode_number(len(encoded_handshake)) + + encoded_handshake) + payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block + return create_binary_frame(payload, mask=outer_frame_mask) + + +def _create_drop_channel(channel_id, code=None, message='', + outer_frame_mask=False): + if len(message) > 0 and code is None: + raise ValueError('Code must be specified if message is specified') + + first_byte = _MUX_OPCODE_DROP_CHANNEL << 5 + block = chr(first_byte) + _encode_channel_id(channel_id) + if code is None: + block += _encode_number(0) # Reason size + else: + reason = struct.pack('!H', code) + message + reason_size = _encode_number(len(reason)) + block += reason_size + reason + + payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block + return create_binary_frame(payload, mask=outer_frame_mask) + + +def _create_flow_control(channel_id, replenished_quota, + outer_frame_mask=False): + first_byte = _MUX_OPCODE_FLOW_CONTROL << 5 + block = (chr(first_byte) + + _encode_channel_id(channel_id) + + _encode_number(replenished_quota)) + payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block + return create_binary_frame(payload, mask=outer_frame_mask) + + +def _create_new_channel_slot(slots, send_quota, outer_frame_mask=False): + if slots < 0 or send_quota < 0: + raise ValueError('slots and send_quota must be non-negative.') + first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5 + block = (chr(first_byte) + + _encode_number(slots) + + _encode_number(send_quota)) + payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block + return create_binary_frame(payload, mask=outer_frame_mask) + + +def _create_fallback_new_channel_slot(outer_frame_mask=False): + first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag + block = (chr(first_byte) + _encode_number(0) + _encode_number(0)) + payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block + return create_binary_frame(payload, mask=outer_frame_mask) + + +def _parse_request_text(request_text): + request_line, header_lines = request_text.split('\r\n', 1) + + words = request_line.split(' ') + if len(words) != 3: + raise ValueError('Bad Request-Line syntax %r' % request_line) + [command, path, version] = words + if version != 'HTTP/1.1': + raise ValueError('Bad request version %r' % version) + + # email.parser.Parser() parses RFC 2822 (RFC 822) style headers. + # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers + # RFC 822. + headers = email.parser.Parser().parsestr(header_lines) + return command, path, version, headers + + +class _ControlBlock(object): + """A structure that holds parsing result of multiplexing control block. + Control block specific attributes will be added by _MuxFramePayloadParser. + (e.g. encoded_handshake will be added for AddChannelRequest and + AddChannelResponse) + """ + + def __init__(self, opcode): + self.opcode = opcode + + +class _MuxFramePayloadParser(object): + """A class that parses multiplexed frame payload.""" + + def __init__(self, payload): + self._data = payload + self._read_position = 0 + self._logger = util.get_class_logger(self) + + def read_channel_id(self): + """Reads channel id. + + Raises: + ValueError: when the payload doesn't contain + valid channel id. + """ + + remaining_length = len(self._data) - self._read_position + pos = self._read_position + if remaining_length == 0: + raise ValueError('Invalid channel id format') + + channel_id = ord(self._data[pos]) + channel_id_length = 1 + if channel_id & 0xe0 == 0xe0: + if remaining_length < 4: + raise ValueError('Invalid channel id format') + channel_id = struct.unpack('!L', + self._data[pos:pos+4])[0] & 0x1fffffff + channel_id_length = 4 + elif channel_id & 0xc0 == 0xc0: + if remaining_length < 3: + raise ValueError('Invalid channel id format') + channel_id = (((channel_id & 0x1f) << 16) + + struct.unpack('!H', self._data[pos+1:pos+3])[0]) + channel_id_length = 3 + elif channel_id & 0x80 == 0x80: + if remaining_length < 2: + raise ValueError('Invalid channel id format') + channel_id = struct.unpack('!H', + self._data[pos:pos+2])[0] & 0x3fff + channel_id_length = 2 + self._read_position += channel_id_length + + return channel_id + + def read_inner_frame(self): + """Reads an inner frame. + + Raises: + PhysicalConnectionError: when the inner frame is invalid. + """ + + if len(self._data) == self._read_position: + raise PhysicalConnectionError( + _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED) + + bits = ord(self._data[self._read_position]) + self._read_position += 1 + fin = (bits & 0x80) == 0x80 + rsv1 = (bits & 0x40) == 0x40 + rsv2 = (bits & 0x20) == 0x20 + rsv3 = (bits & 0x10) == 0x10 + opcode = bits & 0xf + payload = self.remaining_data() + # Consume rest of the message which is payload data of the original + # frame. + self._read_position = len(self._data) + return fin, rsv1, rsv2, rsv3, opcode, payload + + def _read_number(self): + if self._read_position + 1 > len(self._data): + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Cannot read the first byte of number field') + + number = ord(self._data[self._read_position]) + if number & 0x80 == 0x80: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'The most significant bit of the first byte of number should ' + 'be unset') + self._read_position += 1 + pos = self._read_position + if number == 127: + if pos + 8 > len(self._data): + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Invalid number field') + self._read_position += 8 + number = struct.unpack('!Q', self._data[pos:pos+8])[0] + if number > 0x7FFFFFFFFFFFFFFF: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Encoded number >= 2^63') + if number <= 0xFFFF: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + '%d should not be encoded by 9 bytes encoding' % number) + return number + if number == 126: + if pos + 2 > len(self._data): + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Invalid number field') + self._read_position += 2 + number = struct.unpack('!H', self._data[pos:pos+2])[0] + if number <= 125: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + '%d should not be encoded by 3 bytes encoding' % number) + return number + + def _read_size_and_contents(self): + """Reads data that consists of followings: + - the size of the contents encoded the same way as payload length + of the WebSocket Protocol with 1 bit padding at the head. + - the contents. + """ + + size = self._read_number() + pos = self._read_position + if pos + size > len(self._data): + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Cannot read %d bytes data' % size) + + self._read_position += size + return self._data[pos:pos+size] + + def _read_add_channel_request(self, first_byte, control_block): + reserved = (first_byte >> 2) & 0x7 + if reserved != 0: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Reserved bits must be unset') + + # Invalid encoding will be handled by MuxHandler. + encoding = first_byte & 0x3 + try: + control_block.channel_id = self.read_channel_id() + except ValueError, e: + raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) + control_block.encoding = encoding + encoded_handshake = self._read_size_and_contents() + control_block.encoded_handshake = encoded_handshake + return control_block + + def _read_add_channel_response(self, first_byte, control_block): + reserved = (first_byte >> 2) & 0x3 + if reserved != 0: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Reserved bits must be unset') + + control_block.accepted = (first_byte >> 4) & 1 + control_block.encoding = first_byte & 0x3 + try: + control_block.channel_id = self.read_channel_id() + except ValueError, e: + raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) + control_block.encoded_handshake = self._read_size_and_contents() + return control_block + + def _read_flow_control(self, first_byte, control_block): + reserved = first_byte & 0x1f + if reserved != 0: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Reserved bits must be unset') + + try: + control_block.channel_id = self.read_channel_id() + except ValueError, e: + raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) + control_block.send_quota = self._read_number() + return control_block + + def _read_drop_channel(self, first_byte, control_block): + reserved = first_byte & 0x1f + if reserved != 0: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Reserved bits must be unset') + + try: + control_block.channel_id = self.read_channel_id() + except ValueError, e: + raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) + reason = self._read_size_and_contents() + if len(reason) == 0: + control_block.drop_code = None + control_block.drop_message = '' + elif len(reason) >= 2: + control_block.drop_code = struct.unpack('!H', reason[:2])[0] + control_block.drop_message = reason[2:] + else: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Received DropChannel that conains only 1-byte reason') + return control_block + + def _read_new_channel_slot(self, first_byte, control_block): + reserved = first_byte & 0x1e + if reserved != 0: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Reserved bits must be unset') + control_block.fallback = first_byte & 1 + control_block.slots = self._read_number() + control_block.send_quota = self._read_number() + return control_block + + def read_control_blocks(self): + """Reads control block(s). + + Raises: + PhysicalConnectionError: when the payload contains invalid control + block(s). + StopIteration: when no control blocks left. + """ + + while self._read_position < len(self._data): + first_byte = ord(self._data[self._read_position]) + self._read_position += 1 + opcode = (first_byte >> 5) & 0x7 + control_block = _ControlBlock(opcode=opcode) + if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: + yield self._read_add_channel_request(first_byte, control_block) + elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: + yield self._read_add_channel_response( + first_byte, control_block) + elif opcode == _MUX_OPCODE_FLOW_CONTROL: + yield self._read_flow_control(first_byte, control_block) + elif opcode == _MUX_OPCODE_DROP_CHANNEL: + yield self._read_drop_channel(first_byte, control_block) + elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: + yield self._read_new_channel_slot(first_byte, control_block) + else: + raise PhysicalConnectionError( + _DROP_CODE_UNKNOWN_MUX_OPCODE, + 'Invalid opcode %d' % opcode) + + assert self._read_position == len(self._data) + raise StopIteration + + def remaining_data(self): + """Returns remaining data.""" + + return self._data[self._read_position:] + + +class _LogicalRequest(object): + """Mimics mod_python request.""" + + def __init__(self, channel_id, command, path, protocol, headers, + connection): + """Constructs an instance. + + Args: + channel_id: the channel id of the logical channel. + command: HTTP request command. + path: HTTP request path. + headers: HTTP headers. + connection: _LogicalConnection instance. + """ + + self.channel_id = channel_id + self.method = command + self.uri = path + self.protocol = protocol + self.headers_in = headers + self.connection = connection + self.server_terminated = False + self.client_terminated = False + + def is_https(self): + """Mimics request.is_https(). Returns False because this method is + used only by old protocols (hixie and hybi00). + """ + + return False + + +class _LogicalConnection(object): + """Mimics mod_python mp_conn.""" + + # For details, see the comment of set_read_state(). + STATE_ACTIVE = 1 + STATE_GRACEFULLY_CLOSED = 2 + STATE_TERMINATED = 3 + + def __init__(self, mux_handler, channel_id): + """Constructs an instance. + + Args: + mux_handler: _MuxHandler instance. + channel_id: channel id of this connection. + """ + + self._mux_handler = mux_handler + self._channel_id = channel_id + self._incoming_data = '' + self._write_condition = threading.Condition() + self._waiting_write_completion = False + self._read_condition = threading.Condition() + self._read_state = self.STATE_ACTIVE + + def get_local_addr(self): + """Getter to mimic mp_conn.local_addr.""" + + return self._mux_handler.physical_connection.get_local_addr() + local_addr = property(get_local_addr) + + def get_remote_addr(self): + """Getter to mimic mp_conn.remote_addr.""" + + return self._mux_handler.physical_connection.get_remote_addr() + remote_addr = property(get_remote_addr) + + def get_memorized_lines(self): + """Gets memorized lines. Not supported.""" + + raise MuxUnexpectedException('_LogicalConnection does not support ' + 'get_memorized_lines') + + def write(self, data): + """Writes data. mux_handler sends data asynchronously. The caller will + be suspended until write done. + + Args: + data: data to be written. + + Raises: + MuxUnexpectedException: when called before finishing the previous + write. + """ + + try: + self._write_condition.acquire() + if self._waiting_write_completion: + raise MuxUnexpectedException( + 'Logical connection %d is already waiting the completion ' + 'of write' % self._channel_id) + + self._waiting_write_completion = True + self._mux_handler.send_data(self._channel_id, data) + self._write_condition.wait() + finally: + self._write_condition.release() + + def write_control_data(self, data): + """Writes data via the control channel. Don't wait finishing write + because this method can be called by mux dispatcher. + + Args: + data: data to be written. + """ + + self._mux_handler.send_control_data(data) + + def notify_write_done(self): + """Called when sending data is completed.""" + + try: + self._write_condition.acquire() + if not self._waiting_write_completion: + raise MuxUnexpectedException( + 'Invalid call of notify_write_done for logical connection' + ' %d' % self._channel_id) + self._waiting_write_completion = False + self._write_condition.notify() + finally: + self._write_condition.release() + + def append_frame_data(self, frame_data): + """Appends incoming frame data. Called when mux_handler dispatches + frame data to the corresponding application. + + Args: + frame_data: incoming frame data. + """ + + self._read_condition.acquire() + self._incoming_data += frame_data + self._read_condition.notify() + self._read_condition.release() + + def read(self, length): + """Reads data. Blocks until enough data has arrived via physical + connection. + + Args: + length: length of data to be read. + Raises: + LogicalConnectionClosedException: when closing handshake for this + logical channel has been received. + ConnectionTerminatedException: when the physical connection has + closed, or an error is caused on the reader thread. + """ + + self._read_condition.acquire() + while (self._read_state == self.STATE_ACTIVE and + len(self._incoming_data) < length): + self._read_condition.wait() + + try: + if self._read_state == self.STATE_GRACEFULLY_CLOSED: + raise LogicalConnectionClosedException( + 'Logical channel %d has closed.' % self._channel_id) + elif self._read_state == self.STATE_TERMINATED: + raise ConnectionTerminatedException( + 'Receiving %d byte failed. Logical channel (%d) closed' % + (length, self._channel_id)) + + value = self._incoming_data[:length] + self._incoming_data = self._incoming_data[length:] + finally: + self._read_condition.release() + + return value + + def set_read_state(self, new_state): + """Sets the state of this connection. Called when an event for this + connection has occurred. + + Args: + new_state: state to be set. new_state must be one of followings: + - STATE_GRACEFULLY_CLOSED: when closing handshake for this + connection has been received. + - STATE_TERMINATED: when the physical connection has closed or + DropChannel of this connection has received. + """ + + self._read_condition.acquire() + self._read_state = new_state + self._read_condition.notify() + self._read_condition.release() + + +class _LogicalStream(Stream): + """Mimics the Stream class. This class interprets multiplexed WebSocket + frames. + """ + + def __init__(self, request, send_quota, receive_quota): + """Constructs an instance. + + Args: + request: _LogicalRequest instance. + send_quota: Initial send quota. + receive_quota: Initial receive quota. + """ + + # TODO(bashi): Support frame filters. + stream_options = StreamOptions() + # Physical stream is responsible for masking. + stream_options.unmask_receive = False + # Control frames can be fragmented on logical channel. + stream_options.allow_fragmented_control_frame = True + Stream.__init__(self, request, stream_options) + self._send_quota = send_quota + self._send_quota_condition = threading.Condition() + self._receive_quota = receive_quota + self._write_inner_frame_semaphore = threading.Semaphore() + + def _create_inner_frame(self, opcode, payload, end=True): + # TODO(bashi): Support extensions that use reserved bits. + first_byte = (end << 7) | opcode + return (_encode_channel_id(self._request.channel_id) + + chr(first_byte) + payload) + + def _write_inner_frame(self, opcode, payload, end=True): + payload_length = len(payload) + write_position = 0 + + try: + # An inner frame will be fragmented if there is no enough send + # quota. This semaphore ensures that fragmented inner frames are + # sent in order on the logical channel. + # Note that frames that come from other logical channels or + # multiplexing control blocks can be inserted between fragmented + # inner frames on the physical channel. + self._write_inner_frame_semaphore.acquire() + while write_position < payload_length: + try: + self._send_quota_condition.acquire() + while self._send_quota == 0: + self._logger.debug( + 'No quota. Waiting FlowControl message for %d.' % + self._request.channel_id) + self._send_quota_condition.wait() + + remaining = payload_length - write_position + write_length = min(self._send_quota, remaining) + inner_frame_end = ( + end and + (write_position + write_length == payload_length)) + + inner_frame = self._create_inner_frame( + opcode, + payload[write_position:write_position+write_length], + inner_frame_end) + frame_data = self._writer.build( + inner_frame, end=True, binary=True) + self._send_quota -= write_length + self._logger.debug('Consumed quota=%d, remaining=%d' % + (write_length, self._send_quota)) + finally: + self._send_quota_condition.release() + + # Writing data will block the worker so we need to release + # _send_quota_condition before writing. + self._logger.debug('Sending inner frame: %r' % frame_data) + self._request.connection.write(frame_data) + write_position += write_length + + opcode = common.OPCODE_CONTINUATION + + except ValueError, e: + raise BadOperationException(e) + finally: + self._write_inner_frame_semaphore.release() + + def replenish_send_quota(self, send_quota): + """Replenish send quota.""" + + self._send_quota_condition.acquire() + self._send_quota += send_quota + self._logger.debug('Replenished send quota for channel id %d: %d' % + (self._request.channel_id, self._send_quota)) + self._send_quota_condition.notify() + self._send_quota_condition.release() + + def consume_receive_quota(self, amount): + """Consumes receive quota. Returns False on failure.""" + + if self._receive_quota < amount: + self._logger.debug('Violate quota on channel id %d: %d < %d' % + (self._request.channel_id, + self._receive_quota, amount)) + return False + self._receive_quota -= amount + return True + + def send_message(self, message, end=True, binary=False): + """Override Stream.send_message.""" + + if self._request.server_terminated: + raise BadOperationException( + 'Requested send_message after sending out a closing handshake') + + if binary and isinstance(message, unicode): + raise BadOperationException( + 'Message for binary frame must be instance of str') + + if binary: + opcode = common.OPCODE_BINARY + else: + opcode = common.OPCODE_TEXT + message = message.encode('utf-8') + + self._write_inner_frame(opcode, message, end) + + def _receive_frame(self): + """Overrides Stream._receive_frame. + + In addition to call Stream._receive_frame, this method adds the amount + of payload to receiving quota and sends FlowControl to the client. + We need to do it here because Stream.receive_message() handles + control frames internally. + """ + + opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self) + amount = len(payload) + self._receive_quota += amount + frame_data = _create_flow_control(self._request.channel_id, + amount) + self._logger.debug('Sending flow control for %d, replenished=%d' % + (self._request.channel_id, amount)) + self._request.connection.write_control_data(frame_data) + return opcode, payload, fin, rsv1, rsv2, rsv3 + + def receive_message(self): + """Overrides Stream.receive_message.""" + + # Just call Stream.receive_message(), but catch + # LogicalConnectionClosedException, which is raised when the logical + # connection has closed gracefully. + try: + return Stream.receive_message(self) + except LogicalConnectionClosedException, e: + self._logger.debug('%s', e) + return None + + def _send_closing_handshake(self, code, reason): + """Overrides Stream._send_closing_handshake.""" + + body = create_closing_handshake_body(code, reason) + self._logger.debug('Sending closing handshake for %d: (%r, %r)' % + (self._request.channel_id, code, reason)) + self._write_inner_frame(common.OPCODE_CLOSE, body, end=True) + + self._request.server_terminated = True + + def send_ping(self, body=''): + """Overrides Stream.send_ping""" + + self._logger.debug('Sending ping on logical channel %d: %r' % + (self._request.channel_id, body)) + self._write_inner_frame(common.OPCODE_PING, body, end=True) + + self._ping_queue.append(body) + + def _send_pong(self, body): + """Overrides Stream._send_pong""" + + self._logger.debug('Sending pong on logical channel %d: %r' % + (self._request.channel_id, body)) + self._write_inner_frame(common.OPCODE_PONG, body, end=True) + + def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''): + """Overrides Stream.close_connection.""" + + # TODO(bashi): Implement + self._logger.debug('Closing logical connection %d' % + self._request.channel_id) + self._request.server_terminated = True + + def _drain_received_data(self): + """Overrides Stream._drain_received_data. Nothing need to be done for + logical channel. + """ + + pass + + +class _OutgoingData(object): + """A structure that holds data to be sent via physical connection and + origin of the data. + """ + + def __init__(self, channel_id, data): + self.channel_id = channel_id + self.data = data + + +class _PhysicalConnectionWriter(threading.Thread): + """A thread that is responsible for writing data to physical connection. + + TODO(bashi): Make sure there is no thread-safety problem when the reader + thread reads data from the same socket at a time. + """ + + def __init__(self, mux_handler): + """Constructs an instance. + + Args: + mux_handler: _MuxHandler instance. + """ + + threading.Thread.__init__(self) + self._logger = util.get_class_logger(self) + self._mux_handler = mux_handler + self.setDaemon(True) + self._stop_requested = False + self._deque = collections.deque() + self._deque_condition = threading.Condition() + + def put_outgoing_data(self, data): + """Puts outgoing data. + + Args: + data: _OutgoingData instance. + + Raises: + BadOperationException: when the thread has been requested to + terminate. + """ + + try: + self._deque_condition.acquire() + if self._stop_requested: + raise BadOperationException('Cannot write data anymore') + + self._deque.append(data) + self._deque_condition.notify() + finally: + self._deque_condition.release() + + def _write_data(self, outgoing_data): + try: + self._mux_handler.physical_connection.write(outgoing_data.data) + except Exception, e: + util.prepend_message_to_exception( + 'Failed to send message to %r: ' % + (self._mux_handler.physical_connection.remote_addr,), e) + raise + + # TODO(bashi): It would be better to block the thread that sends + # control data as well. + if outgoing_data.channel_id != _CONTROL_CHANNEL_ID: + self._mux_handler.notify_write_done(outgoing_data.channel_id) + + def run(self): + self._deque_condition.acquire() + while not self._stop_requested: + if len(self._deque) == 0: + self._deque_condition.wait() + continue + + outgoing_data = self._deque.popleft() + self._deque_condition.release() + self._write_data(outgoing_data) + self._deque_condition.acquire() + + # Flush deque + try: + while len(self._deque) > 0: + outgoing_data = self._deque.popleft() + self._write_data(outgoing_data) + finally: + self._deque_condition.release() + + def stop(self): + """Stops the writer thread.""" + + self._deque_condition.acquire() + self._stop_requested = True + self._deque_condition.notify() + self._deque_condition.release() + + +class _PhysicalConnectionReader(threading.Thread): + """A thread that is responsible for reading data from physical connection. + """ + + def __init__(self, mux_handler): + """Constructs an instance. + + Args: + mux_handler: _MuxHandler instance. + """ + + threading.Thread.__init__(self) + self._logger = util.get_class_logger(self) + self._mux_handler = mux_handler + self.setDaemon(True) + + def run(self): + while True: + try: + physical_stream = self._mux_handler.physical_stream + message = physical_stream.receive_message() + if message is None: + break + # Below happens only when a data message is received. + opcode = physical_stream.get_last_received_opcode() + if opcode != common.OPCODE_BINARY: + self._mux_handler.fail_physical_connection( + _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE, + 'Received a text message on physical connection') + break + + except ConnectionTerminatedException, e: + self._logger.debug('%s', e) + break + + try: + self._mux_handler.dispatch_message(message) + except PhysicalConnectionError, e: + self._mux_handler.fail_physical_connection( + e.drop_code, e.message) + break + except LogicalChannelError, e: + self._mux_handler.fail_logical_channel( + e.channel_id, e.drop_code, e.message) + except Exception, e: + self._logger.debug(traceback.format_exc()) + break + + self._mux_handler.notify_reader_done() + + +class _Worker(threading.Thread): + """A thread that is responsible for running the corresponding application + handler. + """ + + def __init__(self, mux_handler, request): + """Constructs an instance. + + Args: + mux_handler: _MuxHandler instance. + request: _LogicalRequest instance. + """ + + threading.Thread.__init__(self) + self._logger = util.get_class_logger(self) + self._mux_handler = mux_handler + self._request = request + self.setDaemon(True) + + def run(self): + self._logger.debug('Logical channel worker started. (id=%d)' % + self._request.channel_id) + try: + # Non-critical exceptions will be handled by dispatcher. + self._mux_handler.dispatcher.transfer_data(self._request) + finally: + self._mux_handler.notify_worker_done(self._request.channel_id) + + +class _MuxHandshaker(hybi.Handshaker): + """Opening handshake processor for multiplexing.""" + + _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ==' + + def __init__(self, request, dispatcher, send_quota, receive_quota): + """Constructs an instance. + Args: + request: _LogicalRequest instance. + dispatcher: Dispatcher instance (dispatch.Dispatcher). + send_quota: Initial send quota. + receive_quota: Initial receive quota. + """ + + hybi.Handshaker.__init__(self, request, dispatcher) + self._send_quota = send_quota + self._receive_quota = receive_quota + + # Append headers which should not be included in handshake field of + # AddChannelRequest. + # TODO(bashi): Make sure whether we should raise exception when + # these headers are included already. + request.headers_in[common.UPGRADE_HEADER] = ( + common.WEBSOCKET_UPGRADE_TYPE) + request.headers_in[common.CONNECTION_HEADER] = ( + common.UPGRADE_CONNECTION_TYPE) + request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = ( + str(common.VERSION_HYBI_LATEST)) + request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = ( + self._DUMMY_WEBSOCKET_KEY) + + def _create_stream(self, stream_options): + """Override hybi.Handshaker._create_stream.""" + + self._logger.debug('Creating logical stream for %d' % + self._request.channel_id) + return _LogicalStream(self._request, self._send_quota, + self._receive_quota) + + def _create_handshake_response(self, accept): + """Override hybi._create_handshake_response.""" + + response = [] + + response.append('HTTP/1.1 101 Switching Protocols\r\n') + + # Upgrade, Connection and Sec-WebSocket-Accept should be excluded. + if self._request.ws_protocol is not None: + response.append('%s: %s\r\n' % ( + common.SEC_WEBSOCKET_PROTOCOL_HEADER, + self._request.ws_protocol)) + if (self._request.ws_extensions is not None and + len(self._request.ws_extensions) != 0): + response.append('%s: %s\r\n' % ( + common.SEC_WEBSOCKET_EXTENSIONS_HEADER, + common.format_extensions(self._request.ws_extensions))) + response.append('\r\n') + + return ''.join(response) + + def _send_handshake(self, accept): + """Override hybi.Handshaker._send_handshake.""" + + # Don't send handshake response for the default channel + if self._request.channel_id == _DEFAULT_CHANNEL_ID: + return + + handshake_response = self._create_handshake_response(accept) + frame_data = _create_add_channel_response( + self._request.channel_id, + handshake_response) + self._logger.debug('Sending handshake response for %d: %r' % + (self._request.channel_id, frame_data)) + self._request.connection.write_control_data(frame_data) + + +class _LogicalChannelData(object): + """A structure that holds information about logical channel. + """ + + def __init__(self, request, worker): + self.request = request + self.worker = worker + self.drop_code = _DROP_CODE_NORMAL_CLOSURE + self.drop_message = '' + + +class _HandshakeDeltaBase(object): + """A class that holds information for delta-encoded handshake.""" + + def __init__(self, headers): + self._headers = headers + + def create_headers(self, delta=None): + """Creates request headers for an AddChannelRequest that has + delta-encoded handshake. + + Args: + delta: headers should be overridden. + """ + + headers = copy.copy(self._headers) + if delta: + for key, value in delta.items(): + # The spec requires that a header with an empty value is + # removed from the delta base. + if len(value) == 0 and headers.has_key(key): + del headers[key] + else: + headers[key] = value + # TODO(bashi): Support extensions + headers['Sec-WebSocket-Extensions'] = '' + return headers + + +class _MuxHandler(object): + """Multiplexing handler. When a handler starts, it launches three + threads; the reader thread, the writer thread, and a worker thread. + + The reader thread reads data from the physical stream, i.e., the + ws_stream object of the underlying websocket connection. The reader + thread interprets multiplexed frames and dispatches them to logical + channels. Methods of this class are mostly called by the reader thread. + + The writer thread sends multiplexed frames which are created by + logical channels via the physical connection. + + The worker thread launched at the starting point handles the + "Implicitly Opened Connection". If multiplexing handler receives + an AddChannelRequest and accepts it, the handler will launch a new worker + thread and dispatch the request to it. + """ + + def __init__(self, request, dispatcher): + """Constructs an instance. + + Args: + request: mod_python request of the physical connection. + dispatcher: Dispatcher instance (dispatch.Dispatcher). + """ + + self.original_request = request + self.dispatcher = dispatcher + self.physical_connection = request.connection + self.physical_stream = request.ws_stream + self._logger = util.get_class_logger(self) + self._logical_channels = {} + self._logical_channels_condition = threading.Condition() + # Holds client's initial quota + self._channel_slots = collections.deque() + self._handshake_base = None + self._worker_done_notify_received = False + self._reader = None + self._writer = None + + def start(self): + """Starts the handler. + + Raises: + MuxUnexpectedException: when the handler already started, or when + opening handshake of the default channel fails. + """ + + if self._reader or self._writer: + raise MuxUnexpectedException('MuxHandler already started') + + self._reader = _PhysicalConnectionReader(self) + self._writer = _PhysicalConnectionWriter(self) + self._reader.start() + self._writer.start() + + # Create "Implicitly Opened Connection". + logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID) + self._handshake_base = _HandshakeDeltaBase( + self.original_request.headers_in) + logical_request = _LogicalRequest( + _DEFAULT_CHANNEL_ID, + self.original_request.method, + self.original_request.uri, + self.original_request.protocol, + self._handshake_base.create_headers(), + logical_connection) + # Client's send quota for the implicitly opened connection is zero, + # but we will send FlowControl later so set the initial quota to + # _INITIAL_QUOTA_FOR_CLIENT. + self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT) + if not self._do_handshake_for_logical_request( + logical_request, send_quota=self.original_request.mux_quota): + raise MuxUnexpectedException( + 'Failed handshake on the default channel id') + self._add_logical_channel(logical_request) + + # Send FlowControl for the implicitly opened connection. + frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID, + _INITIAL_QUOTA_FOR_CLIENT) + logical_request.connection.write_control_data(frame_data) + + def add_channel_slots(self, slots, send_quota): + """Adds channel slots. + + Args: + slots: number of slots to be added. + send_quota: initial send quota for slots. + """ + + self._channel_slots.extend([send_quota] * slots) + # Send NewChannelSlot to client. + frame_data = _create_new_channel_slot(slots, send_quota) + self.send_control_data(frame_data) + + def wait_until_done(self, timeout=None): + """Waits until all workers are done. Returns False when timeout has + occurred. Returns True on success. + + Args: + timeout: timeout in sec. + """ + + self._logical_channels_condition.acquire() + try: + while len(self._logical_channels) > 0: + self._logger.debug('Waiting workers(%d)...' % + len(self._logical_channels)) + self._worker_done_notify_received = False + self._logical_channels_condition.wait(timeout) + if not self._worker_done_notify_received: + self._logger.debug('Waiting worker(s) timed out') + return False + + finally: + self._logical_channels_condition.release() + + # Flush pending outgoing data + self._writer.stop() + self._writer.join() + + return True + + def notify_write_done(self, channel_id): + """Called by the writer thread when a write operation has done. + + Args: + channel_id: objective channel id. + """ + + try: + self._logical_channels_condition.acquire() + if channel_id in self._logical_channels: + channel_data = self._logical_channels[channel_id] + channel_data.request.connection.notify_write_done() + else: + self._logger.debug('Seems that logical channel for %d has gone' + % channel_id) + finally: + self._logical_channels_condition.release() + + def send_control_data(self, data): + """Sends data via the control channel. + + Args: + data: data to be sent. + """ + + self._writer.put_outgoing_data(_OutgoingData( + channel_id=_CONTROL_CHANNEL_ID, data=data)) + + def send_data(self, channel_id, data): + """Sends data via given logical channel. This method is called by + worker threads. + + Args: + channel_id: objective channel id. + data: data to be sent. + """ + + self._writer.put_outgoing_data(_OutgoingData( + channel_id=channel_id, data=data)) + + def _send_drop_channel(self, channel_id, code=None, message=''): + frame_data = _create_drop_channel(channel_id, code, message) + self._logger.debug( + 'Sending drop channel for channel id %d' % channel_id) + self.send_control_data(frame_data) + + def _send_error_add_channel_response(self, channel_id, status=None): + if status is None: + status = common.HTTP_STATUS_BAD_REQUEST + + if status in _HTTP_BAD_RESPONSE_MESSAGES: + message = _HTTP_BAD_RESPONSE_MESSAGES[status] + else: + self._logger.debug('Response message for %d is not found' % status) + message = '???' + + response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message) + frame_data = _create_add_channel_response(channel_id, + encoded_handshake=response, + encoding=0, rejected=True) + self.send_control_data(frame_data) + + def _create_logical_request(self, block): + if block.channel_id == _CONTROL_CHANNEL_ID: + # TODO(bashi): Raise PhysicalConnectionError with code 2006 + # instead of MuxUnexpectedException. + raise MuxUnexpectedException( + 'Received the control channel id (0) as objective channel ' + 'id for AddChannel') + + if block.encoding > _HANDSHAKE_ENCODING_DELTA: + raise PhysicalConnectionError( + _DROP_CODE_UNKNOWN_REQUEST_ENCODING) + + method, path, version, headers = _parse_request_text( + block.encoded_handshake) + if block.encoding == _HANDSHAKE_ENCODING_DELTA: + headers = self._handshake_base.create_headers(headers) + + connection = _LogicalConnection(self, block.channel_id) + request = _LogicalRequest(block.channel_id, method, path, version, + headers, connection) + return request + + def _do_handshake_for_logical_request(self, request, send_quota=0): + try: + receive_quota = self._channel_slots.popleft() + except IndexError: + raise LogicalChannelError( + request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION) + + handshaker = _MuxHandshaker(request, self.dispatcher, + send_quota, receive_quota) + try: + handshaker.do_handshake() + except handshake.VersionException, e: + self._logger.info('%s', e) + self._send_error_add_channel_response( + request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) + return False + except handshake.HandshakeException, e: + # TODO(bashi): Should we _Fail the Logical Channel_ with 3001 + # instead? + self._logger.info('%s', e) + self._send_error_add_channel_response(request.channel_id, + status=e.status) + return False + except handshake.AbortedByUserException, e: + self._logger.info('%s', e) + self._send_error_add_channel_response(request.channel_id) + return False + + return True + + def _add_logical_channel(self, logical_request): + try: + self._logical_channels_condition.acquire() + if logical_request.channel_id in self._logical_channels: + self._logger.debug('Channel id %d already exists' % + logical_request.channel_id) + raise PhysicalConnectionError( + _DROP_CODE_CHANNEL_ALREADY_EXISTS, + 'Channel id %d already exists' % + logical_request.channel_id) + worker = _Worker(self, logical_request) + channel_data = _LogicalChannelData(logical_request, worker) + self._logical_channels[logical_request.channel_id] = channel_data + worker.start() + finally: + self._logical_channels_condition.release() + + def _process_add_channel_request(self, block): + try: + logical_request = self._create_logical_request(block) + except ValueError, e: + self._logger.debug('Failed to create logical request: %r' % e) + self._send_error_add_channel_response( + block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) + return + if self._do_handshake_for_logical_request(logical_request): + if block.encoding == _HANDSHAKE_ENCODING_IDENTITY: + # Update handshake base. + # TODO(bashi): Make sure this is the right place to update + # handshake base. + self._handshake_base = _HandshakeDeltaBase( + logical_request.headers_in) + self._add_logical_channel(logical_request) + else: + self._send_error_add_channel_response( + block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) + + def _process_flow_control(self, block): + try: + self._logical_channels_condition.acquire() + if not block.channel_id in self._logical_channels: + return + channel_data = self._logical_channels[block.channel_id] + channel_data.request.ws_stream.replenish_send_quota( + block.send_quota) + finally: + self._logical_channels_condition.release() + + def _process_drop_channel(self, block): + self._logger.debug( + 'DropChannel received for %d: code=%r, reason=%r' % + (block.channel_id, block.drop_code, block.drop_message)) + try: + self._logical_channels_condition.acquire() + if not block.channel_id in self._logical_channels: + return + channel_data = self._logical_channels[block.channel_id] + channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED + # Close the logical channel + channel_data.request.connection.set_read_state( + _LogicalConnection.STATE_TERMINATED) + finally: + self._logical_channels_condition.release() + + def _process_control_blocks(self, parser): + for control_block in parser.read_control_blocks(): + opcode = control_block.opcode + self._logger.debug('control block received, opcode: %d' % opcode) + if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: + self._process_add_channel_request(control_block) + elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Received AddChannelResponse') + elif opcode == _MUX_OPCODE_FLOW_CONTROL: + self._process_flow_control(control_block) + elif opcode == _MUX_OPCODE_DROP_CHANNEL: + self._process_drop_channel(control_block) + elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: + raise PhysicalConnectionError( + _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, + 'Received NewChannelSlot') + else: + raise MuxUnexpectedException( + 'Unexpected opcode %r' % opcode) + + def _process_logical_frame(self, channel_id, parser): + self._logger.debug('Received a frame. channel id=%d' % channel_id) + try: + self._logical_channels_condition.acquire() + if not channel_id in self._logical_channels: + # We must ignore the message for an inactive channel. + return + channel_data = self._logical_channels[channel_id] + fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame() + if not channel_data.request.ws_stream.consume_receive_quota( + len(payload)): + # The client violates quota. Close logical channel. + raise LogicalChannelError( + channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION) + header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3, + mask=False) + frame_data = header + payload + channel_data.request.connection.append_frame_data(frame_data) + finally: + self._logical_channels_condition.release() + + def dispatch_message(self, message): + """Dispatches message. The reader thread calls this method. + + Args: + message: a message that contains encapsulated frame. + Raises: + PhysicalConnectionError: if the message contains physical + connection level errors. + LogicalChannelError: if the message contains logical channel + level errors. + """ + + parser = _MuxFramePayloadParser(message) + try: + channel_id = parser.read_channel_id() + except ValueError, e: + raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED) + if channel_id == _CONTROL_CHANNEL_ID: + self._process_control_blocks(parser) + else: + self._process_logical_frame(channel_id, parser) + + def notify_worker_done(self, channel_id): + """Called when a worker has finished. + + Args: + channel_id: channel id corresponded with the worker. + """ + + self._logger.debug('Worker for channel id %d terminated' % channel_id) + try: + self._logical_channels_condition.acquire() + if not channel_id in self._logical_channels: + raise MuxUnexpectedException( + 'Channel id %d not found' % channel_id) + channel_data = self._logical_channels.pop(channel_id) + finally: + self._worker_done_notify_received = True + self._logical_channels_condition.notify() + self._logical_channels_condition.release() + + if not channel_data.request.server_terminated: + self._send_drop_channel( + channel_id, code=channel_data.drop_code, + message=channel_data.drop_message) + + def notify_reader_done(self): + """This method is called by the reader thread when the reader has + finished. + """ + + # Terminate all logical connections + self._logger.debug('termiating all logical connections...') + self._logical_channels_condition.acquire() + for channel_data in self._logical_channels.values(): + try: + channel_data.request.connection.set_read_state( + _LogicalConnection.STATE_TERMINATED) + except Exception: + pass + self._logical_channels_condition.release() + + def fail_physical_connection(self, code, message): + """Fail the physical connection. + + Args: + code: drop reason code. + message: drop message. + """ + + self._logger.debug('Failing the physical connection...') + self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message) + self.physical_stream.close_connection( + common.STATUS_INTERNAL_ENDPOINT_ERROR) + + def fail_logical_channel(self, channel_id, code, message): + """Fail a logical channel. + + Args: + channel_id: channel id. + code: drop reason code. + message: drop message. + """ + + self._logger.debug('Failing logical channel %d...' % channel_id) + try: + self._logical_channels_condition.acquire() + if channel_id in self._logical_channels: + channel_data = self._logical_channels[channel_id] + # Close the logical channel. notify_worker_done() will be + # called later and it will send DropChannel. + channel_data.drop_code = code + channel_data.drop_message = message + channel_data.request.connection.set_read_state( + _LogicalConnection.STATE_TERMINATED) + else: + self._send_drop_channel(channel_id, code, message) + finally: + self._logical_channels_condition.release() + + +def use_mux(request): + return hasattr(request, 'mux') and request.mux + + +def start(request, dispatcher): + mux_handler = _MuxHandler(request, dispatcher) + mux_handler.start() + + mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS, + _INITIAL_QUOTA_FOR_CLIENT) + + mux_handler.wait_until_done() + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/standalone.py b/pyload/lib/mod_pywebsocket/standalone.py new file mode 100755 index 000000000..07a33d9c9 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/standalone.py @@ -0,0 +1,998 @@ +#!/usr/bin/env python +# +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Standalone WebSocket server. + +Use this file to launch pywebsocket without Apache HTTP Server. + + +BASIC USAGE + +Go to the src directory and run + + $ python mod_pywebsocket/standalone.py [-p <ws_port>] + [-w <websock_handlers>] + [-d <document_root>] + +<ws_port> is the port number to use for ws:// connection. + +<document_root> is the path to the root directory of HTML files. + +<websock_handlers> is the path to the root directory of WebSocket handlers. +If not specified, <document_root> will be used. See __init__.py (or +run $ pydoc mod_pywebsocket) for how to write WebSocket handlers. + +For more detail and other options, run + + $ python mod_pywebsocket/standalone.py --help + +or see _build_option_parser method below. + +For trouble shooting, adding "--log_level debug" might help you. + + +TRY DEMO + +Go to the src directory and run + + $ python standalone.py -d example + +to launch pywebsocket with the sample handler and html on port 80. Open +http://localhost/console.html, click the connect button, type something into +the text box next to the send button and click the send button. If everything +is working, you'll see the message you typed echoed by the server. + + +SUPPORTING TLS + +To support TLS, run standalone.py with -t, -k, and -c options. + + +SUPPORTING CLIENT AUTHENTICATION + +To support client authentication with TLS, run standalone.py with -t, -k, -c, +and --tls-client-auth, and --tls-client-ca options. + +E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k +../test/cert/key.pem --tls-client-auth --tls-client-ca=../test/cert/cacert.pem + + +CONFIGURATION FILE + +You can also write a configuration file and use it by specifying the path to +the configuration file by --config option. Please write a configuration file +following the documentation of the Python ConfigParser library. Name of each +entry must be the long version argument name. E.g. to set log level to debug, +add the following line: + +log_level=debug + +For options which doesn't take value, please add some fake value. E.g. for +--tls option, add the following line: + +tls=True + +Note that tls will be enabled even if you write tls=False as the value part is +fake. + +When both a command line argument and a configuration file entry are set for +the same configuration item, the command line value will override one in the +configuration file. + + +THREADING + +This server is derived from SocketServer.ThreadingMixIn. Hence a thread is +used for each request. + + +SECURITY WARNING + +This uses CGIHTTPServer and CGIHTTPServer is not secure. +It may execute arbitrary Python code or external programs. It should not be +used outside a firewall. +""" + +import BaseHTTPServer +import CGIHTTPServer +import SimpleHTTPServer +import SocketServer +import ConfigParser +import base64 +import httplib +import logging +import logging.handlers +import optparse +import os +import re +import select +import socket +import sys +import threading +import time + +_HAS_SSL = False +_HAS_OPEN_SSL = False +try: + import ssl + _HAS_SSL = True +except ImportError: + try: + import OpenSSL.SSL + _HAS_OPEN_SSL = True + except ImportError: + pass + +from mod_pywebsocket import common +from mod_pywebsocket import dispatch +from mod_pywebsocket import handshake +from mod_pywebsocket import http_header_util +from mod_pywebsocket import memorizingfile +from mod_pywebsocket import util + + +_DEFAULT_LOG_MAX_BYTES = 1024 * 256 +_DEFAULT_LOG_BACKUP_COUNT = 5 + +_DEFAULT_REQUEST_QUEUE_SIZE = 128 + +# 1024 is practically large enough to contain WebSocket handshake lines. +_MAX_MEMORIZED_LINES = 1024 + + +class _StandaloneConnection(object): + """Mimic mod_python mp_conn.""" + + def __init__(self, request_handler): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._request_handler = request_handler + + def get_local_addr(self): + """Getter to mimic mp_conn.local_addr.""" + + return (self._request_handler.server.server_name, + self._request_handler.server.server_port) + local_addr = property(get_local_addr) + + def get_remote_addr(self): + """Getter to mimic mp_conn.remote_addr. + + Setting the property in __init__ won't work because the request + handler is not initialized yet there.""" + + return self._request_handler.client_address + remote_addr = property(get_remote_addr) + + def write(self, data): + """Mimic mp_conn.write().""" + + return self._request_handler.wfile.write(data) + + def read(self, length): + """Mimic mp_conn.read().""" + + return self._request_handler.rfile.read(length) + + def get_memorized_lines(self): + """Get memorized lines.""" + + return self._request_handler.rfile.get_memorized_lines() + + +class _StandaloneRequest(object): + """Mimic mod_python request.""" + + def __init__(self, request_handler, use_tls): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._logger = util.get_class_logger(self) + + self._request_handler = request_handler + self.connection = _StandaloneConnection(request_handler) + self._use_tls = use_tls + self.headers_in = request_handler.headers + + def get_uri(self): + """Getter to mimic request.uri.""" + + return self._request_handler.path + uri = property(get_uri) + + def get_method(self): + """Getter to mimic request.method.""" + + return self._request_handler.command + method = property(get_method) + + def get_protocol(self): + """Getter to mimic request.protocol.""" + + return self._request_handler.request_version + protocol = property(get_protocol) + + def is_https(self): + """Mimic request.is_https().""" + + return self._use_tls + + def _drain_received_data(self): + """Don't use this method from WebSocket handler. Drains unread data + in the receive buffer. + """ + + raw_socket = self._request_handler.connection + drained_data = util.drain_received_data(raw_socket) + + if drained_data: + self._logger.debug( + 'Drained data following close frame: %r', drained_data) + + +class _StandaloneSSLConnection(object): + """A wrapper class for OpenSSL.SSL.Connection to provide makefile method + which is not supported by the class. + """ + + def __init__(self, connection): + self._connection = connection + + def __getattribute__(self, name): + if name in ('_connection', 'makefile'): + return object.__getattribute__(self, name) + return self._connection.__getattribute__(name) + + def __setattr__(self, name, value): + if name in ('_connection', 'makefile'): + return object.__setattr__(self, name, value) + return self._connection.__setattr__(name, value) + + def makefile(self, mode='r', bufsize=-1): + return socket._fileobject(self._connection, mode, bufsize) + + +def _alias_handlers(dispatcher, websock_handlers_map_file): + """Set aliases specified in websock_handler_map_file in dispatcher. + + Args: + dispatcher: dispatch.Dispatcher instance + websock_handler_map_file: alias map file + """ + + fp = open(websock_handlers_map_file) + try: + for line in fp: + if line[0] == '#' or line.isspace(): + continue + m = re.match('(\S+)\s+(\S+)', line) + if not m: + logging.warning('Wrong format in map file:' + line) + continue + try: + dispatcher.add_resource_path_alias( + m.group(1), m.group(2)) + except dispatch.DispatchException, e: + logging.error(str(e)) + finally: + fp.close() + + +class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + """HTTPServer specialized for WebSocket.""" + + # Overrides SocketServer.ThreadingMixIn.daemon_threads + daemon_threads = True + # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address + allow_reuse_address = True + + def __init__(self, options): + """Override SocketServer.TCPServer.__init__ to set SSL enabled + socket object to self.socket before server_bind and server_activate, + if necessary. + """ + + # Share a Dispatcher among request handlers to save time for + # instantiation. Dispatcher can be shared because it is thread-safe. + options.dispatcher = dispatch.Dispatcher( + options.websock_handlers, + options.scan_dir, + options.allow_handlers_outside_root_dir) + if options.websock_handlers_map_file: + _alias_handlers(options.dispatcher, + options.websock_handlers_map_file) + warnings = options.dispatcher.source_warnings() + if warnings: + for warning in warnings: + logging.warning('mod_pywebsocket: %s' % warning) + + self._logger = util.get_class_logger(self) + + self.request_queue_size = options.request_queue_size + self.__ws_is_shut_down = threading.Event() + self.__ws_serving = False + + SocketServer.BaseServer.__init__( + self, (options.server_host, options.port), WebSocketRequestHandler) + + # Expose the options object to allow handler objects access it. We name + # it with websocket_ prefix to avoid conflict. + self.websocket_server_options = options + + self._create_sockets() + self.server_bind() + self.server_activate() + + def _create_sockets(self): + self.server_name, self.server_port = self.server_address + self._sockets = [] + if not self.server_name: + # On platforms that doesn't support IPv6, the first bind fails. + # On platforms that supports IPv6 + # - If it binds both IPv4 and IPv6 on call with AF_INET6, the + # first bind succeeds and the second fails (we'll see 'Address + # already in use' error). + # - If it binds only IPv6 on call with AF_INET6, both call are + # expected to succeed to listen both protocol. + addrinfo_array = [ + (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''), + (socket.AF_INET, socket.SOCK_STREAM, '', '', '')] + else: + addrinfo_array = socket.getaddrinfo(self.server_name, + self.server_port, + socket.AF_UNSPEC, + socket.SOCK_STREAM, + socket.IPPROTO_TCP) + for addrinfo in addrinfo_array: + self._logger.info('Create socket on: %r', addrinfo) + family, socktype, proto, canonname, sockaddr = addrinfo + try: + socket_ = socket.socket(family, socktype) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + continue + if self.websocket_server_options.use_tls: + if _HAS_SSL: + if self.websocket_server_options.tls_client_auth: + client_cert_ = ssl.CERT_REQUIRED + else: + client_cert_ = ssl.CERT_NONE + socket_ = ssl.wrap_socket(socket_, + keyfile=self.websocket_server_options.private_key, + certfile=self.websocket_server_options.certificate, + ssl_version=ssl.PROTOCOL_SSLv23, + ca_certs=self.websocket_server_options.tls_client_ca, + cert_reqs=client_cert_) + if _HAS_OPEN_SSL: + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + ctx.use_privatekey_file( + self.websocket_server_options.private_key) + ctx.use_certificate_file( + self.websocket_server_options.certificate) + socket_ = OpenSSL.SSL.Connection(ctx, socket_) + self._sockets.append((socket_, addrinfo)) + + def server_bind(self): + """Override SocketServer.TCPServer.server_bind to enable multiple + sockets bind. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Bind on: %r', addrinfo) + if self.allow_reuse_address: + socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + socket_.bind(self.server_address) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + if self.server_address[1] == 0: + # The operating system assigns the actual port number for port + # number 0. This case, the second and later sockets should use + # the same port number. Also self.server_port is rewritten + # because it is exported, and will be used by external code. + self.server_address = ( + self.server_name, socket_.getsockname()[1]) + self.server_port = self.server_address[1] + self._logger.info('Port %r is assigned', self.server_port) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + def server_activate(self): + """Override SocketServer.TCPServer.server_activate to enable multiple + sockets listen. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Listen on: %r', addrinfo) + try: + socket_.listen(self.request_queue_size) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + if len(self._sockets) == 0: + self._logger.critical( + 'No sockets activated. Use info log level to see the reason.') + + def server_close(self): + """Override SocketServer.TCPServer.server_close to enable multiple + sockets close. + """ + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Close on: %r', addrinfo) + socket_.close() + + def fileno(self): + """Override SocketServer.TCPServer.fileno.""" + + self._logger.critical('Not supported: fileno') + return self._sockets[0][0].fileno() + + def handle_error(self, rquest, client_address): + """Override SocketServer.handle_error.""" + + self._logger.error( + 'Exception in processing request from: %r\n%s', + client_address, + util.get_stack_trace()) + # Note: client_address is a tuple. + + def get_request(self): + """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection + object with _StandaloneSSLConnection to provide makefile method. We + cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly + attribute. + """ + + accepted_socket, client_address = self.socket.accept() + if self.websocket_server_options.use_tls and _HAS_OPEN_SSL: + accepted_socket = _StandaloneSSLConnection(accepted_socket) + return accepted_socket, client_address + + def serve_forever(self, poll_interval=0.5): + """Override SocketServer.BaseServer.serve_forever.""" + + self.__ws_serving = True + self.__ws_is_shut_down.clear() + handle_request = self.handle_request + if hasattr(self, '_handle_request_noblock'): + handle_request = self._handle_request_noblock + else: + self._logger.warning('Fallback to blocking request handler') + try: + while self.__ws_serving: + r, w, e = select.select( + [socket_[0] for socket_ in self._sockets], + [], [], poll_interval) + for socket_ in r: + self.socket = socket_ + handle_request() + self.socket = None + finally: + self.__ws_is_shut_down.set() + + def shutdown(self): + """Override SocketServer.BaseServer.shutdown.""" + + self.__ws_serving = False + self.__ws_is_shut_down.wait() + + +class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): + """CGIHTTPRequestHandler specialized for WebSocket.""" + + # Use httplib.HTTPMessage instead of mimetools.Message. + MessageClass = httplib.HTTPMessage + + def setup(self): + """Override SocketServer.StreamRequestHandler.setup to wrap rfile + with MemorizingFile. + + This method will be called by BaseRequestHandler's constructor + before calling BaseHTTPRequestHandler.handle. + BaseHTTPRequestHandler.handle will call + BaseHTTPRequestHandler.handle_one_request and it will call + WebSocketRequestHandler.parse_request. + """ + + # Call superclass's setup to prepare rfile, wfile, etc. See setup + # definition on the root class SocketServer.StreamRequestHandler to + # understand what this does. + CGIHTTPServer.CGIHTTPRequestHandler.setup(self) + + self.rfile = memorizingfile.MemorizingFile( + self.rfile, + max_memorized_lines=_MAX_MEMORIZED_LINES) + + def __init__(self, request, client_address, server): + self._logger = util.get_class_logger(self) + + self._options = server.websocket_server_options + + # Overrides CGIHTTPServerRequestHandler.cgi_directories. + self.cgi_directories = self._options.cgi_directories + # Replace CGIHTTPRequestHandler.is_executable method. + if self._options.is_executable_method is not None: + self.is_executable = self._options.is_executable_method + + # This actually calls BaseRequestHandler.__init__. + CGIHTTPServer.CGIHTTPRequestHandler.__init__( + self, request, client_address, server) + + def parse_request(self): + """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. + + Return True to continue processing for HTTP(S), False otherwise. + + See BaseHTTPRequestHandler.handle_one_request method which calls + this method to understand how the return value will be handled. + """ + + # We hook parse_request method, but also call the original + # CGIHTTPRequestHandler.parse_request since when we return False, + # CGIHTTPRequestHandler.handle_one_request continues processing and + # it needs variables set by CGIHTTPRequestHandler.parse_request. + # + # Variables set by this method will be also used by WebSocket request + # handling (self.path, self.command, self.requestline, etc. See also + # how _StandaloneRequest's members are implemented using these + # attributes). + if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self): + return False + + if self._options.use_basic_auth: + auth = self.headers.getheader('Authorization') + if auth != self._options.basic_auth_credential: + self.send_response(401) + self.send_header('WWW-Authenticate', + 'Basic realm="Pywebsocket"') + self.end_headers() + self._logger.info('Request basic authentication') + return True + + host, port, resource = http_header_util.parse_uri(self.path) + if resource is None: + self._logger.info('Invalid URI: %r', self.path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + server_options = self.server.websocket_server_options + if host is not None: + validation_host = server_options.validation_host + if validation_host is not None and host != validation_host: + self._logger.info('Invalid host: %r (expected: %r)', + host, + validation_host) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + if port is not None: + validation_port = server_options.validation_port + if validation_port is not None and port != validation_port: + self._logger.info('Invalid port: %r (expected: %r)', + port, + validation_port) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + self.path = resource + + request = _StandaloneRequest(self, self._options.use_tls) + + try: + # Fallback to default http handler for request paths for which + # we don't have request handlers. + if not self._options.dispatcher.get_handler_suite(self.path): + self._logger.info('No handler for resource: %r', + self.path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + except dispatch.DispatchException, e: + self._logger.info('%s', e) + self.send_error(e.status) + return False + + # If any Exceptions without except clause setup (including + # DispatchException) is raised below this point, it will be caught + # and logged by WebSocketServer. + + try: + try: + handshake.do_handshake( + request, + self._options.dispatcher, + allowDraft75=self._options.allow_draft75, + strict=self._options.strict) + except handshake.VersionException, e: + self._logger.info('%s', e) + self.send_response(common.HTTP_STATUS_BAD_REQUEST) + self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER, + e.supported_versions) + self.end_headers() + return False + except handshake.HandshakeException, e: + # Handshake for ws(s) failed. + self._logger.info('%s', e) + self.send_error(e.status) + return False + + request._dispatcher = self._options.dispatcher + self._options.dispatcher.transfer_data(request) + except handshake.AbortedByUserException, e: + self._logger.info('%s', e) + return False + + def log_request(self, code='-', size='-'): + """Override BaseHTTPServer.log_request.""" + + self._logger.info('"%s" %s %s', + self.requestline, str(code), str(size)) + + def log_error(self, *args): + """Override BaseHTTPServer.log_error.""" + + # Despite the name, this method is for warnings than for errors. + # For example, HTTP status code is logged by this method. + self._logger.warning('%s - %s', + self.address_string(), + args[0] % args[1:]) + + def is_cgi(self): + """Test whether self.path corresponds to a CGI script. + + Add extra check that self.path doesn't contains .. + Also check if the file is a executable file or not. + If the file is not executable, it is handled as static file or dir + rather than a CGI script. + """ + + if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): + if '..' in self.path: + return False + # strip query parameter from request path + resource_name = self.path.split('?', 2)[0] + # convert resource_name into real path name in filesystem. + scriptfile = self.translate_path(resource_name) + if not os.path.isfile(scriptfile): + return False + if not self.is_executable(scriptfile): + return False + return True + return False + + +def _get_logger_from_class(c): + return logging.getLogger('%s.%s' % (c.__module__, c.__name__)) + + +def _configure_logging(options): + logging.addLevelName(common.LOGLEVEL_FINE, 'FINE') + + logger = logging.getLogger() + logger.setLevel(logging.getLevelName(options.log_level.upper())) + if options.log_file: + handler = logging.handlers.RotatingFileHandler( + options.log_file, 'a', options.log_max, options.log_count) + else: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + deflate_log_level_name = logging.getLevelName( + options.deflate_log_level.upper()) + _get_logger_from_class(util._Deflater).setLevel( + deflate_log_level_name) + _get_logger_from_class(util._Inflater).setLevel( + deflate_log_level_name) + + +def _build_option_parser(): + parser = optparse.OptionParser() + + parser.add_option('--config', dest='config_file', type='string', + default=None, + help=('Path to configuration file. See the file comment ' + 'at the top of this file for the configuration ' + 'file format')) + parser.add_option('-H', '--server-host', '--server_host', + dest='server_host', + default='', + help='server hostname to listen to') + parser.add_option('-V', '--validation-host', '--validation_host', + dest='validation_host', + default=None, + help='server hostname to validate in absolute path.') + parser.add_option('-p', '--port', dest='port', type='int', + default=common.DEFAULT_WEB_SOCKET_PORT, + help='port to listen to') + parser.add_option('-P', '--validation-port', '--validation_port', + dest='validation_port', type='int', + default=None, + help='server port to validate in absolute path.') + parser.add_option('-w', '--websock-handlers', '--websock_handlers', + dest='websock_handlers', + default='.', + help=('The root directory of WebSocket handler files. ' + 'If the path is relative, --document-root is used ' + 'as the base.')) + parser.add_option('-m', '--websock-handlers-map-file', + '--websock_handlers_map_file', + dest='websock_handlers_map_file', + default=None, + help=('WebSocket handlers map file. ' + 'Each line consists of alias_resource_path and ' + 'existing_resource_path, separated by spaces.')) + parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir', + default=None, + help=('Must be a directory under --websock-handlers. ' + 'Only handlers under this directory are scanned ' + 'and registered to the server. ' + 'Useful for saving scan time when the handler ' + 'root directory contains lots of files that are ' + 'not handler file or are handler files but you ' + 'don\'t want them to be registered. ')) + parser.add_option('--allow-handlers-outside-root-dir', + '--allow_handlers_outside_root_dir', + dest='allow_handlers_outside_root_dir', + action='store_true', + default=False, + help=('Scans WebSocket handlers even if their canonical ' + 'path is not under --websock-handlers.')) + parser.add_option('-d', '--document-root', '--document_root', + dest='document_root', default='.', + help='Document root directory.') + parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths', + default=None, + help=('CGI paths relative to document_root.' + 'Comma-separated. (e.g -x /cgi,/htbin) ' + 'Files under document_root/cgi_path are handled ' + 'as CGI programs. Must be executable.')) + parser.add_option('-t', '--tls', dest='use_tls', action='store_true', + default=False, help='use TLS (wss://)') + parser.add_option('-k', '--private-key', '--private_key', + dest='private_key', + default='', help='TLS private key file.') + parser.add_option('-c', '--certificate', dest='certificate', + default='', help='TLS certificate file.') + parser.add_option('--tls-client-auth', dest='tls_client_auth', + action='store_true', default=False, + help='Requires TLS client auth on every connection.') + parser.add_option('--tls-client-ca', dest='tls_client_ca', default='', + help=('Specifies a pem file which contains a set of ' + 'concatenated CA certificates which are used to ' + 'validate certificates passed from clients')) + parser.add_option('--basic-auth', dest='use_basic_auth', + action='store_true', default=False, + help='Requires Basic authentication.') + parser.add_option('--basic-auth-credential', + dest='basic_auth_credential', default='test:test', + help='Specifies the credential of basic authentication ' + 'by username:password pair (e.g. test:test).') + parser.add_option('-l', '--log-file', '--log_file', dest='log_file', + default='', help='Log file.') + # Custom log level: + # - FINE: Prints status of each frame processing step + parser.add_option('--log-level', '--log_level', type='choice', + dest='log_level', default='warn', + choices=['fine', + 'debug', 'info', 'warning', 'warn', 'error', + 'critical'], + help='Log level.') + parser.add_option('--deflate-log-level', '--deflate_log_level', + type='choice', + dest='deflate_log_level', default='warn', + choices=['debug', 'info', 'warning', 'warn', 'error', + 'critical'], + help='Log level for _Deflater and _Inflater.') + parser.add_option('--thread-monitor-interval-in-sec', + '--thread_monitor_interval_in_sec', + dest='thread_monitor_interval_in_sec', + type='int', default=-1, + help=('If positive integer is specified, run a thread ' + 'monitor to show the status of server threads ' + 'periodically in the specified inteval in ' + 'second. If non-positive integer is specified, ' + 'disable the thread monitor.')) + parser.add_option('--log-max', '--log_max', dest='log_max', type='int', + default=_DEFAULT_LOG_MAX_BYTES, + help='Log maximum bytes') + parser.add_option('--log-count', '--log_count', dest='log_count', + type='int', default=_DEFAULT_LOG_BACKUP_COUNT, + help='Log backup count') + parser.add_option('--allow-draft75', dest='allow_draft75', + action='store_true', default=False, + help='Obsolete option. Ignored.') + parser.add_option('--strict', dest='strict', action='store_true', + default=False, help='Obsolete option. Ignored.') + parser.add_option('-q', '--queue', dest='request_queue_size', type='int', + default=_DEFAULT_REQUEST_QUEUE_SIZE, + help='request queue size') + + return parser + + +class ThreadMonitor(threading.Thread): + daemon = True + + def __init__(self, interval_in_sec): + threading.Thread.__init__(self, name='ThreadMonitor') + + self._logger = util.get_class_logger(self) + + self._interval_in_sec = interval_in_sec + + def run(self): + while True: + thread_name_list = [] + for thread in threading.enumerate(): + thread_name_list.append(thread.name) + self._logger.info( + "%d active threads: %s", + threading.active_count(), + ', '.join(thread_name_list)) + time.sleep(self._interval_in_sec) + + +def _parse_args_and_config(args): + parser = _build_option_parser() + + # First, parse options without configuration file. + temporary_options, temporary_args = parser.parse_args(args=args) + if temporary_args: + logging.critical( + 'Unrecognized positional arguments: %r', temporary_args) + sys.exit(1) + + if temporary_options.config_file: + try: + config_fp = open(temporary_options.config_file, 'r') + except IOError, e: + logging.critical( + 'Failed to open configuration file %r: %r', + temporary_options.config_file, + e) + sys.exit(1) + + config_parser = ConfigParser.SafeConfigParser() + config_parser.readfp(config_fp) + config_fp.close() + + args_from_config = [] + for name, value in config_parser.items('pywebsocket'): + args_from_config.append('--' + name) + args_from_config.append(value) + if args is None: + args = args_from_config + else: + args = args_from_config + args + return parser.parse_args(args=args) + else: + return temporary_options, temporary_args + + +def _main(args=None): + """You can call this function from your own program, but please note that + this function has some side-effects that might affect your program. For + example, util.wrap_popen3_for_win use in this method replaces implementation + of os.popen3. + """ + + options, args = _parse_args_and_config(args=args) + + os.chdir(options.document_root) + + _configure_logging(options) + + # TODO(tyoshino): Clean up initialization of CGI related values. Move some + # of code here to WebSocketRequestHandler class if it's better. + options.cgi_directories = [] + options.is_executable_method = None + if options.cgi_paths: + options.cgi_directories = options.cgi_paths.split(',') + if sys.platform in ('cygwin', 'win32'): + cygwin_path = None + # For Win32 Python, it is expected that CYGWIN_PATH + # is set to a directory of cygwin binaries. + # For example, websocket_server.py in Chromium sets CYGWIN_PATH to + # full path of third_party/cygwin/bin. + if 'CYGWIN_PATH' in os.environ: + cygwin_path = os.environ['CYGWIN_PATH'] + util.wrap_popen3_for_win(cygwin_path) + + def __check_script(scriptpath): + return util.get_script_interp(scriptpath, cygwin_path) + + options.is_executable_method = __check_script + + if options.use_tls: + if not (_HAS_SSL or _HAS_OPEN_SSL): + logging.critical('TLS support requires ssl or pyOpenSSL module.') + sys.exit(1) + if not options.private_key or not options.certificate: + logging.critical( + 'To use TLS, specify private_key and certificate.') + sys.exit(1) + + if options.tls_client_auth: + if not options.use_tls: + logging.critical('TLS must be enabled for client authentication.') + sys.exit(1) + if not _HAS_SSL: + logging.critical('Client authentication requires ssl module.') + + if not options.scan_dir: + options.scan_dir = options.websock_handlers + + if options.use_basic_auth: + options.basic_auth_credential = 'Basic ' + base64.b64encode( + options.basic_auth_credential) + + try: + if options.thread_monitor_interval_in_sec > 0: + # Run a thread monitor to show the status of server threads for + # debugging. + ThreadMonitor(options.thread_monitor_interval_in_sec).start() + + server = WebSocketServer(options) + server.serve_forever() + except Exception, e: + logging.critical('mod_pywebsocket: %s' % e) + logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) + sys.exit(1) + + +if __name__ == '__main__': + _main(sys.argv[1:]) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/stream.py b/pyload/lib/mod_pywebsocket/stream.py new file mode 100644 index 000000000..edc533279 --- /dev/null +++ b/pyload/lib/mod_pywebsocket/stream.py @@ -0,0 +1,57 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""This file exports public symbols. +""" + + +from mod_pywebsocket._stream_base import BadOperationException +from mod_pywebsocket._stream_base import ConnectionTerminatedException +from mod_pywebsocket._stream_base import InvalidFrameException +from mod_pywebsocket._stream_base import InvalidUTF8Exception +from mod_pywebsocket._stream_base import UnsupportedFrameException +from mod_pywebsocket._stream_hixie75 import StreamHixie75 +from mod_pywebsocket._stream_hybi import Frame +from mod_pywebsocket._stream_hybi import Stream +from mod_pywebsocket._stream_hybi import StreamOptions + +# These methods are intended to be used by WebSocket client developers to have +# their implementations receive broken data in tests. +from mod_pywebsocket._stream_hybi import create_close_frame +from mod_pywebsocket._stream_hybi import create_header +from mod_pywebsocket._stream_hybi import create_length_header +from mod_pywebsocket._stream_hybi import create_ping_frame +from mod_pywebsocket._stream_hybi import create_pong_frame +from mod_pywebsocket._stream_hybi import create_binary_frame +from mod_pywebsocket._stream_hybi import create_text_frame +from mod_pywebsocket._stream_hybi import create_closing_handshake_body + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/mod_pywebsocket/util.py b/pyload/lib/mod_pywebsocket/util.py new file mode 100644 index 000000000..7bb0b5d9e --- /dev/null +++ b/pyload/lib/mod_pywebsocket/util.py @@ -0,0 +1,515 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""WebSocket utilities. +""" + + +import array +import errno + +# Import hash classes from a module available and recommended for each Python +# version and re-export those symbol. Use sha and md5 module in Python 2.4, and +# hashlib module in Python 2.6. +try: + import hashlib + md5_hash = hashlib.md5 + sha1_hash = hashlib.sha1 +except ImportError: + import md5 + import sha + md5_hash = md5.md5 + sha1_hash = sha.sha + +import StringIO +import logging +import os +import re +import socket +import traceback +import zlib + + +def get_stack_trace(): + """Get the current stack trace as string. + + This is needed to support Python 2.3. + TODO: Remove this when we only support Python 2.4 and above. + Use traceback.format_exc instead. + """ + + out = StringIO.StringIO() + traceback.print_exc(file=out) + return out.getvalue() + + +def prepend_message_to_exception(message, exc): + """Prepend message to the exception.""" + + exc.args = (message + str(exc),) + return + + +def __translate_interp(interp, cygwin_path): + """Translate interp program path for Win32 python to run cygwin program + (e.g. perl). Note that it doesn't support path that contains space, + which is typically true for Unix, where #!-script is written. + For Win32 python, cygwin_path is a directory of cygwin binaries. + + Args: + interp: interp command line + cygwin_path: directory name of cygwin binary, or None + Returns: + translated interp command line. + """ + if not cygwin_path: + return interp + m = re.match('^[^ ]*/([^ ]+)( .*)?', interp) + if m: + cmd = os.path.join(cygwin_path, m.group(1)) + return cmd + m.group(2) + return interp + + +def get_script_interp(script_path, cygwin_path=None): + """Gets #!-interpreter command line from the script. + + It also fixes command path. When Cygwin Python is used, e.g. in WebKit, + it could run "/usr/bin/perl -wT hello.pl". + When Win32 Python is used, e.g. in Chromium, it couldn't. So, fix + "/usr/bin/perl" to "<cygwin_path>\perl.exe". + + Args: + script_path: pathname of the script + cygwin_path: directory name of cygwin binary, or None + Returns: + #!-interpreter command line, or None if it is not #!-script. + """ + fp = open(script_path) + line = fp.readline() + fp.close() + m = re.match('^#!(.*)', line) + if m: + return __translate_interp(m.group(1), cygwin_path) + return None + + +def wrap_popen3_for_win(cygwin_path): + """Wrap popen3 to support #!-script on Windows. + + Args: + cygwin_path: path for cygwin binary if command path is needed to be + translated. None if no translation required. + """ + + __orig_popen3 = os.popen3 + + def __wrap_popen3(cmd, mode='t', bufsize=-1): + cmdline = cmd.split(' ') + interp = get_script_interp(cmdline[0], cygwin_path) + if interp: + cmd = interp + ' ' + cmd + return __orig_popen3(cmd, mode, bufsize) + + os.popen3 = __wrap_popen3 + + +def hexify(s): + return ' '.join(map(lambda x: '%02x' % ord(x), s)) + + +def get_class_logger(o): + return logging.getLogger( + '%s.%s' % (o.__class__.__module__, o.__class__.__name__)) + + +class NoopMasker(object): + """A masking object that has the same interface as RepeatedXorMasker but + just returns the string passed in without making any change. + """ + + def __init__(self): + pass + + def mask(self, s): + return s + + +class RepeatedXorMasker(object): + """A masking object that applies XOR on the string given to mask method + with the masking bytes given to the constructor repeatedly. This object + remembers the position in the masking bytes the last mask method call + ended and resumes from that point on the next mask method call. + """ + + def __init__(self, mask): + self._mask = map(ord, mask) + self._mask_size = len(self._mask) + self._count = 0 + + def mask(self, s): + result = array.array('B') + result.fromstring(s) + # Use temporary local variables to eliminate the cost to access + # attributes + count = self._count + mask = self._mask + mask_size = self._mask_size + for i in xrange(len(result)): + result[i] ^= mask[count] + count = (count + 1) % mask_size + self._count = count + + return result.tostring() + + +class DeflateRequest(object): + """A wrapper class for request object to intercept send and recv to perform + deflate compression and decompression transparently. + """ + + def __init__(self, request): + self._request = request + self.connection = DeflateConnection(request.connection) + + def __getattribute__(self, name): + if name in ('_request', 'connection'): + return object.__getattribute__(self, name) + return self._request.__getattribute__(name) + + def __setattr__(self, name, value): + if name in ('_request', 'connection'): + return object.__setattr__(self, name, value) + return self._request.__setattr__(name, value) + + +# By making wbits option negative, we can suppress CMF/FLG (2 octet) and +# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as +# deflate library. DICTID won't be added as far as we don't set dictionary. +# LZ77 window of 32K will be used for both compression and decompression. +# For decompression, we can just use 32K to cover any windows size. For +# compression, we use 32K so receivers must use 32K. +# +# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level +# to decode. +# +# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of +# Python. See also RFC1950 (ZLIB 3.3). + + +class _Deflater(object): + + def __init__(self, window_bits): + self._logger = get_class_logger(self) + + self._compress = zlib.compressobj( + zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits) + + def compress(self, bytes): + compressed_bytes = self._compress.compress(bytes) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + + def compress_and_flush(self, bytes): + compressed_bytes = self._compress.compress(bytes) + compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + + def compress_and_finish(self, bytes): + compressed_bytes = self._compress.compress(bytes) + compressed_bytes += self._compress.flush(zlib.Z_FINISH) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + +class _Inflater(object): + + def __init__(self): + self._logger = get_class_logger(self) + + self._unconsumed = '' + + self.reset() + + def decompress(self, size): + if not (size == -1 or size > 0): + raise Exception('size must be -1 or positive') + + data = '' + + while True: + if size == -1: + data += self._decompress.decompress(self._unconsumed) + # See Python bug http://bugs.python.org/issue12050 to + # understand why the same code cannot be used for updating + # self._unconsumed for here and else block. + self._unconsumed = '' + else: + data += self._decompress.decompress( + self._unconsumed, size - len(data)) + self._unconsumed = self._decompress.unconsumed_tail + if self._decompress.unused_data: + # Encountered a last block (i.e. a block with BFINAL = 1) and + # found a new stream (unused_data). We cannot use the same + # zlib.Decompress object for the new stream. Create a new + # Decompress object to decompress the new one. + # + # It's fine to ignore unconsumed_tail if unused_data is not + # empty. + self._unconsumed = self._decompress.unused_data + self.reset() + if size >= 0 and len(data) == size: + # data is filled. Don't call decompress again. + break + else: + # Re-invoke Decompress.decompress to try to decompress all + # available bytes before invoking read which blocks until + # any new byte is available. + continue + else: + # Here, since unused_data is empty, even if unconsumed_tail is + # not empty, bytes of requested length are already in data. We + # don't have to "continue" here. + break + + if data: + self._logger.debug('Decompressed %r', data) + return data + + def append(self, data): + self._logger.debug('Appended %r', data) + self._unconsumed += data + + def reset(self): + self._logger.debug('Reset') + self._decompress = zlib.decompressobj(-zlib.MAX_WBITS) + + +# Compresses/decompresses given octets using the method introduced in RFC1979. + + +class _RFC1979Deflater(object): + """A compressor class that applies DEFLATE to given byte sequence and + flushes using the algorithm described in the RFC1979 section 2.1. + """ + + def __init__(self, window_bits, no_context_takeover): + self._deflater = None + if window_bits is None: + window_bits = zlib.MAX_WBITS + self._window_bits = window_bits + self._no_context_takeover = no_context_takeover + + def filter(self, bytes, flush=True, bfinal=False): + if self._deflater is None or (self._no_context_takeover and flush): + self._deflater = _Deflater(self._window_bits) + + if bfinal: + result = self._deflater.compress_and_finish(bytes) + # Add a padding block with BFINAL = 0 and BTYPE = 0. + result = result + chr(0) + self._deflater = None + return result + if flush: + # Strip last 4 octets which is LEN and NLEN field of a + # non-compressed block added for Z_SYNC_FLUSH. + return self._deflater.compress_and_flush(bytes)[:-4] + return self._deflater.compress(bytes) + +class _RFC1979Inflater(object): + """A decompressor class for byte sequence compressed and flushed following + the algorithm described in the RFC1979 section 2.1. + """ + + def __init__(self): + self._inflater = _Inflater() + + def filter(self, bytes): + # Restore stripped LEN and NLEN field of a non-compressed block added + # for Z_SYNC_FLUSH. + self._inflater.append(bytes + '\x00\x00\xff\xff') + return self._inflater.decompress(-1) + + +class DeflateSocket(object): + """A wrapper class for socket object to intercept send and recv to perform + deflate compression and decompression transparently. + """ + + # Size of the buffer passed to recv to receive compressed data. + _RECV_SIZE = 4096 + + def __init__(self, socket): + self._socket = socket + + self._logger = get_class_logger(self) + + self._deflater = _Deflater(zlib.MAX_WBITS) + self._inflater = _Inflater() + + def recv(self, size): + """Receives data from the socket specified on the construction up + to the specified size. Once any data is available, returns it even + if it's smaller than the specified size. + """ + + # TODO(tyoshino): Allow call with size=0. It should block until any + # decompressed data is available. + if size <= 0: + raise Exception('Non-positive size passed') + while True: + data = self._inflater.decompress(size) + if len(data) != 0: + return data + + read_data = self._socket.recv(DeflateSocket._RECV_SIZE) + if not read_data: + return '' + self._inflater.append(read_data) + + def sendall(self, bytes): + self.send(bytes) + + def send(self, bytes): + self._socket.sendall(self._deflater.compress_and_flush(bytes)) + return len(bytes) + + +class DeflateConnection(object): + """A wrapper class for request object to intercept write and read to + perform deflate compression and decompression transparently. + """ + + def __init__(self, connection): + self._connection = connection + + self._logger = get_class_logger(self) + + self._deflater = _Deflater(zlib.MAX_WBITS) + self._inflater = _Inflater() + + def get_remote_addr(self): + return self._connection.remote_addr + remote_addr = property(get_remote_addr) + + def put_bytes(self, bytes): + self.write(bytes) + + def read(self, size=-1): + """Reads at most size bytes. Blocks until there's at least one byte + available. + """ + + # TODO(tyoshino): Allow call with size=0. + if not (size == -1 or size > 0): + raise Exception('size must be -1 or positive') + + data = '' + while True: + if size == -1: + data += self._inflater.decompress(-1) + else: + data += self._inflater.decompress(size - len(data)) + + if size >= 0 and len(data) != 0: + break + + # TODO(tyoshino): Make this read efficient by some workaround. + # + # In 3.0.3 and prior of mod_python, read blocks until length bytes + # was read. We don't know the exact size to read while using + # deflate, so read byte-by-byte. + # + # _StandaloneRequest.read that ultimately performs + # socket._fileobject.read also blocks until length bytes was read + read_data = self._connection.read(1) + if not read_data: + break + self._inflater.append(read_data) + return data + + def write(self, bytes): + self._connection.write(self._deflater.compress_and_flush(bytes)) + + +def _is_ewouldblock_errno(error_number): + """Returns True iff error_number indicates that receive operation would + block. To make this portable, we check availability of errno and then + compare them. + """ + + for error_name in ['WSAEWOULDBLOCK', 'EWOULDBLOCK', 'EAGAIN']: + if (error_name in dir(errno) and + error_number == getattr(errno, error_name)): + return True + return False + + +def drain_received_data(raw_socket): + # Set the socket non-blocking. + original_timeout = raw_socket.gettimeout() + raw_socket.settimeout(0.0) + + drained_data = [] + + # Drain until the socket is closed or no data is immediately + # available for read. + while True: + try: + data = raw_socket.recv(1) + if not data: + break + drained_data.append(data) + except socket.error, e: + # e can be either a pair (errno, string) or just a string (or + # something else) telling what went wrong. We suppress only + # the errors that indicates that the socket blocks. Those + # exceptions can be parsed as a pair (errno, string). + try: + error_number, message = e + except: + # Failed to parse socket.error. + raise e + + if _is_ewouldblock_errno(error_number): + break + else: + raise e + + # Rollback timeout value. + raw_socket.settimeout(original_timeout) + + return ''.join(drained_data) + + +# vi:sts=4 sw=4 et diff --git a/pyload/lib/new_collections.py b/pyload/lib/new_collections.py new file mode 100644 index 000000000..12d05b4b9 --- /dev/null +++ b/pyload/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/lib/rename_process.py b/pyload/lib/rename_process.py index 2527cef39..2527cef39 100644 --- a/module/lib/rename_process.py +++ b/pyload/lib/rename_process.py diff --git a/module/lib/simplejson/__init__.py b/pyload/lib/simplejson/__init__.py index ef5c0db48..ef5c0db48 100644 --- a/module/lib/simplejson/__init__.py +++ b/pyload/lib/simplejson/__init__.py diff --git a/module/lib/simplejson/decoder.py b/pyload/lib/simplejson/decoder.py index e5496d6e7..e5496d6e7 100644 --- a/module/lib/simplejson/decoder.py +++ b/pyload/lib/simplejson/decoder.py diff --git a/module/lib/simplejson/encoder.py b/pyload/lib/simplejson/encoder.py index 5ec7440f1..5ec7440f1 100644 --- a/module/lib/simplejson/encoder.py +++ b/pyload/lib/simplejson/encoder.py diff --git a/module/lib/simplejson/ordered_dict.py b/pyload/lib/simplejson/ordered_dict.py index 87ad88824..87ad88824 100644 --- a/module/lib/simplejson/ordered_dict.py +++ b/pyload/lib/simplejson/ordered_dict.py diff --git a/module/lib/simplejson/scanner.py b/pyload/lib/simplejson/scanner.py index 54593a371..54593a371 100644 --- a/module/lib/simplejson/scanner.py +++ b/pyload/lib/simplejson/scanner.py diff --git a/module/lib/simplejson/tool.py b/pyload/lib/simplejson/tool.py index 73370db55..73370db55 100644 --- a/module/lib/simplejson/tool.py +++ b/pyload/lib/simplejson/tool.py diff --git a/module/lib/wsgiserver/LICENSE.txt b/pyload/lib/wsgiserver/LICENSE.txt index a15165ee2..a15165ee2 100644 --- a/module/lib/wsgiserver/LICENSE.txt +++ b/pyload/lib/wsgiserver/LICENSE.txt diff --git a/module/lib/wsgiserver/__init__.py b/pyload/lib/wsgiserver/__init__.py index c380e18b0..c380e18b0 100644 --- a/module/lib/wsgiserver/__init__.py +++ b/pyload/lib/wsgiserver/__init__.py diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py new file mode 100644 index 000000000..262adaebd --- /dev/null +++ b/pyload/network/Browser.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from logging import getLogger + +from HTTPRequest import HTTPRequest +from HTTPDownload import HTTPDownload + +# @ Deprecated +class Browser(object): + __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl") + + def __init__(self, bucket=None, options={}): + self.log = getLogger("log") + + self.options = options #holds pycurl options + self.bucket = bucket + + self.cj = None # needs to be set later + self._size = 0 + + self.renewHTTPRequest() + self.dl = None + + + def renewHTTPRequest(self): + if hasattr(self, "http"): self.http.close() + self.http = HTTPRequest(self.cj, self.options) + + def setLastURL(self, val): + self.http.lastURL = val + + # tunnel some attributes from HTTP Request to Browser + lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL) + lastURL = property(lambda self: self.http.lastURL, setLastURL) + code = property(lambda self: self.http.code) + cookieJar = property(lambda self: self.cj) + + def setCookieJar(self, cj): + self.cj = cj + self.http.cj = cj + + @property + def speed(self): + if self.dl: + return self.dl.speed + return 0 + + @property + def size(self): + if self._size: + return self._size + if self.dl: + return self.dl.size + return 0 + + @property + def name(self): + if self.dl: + return self.dl.name + else: + return "" + + @property + def arrived(self): + if self.dl: + return self.dl.arrived + return 0 + + @property + def percent(self): + if not self.size: return 0 + return (self.arrived * 100) / self.size + + def clearCookies(self): + if self.cj: + self.cj.clear() + self.http.clearCookies() + + def clearReferer(self): + self.http.lastURL = None + + def abortDownloads(self): + self.http.abort = True + if self.dl: + self._size = self.dl.size + self.dl.abort = True + + def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False, + disposition=False): + """ this can also download ftp """ + self._size = 0 + self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None, + self.cj if cookies else None, self.bucket, self.options, disposition) + name = self.dl.download(chunks, resume) + self._size = self.dl.size + + self.dl = None + + return name + + def load(self, *args, **kwargs): + """ retrieves page """ + return self.http.load(*args, **kwargs) + + def putHeader(self, name, value): + """ add a header to the request """ + self.http.putHeader(name, value) + + def addAuth(self, pwd): + """Adds user and pw for http auth + + :param pwd: string, user:password + """ + self.options["auth"] = pwd + self.renewHTTPRequest() #we need a new request + + def removeAuth(self): + if "auth" in self.options: del self.options["auth"] + self.renewHTTPRequest() + + def setOption(self, name, value): + """Adds an option to the request, see HTTPRequest for existing ones""" + self.options[name] = value + + def deleteOption(self, name): + if name in self.options: del self.options[name] + + def clearHeaders(self): + self.http.clearHeaders() + + def close(self): + """ cleanup """ + if hasattr(self, "http"): + self.http.close() + del self.http + if hasattr(self, "dl"): + del self.dl + if hasattr(self, "cj"): + del self.cj + +if __name__ == "__main__": + browser = Browser()#proxies={"socks5": "localhost:5000"}) + ip = "http://www.whatismyip.com/automation/n09230945.asp" + #browser.getPage("http://google.com/search?q=bar") + #browser.getPage("https://encrypted.google.com/") + #print browser.getPage(ip) + #print browser.getRedirectLocation("http://google.com/") + #browser.getPage("https://encrypted.google.com/") + #browser.getPage("http://google.com/search?q=bar") + + browser.httpDownload("http://speedtest.netcologne.de/test_10mb.bin", "test_10mb.bin") + diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py new file mode 100644 index 000000000..40d8c8071 --- /dev/null +++ b/pyload/network/Bucket.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from time import time + +# 10kb minimum rate +MIN_RATE = 10240 + +class Bucket: + def __init__(self): + self.rate = 0 # bytes per second, maximum targeted throughput + self.tokens = 0 + self.timestamp = time() + + def __nonzero__(self): + return False if self.rate < MIN_RATE else True + + def setRate(self, rate): + self.rate = int(rate) + + def consumed(self, amount): + """ return the time the process has to sleep, after it consumed a specified amount """ + if self.rate < MIN_RATE: return 0 #May become unresponsive otherwise + + self.calc_tokens() + self.tokens -= amount + + if self.tokens < 0: + return -self.tokens/float(self.rate) + else: + return 0 + + def calc_tokens(self): + if self.tokens < self.rate: + now = time() + delta = self.rate * (now - self.timestamp) + self.tokens = min(self.rate, self.tokens + delta) + self.timestamp = now + diff --git a/pyload/network/CookieJar.py b/pyload/network/CookieJar.py new file mode 100644 index 000000000..3d39c66b9 --- /dev/null +++ b/pyload/network/CookieJar.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from time import time +from Cookie import SimpleCookie + +class CookieJar(SimpleCookie): + + def getCookie(self, name): + return self[name].value + + def setCookie(self, domain, name, value, path="/", exp=None, secure="FALSE"): + if not exp: exp = time() + 3600 * 24 * 180 + + self[name] = value + self[name]["domain"] = domain + self[name]["path"] = path + self[name]["expires"] = exp + if secure == "TRUE": + self[name]["secure"] = secure diff --git a/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py new file mode 100644 index 000000000..4389aef28 --- /dev/null +++ b/pyload/network/HTTPChunk.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN +""" +from os import remove, stat, fsync +from os.path import exists +from time import sleep +from re import search + +import codecs +import pycurl + +from pyload.utils import remove_chars +from pyload.utils.fs import fs_encode + +from HTTPRequest import HTTPRequest + +class WrongFormat(Exception): + pass + + +class ChunkInfo(): + def __init__(self, name): + self.name = unicode(name) + self.size = 0 + self.resume = False + self.chunks = [] + + def __repr__(self): + ret = "ChunkInfo: %s, %s\n" % (self.name, self.size) + for i, c in enumerate(self.chunks): + ret += "%s# %s\n" % (i, c[1]) + + return ret + + def setSize(self, size): + self.size = int(size) + + def addChunk(self, name, range): + self.chunks.append((name, range)) + + def clear(self): + self.chunks = [] + + def createChunks(self, chunks): + self.clear() + chunk_size = self.size / chunks + + current = 0 + for i in range(chunks): + end = self.size - 1 if (i == chunks - 1) else current + chunk_size + self.addChunk("%s.chunk%s" % (self.name, i), (current, end)) + current += chunk_size + 1 + + + def save(self): + fs_name = fs_encode("%s.chunks" % self.name) + fh = codecs.open(fs_name, "w", "utf_8") + fh.write("name:%s\n" % self.name) + fh.write("size:%s\n" % self.size) + for i, c in enumerate(self.chunks): + fh.write("#%d:\n" % i) + fh.write("\tname:%s\n" % c[0]) + fh.write("\trange:%i-%i\n" % c[1]) + fh.close() + + @staticmethod + def load(name): + fs_name = fs_encode("%s.chunks" % name) + if not exists(fs_name): + raise IOError() + fh = codecs.open(fs_name, "r", "utf_8") + name = fh.readline()[:-1] + size = fh.readline()[:-1] + if name.startswith("name:") and size.startswith("size:"): + name = name[5:] + size = size[5:] + else: + fh.close() + raise WrongFormat() + ci = ChunkInfo(name) + ci.loaded = True + ci.setSize(size) + while True: + if not fh.readline(): #skip line + break + name = fh.readline()[1:-1] + range = fh.readline()[1:-1] + if name.startswith("name:") and range.startswith("range:"): + name = name[5:] + range = range[6:].split("-") + else: + raise WrongFormat() + + ci.addChunk(name, (long(range[0]), long(range[1]))) + fh.close() + return ci + + def remove(self): + fs_name = fs_encode("%s.chunks" % self.name) + if exists(fs_name): remove(fs_name) + + def getCount(self): + return len(self.chunks) + + def getChunkName(self, index): + return self.chunks[index][0] + + def getChunkRange(self, index): + return self.chunks[index][1] + + +class HTTPChunk(HTTPRequest): + def __init__(self, id, parent, range=None, resume=False): + self.id = id + self.p = parent # HTTPDownload instance + self.range = range # tuple (start, end) + self.resume = resume + self.log = parent.log + + self.size = range[1] - range[0] if range else -1 + self.arrived = 0 + self.lastURL = self.p.referer + + self.c = pycurl.Curl() + + self.header = "" + self.headerParsed = False #indicates if the header has been processed + + self.fp = None #file handle + + self.initHandle() + self.setInterface(self.p.options) + + self.BOMChecked = False # check and remove byte order mark + + self.rep = None + + self.sleep = 0.000 + self.lastSize = 0 + + def __repr__(self): + return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived) + + @property + def cj(self): + return self.p.cj + + def getHandle(self): + """ returns a Curl handle ready to use for perform/multiperform """ + + self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj) + self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + # request all bytes, since some servers in russia seems to have a defect arihmetic unit + + fs_name = fs_encode(self.p.info.getChunkName(self.id)) + if self.resume: + self.fp = open(fs_name, "ab") + self.arrived = self.fp.tell() + if not self.arrived: + self.arrived = stat(fs_name).st_size + + if self.range: + #do nothing if chunk already finished + if self.arrived + self.range[0] >= self.range[1]: return None + + if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything + range = "%i-" % (self.arrived + self.range[0]) + else: + range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked resume with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + else: + self.log.debug("Resume File from %i" % self.arrived) + self.c.setopt(pycurl.RESUME_FROM, self.arrived) + + else: + if self.range: + if self.id == len(self.p.info.chunks) - 1: # see above + range = "%i-" % self.range[0] + else: + range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + + self.fp = open(fs_name, "wb") + + return self.c + + def writeHeader(self, buf): + self.header += buf + #@TODO forward headers?, this is possibly 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() + elif not self.range and buf.startswith("150") and "data connection" in buf: #ftp file size parsing + size = search(r"(\d+) bytes", buf) + if size: + self.p.size = int(size.group(1)) + self.p.chunkSupport = True + + self.headerParsed = True + + def writeBody(self, buf): + #ignore BOM, it confuses unrar + if not self.BOMChecked: + if [ord(b) for b in buf[:3]] == [239, 187, 191]: + buf = buf[3:] + self.BOMChecked = True + + size = len(buf) + + self.arrived += size + + self.fp.write(buf) + + if self.p.bucket: + sleep(self.p.bucket.consumed(size)) + else: + # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller + # otherwise reduce sleep time 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 + else: + self.sleep *= 0.7 + + self.lastSize = size + + sleep(self.sleep) + + if self.range and self.arrived > self.size: + return 0 #close if we have enough data + + + def parseHeader(self): + """parse data from 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 "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]) + + self.headerParsed = True + + def stop(self): + """The download will not proceed after next call of writeBody""" + self.range = [0,0] + self.size = 0 + + def resetRange(self): + """ Reset the range, so the download will load all data available """ + self.range = None + + def setRange(self, range): + self.range = range + self.size = range[1] - range[0] + + def flushFile(self): + """ flush and close file """ + self.fp.flush() + fsync(self.fp.fileno()) #make sure everything was written to disk + self.fp.close() #needs to be closed, or merging chunks will fail + + def close(self): + """ closes everything, unusable after this """ + if self.fp: self.fp.close() + self.c.close() + if hasattr(self, "p"): del self.p diff --git a/pyload/network/HTTPDownload.py b/pyload/network/HTTPDownload.py new file mode 100644 index 000000000..04bf2363a --- /dev/null +++ b/pyload/network/HTTPDownload.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN +""" + +from os import remove +from os.path import dirname +from time import time +from shutil import move +from logging import getLogger + +import pycurl + +from HTTPChunk import ChunkInfo, HTTPChunk +from HTTPRequest import BadHeader + +from pyload.plugins.Base import Abort +from pyload.utils.fs import save_join, fs_encode + +# TODO: save content-disposition for resuming + +class HTTPDownload(): + """ loads an url, http + ftp supported """ + + def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None, + options={}, disposition=False): + self.url = url + self.filename = filename #complete file destination, not only name + self.get = get + self.post = post + self.referer = referer + self.cj = cj #cookiejar if cookies are needed + self.bucket = bucket + self.options = options + self.disposition = disposition + # all arguments + + self.abort = False + self.size = 0 + self._name = ""# will be parsed from content disposition + + self.chunks = [] + + self.log = getLogger("log") + + try: + self.info = ChunkInfo.load(filename) + self.info.resume = True #resume is only possible with valid info file + self.size = self.info.size + self.infoSaved = True + except IOError: + self.info = ChunkInfo(filename) + + self.chunkSupport = None + self.m = pycurl.CurlMulti() + + #needed for speed calculation + self.lastArrived = [] + self.speeds = [] + self.lastSpeeds = [0, 0] + + @property + def speed(self): + last = [sum(x) for x in self.lastSpeeds if x] + return (sum(self.speeds) + sum(last)) / (1 + len(last)) + + @property + def arrived(self): + return sum([c.arrived for c in self.chunks]) + + @property + def percent(self): + if not self.size: return 0 + return (self.arrived * 100) / self.size + + @property + def name(self): + return self._name if self.disposition else "" + + def _copyChunks(self): + init = fs_encode(self.info.getChunkName(0)) #initial chunk name + + if self.info.getCount() > 1: + fo = open(init, "rb+") #first chunkfile + for i in range(1, self.info.getCount()): + #input file + fo.seek( + self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks + fname = fs_encode("%s.chunk%d" % (self.filename, i)) + fi = open(fname, "rb") + buf = 32 * 1024 + while True: #copy in chunks, consumes less memory + data = fi.read(buf) + if not data: + break + fo.write(data) + fi.close() + if fo.tell() < self.info.getChunkRange(i)[1]: + fo.close() + remove(init) + self.info.remove() #there are probably invalid chunks + raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.") + remove(fname) #remove chunk + fo.close() + + if self.name: + self.filename = save_join(dirname(self.filename), self.name) + + move(init, fs_encode(self.filename)) + self.info.remove() #remove info file + + def download(self, chunks=1, resume=False): + """ returns new filename or None """ + + chunks = max(1, chunks) + resume = self.info.resume and resume + + try: + self._download(chunks, resume) + except pycurl.error, e: + #code 33 - no resume + code = e.args[0] + if code == 33: + # try again without resume + self.log.debug("Errno 33 -> Restart without resume") + + #remove old handles + for chunk in self.chunks: + self.closeChunk(chunk) + + return self._download(chunks, False) + else: + raise + finally: + self.close() + + return self.name + + def _download(self, chunks, resume): + if not resume: + self.info.clear() + self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #create an initial entry + + self.chunks = [] + + init = HTTPChunk(0, self, None, resume) #initial chunk that will load complete file (if needed) + + self.chunks.append(init) + self.m.add_handle(init.getHandle()) + + lastFinishCheck = 0 + lastTimeCheck = 0 + chunksDone = set() # list of curl handles that are finished + chunksCreated = False + done = False + if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can + self.chunkSupport = True + + while 1: + #need to create chunks + if not chunksCreated and self.chunkSupport and self.size: #will be set later by first chunk + + if not resume: + self.info.setSize(self.size) + self.info.createChunks(chunks) + self.info.save() + + chunks = self.info.getCount() + + init.setRange(self.info.getChunkRange(0)) + + for i in range(1, chunks): + c = HTTPChunk(i, self, self.info.getChunkRange(i), resume) + + handle = c.getHandle() + if handle: + self.chunks.append(c) + self.m.add_handle(handle) + else: + #close immediately + self.log.debug("Invalid curl handle -> closed") + c.close() + + chunksCreated = True + + while 1: + ret, num_handles = self.m.perform() + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + + t = time() + + # reduce these calls + # when num_q is 0, the loop is exited + while lastFinishCheck + 0.5 < t: + # list of failed curl handles + failed = [] + ex = None # save only last exception, we can only raise one anyway + + num_q, ok_list, err_list = self.m.info_read() + for c in ok_list: + chunk = self.findChunk(c) + try: # check if the header implies success, else add it to failed list + chunk.verifyHeader() + except BadHeader, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(c) + + for c in err_list: + curl, errno, msg = c + chunk = self.findChunk(curl) + #test if chunk was finished + if errno != 23 or "0 !=" not in msg: + failed.append(chunk) + ex = pycurl.error(errno, msg) + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex))) + continue + + try: # check if the header implies success, else add it to failed list + chunk.verifyHeader() + except BadHeader, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(curl) + if not num_q: # no more info to get + + # check if init is not finished so we reset download connections + # note that other chunks are closed and everything downloaded with initial connection + if failed and init not in failed and init.c not in chunksDone: + self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex)))) + + #list of chunks to clean and remove + to_clean = filter(lambda x: x is not init, self.chunks) + for chunk in to_clean: + self.closeChunk(chunk) + self.chunks.remove(chunk) + remove(fs_encode(self.info.getChunkName(chunk.id))) + + #let first chunk load the rest and update the info file + init.resetRange() + self.info.clear() + self.info.addChunk("%s.chunk0" % self.filename, (0, self.size)) + self.info.save() + elif failed: + raise ex + + lastFinishCheck = t + + if len(chunksDone) >= len(self.chunks): + if len(chunksDone) > len(self.chunks): + self.log.warning("Finished download chunks size incorrect, please report bug.") + done = True #all chunks loaded + + break + + if done: + break #all chunks loaded + + # calc speed once per second, averaging over 3 seconds + if lastTimeCheck + 1 < t: + diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in + enumerate(self.chunks)] + + self.lastSpeeds[1] = self.lastSpeeds[0] + self.lastSpeeds[0] = self.speeds + self.speeds = [float(a) / (t - lastTimeCheck) for a in diff] + self.lastArrived = [c.arrived for c in self.chunks] + lastTimeCheck = t + + if self.abort: + raise Abort() + + self.m.select(1) + + for chunk in self.chunks: + chunk.flushFile() #make sure downloads are written to disk + + self._copyChunks() + + def findChunk(self, handle): + """ linear search to find a chunk (should be ok since chunk size is usually low) """ + for chunk in self.chunks: + if chunk.c == handle: return chunk + + def closeChunk(self, chunk): + try: + self.m.remove_handle(chunk.c) + except pycurl.error, e: + self.log.debug("Error removing chunk: %s" % str(e)) + finally: + chunk.close() + + def close(self): + """ cleanup """ + for chunk in self.chunks: + self.closeChunk(chunk) + + self.chunks = [] + if hasattr(self, "m"): + self.m.close() + del self.m + if hasattr(self, "cj"): + del self.cj + if hasattr(self, "info"): + del self.info + +if __name__ == "__main__": + url = "http://speedtest.netcologne.de/test_100mb.bin" + + from Bucket import Bucket + + bucket = Bucket() + bucket.setRate(200 * 1024) + bucket = None + + print "starting" + + dwnld = HTTPDownload(url, "test_100mb.bin", bucket=bucket) + dwnld.download(chunks=3, resume=True) diff --git a/pyload/network/HTTPRequest.py b/pyload/network/HTTPRequest.py new file mode 100644 index 000000000..ebf2c3132 --- /dev/null +++ b/pyload/network/HTTPRequest.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN +""" + +import pycurl + +from codecs import getincrementaldecoder, lookup, BOM_UTF8 +from urllib import quote, urlencode +from httplib import responses +from logging import getLogger +from cStringIO import StringIO + +from pyload.plugins.Base import Abort + +def myquote(url): + return quote(url.encode('utf8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]") + +def myurlencode(data): + data = dict(data) + 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.get(int(code), "Unknown Header"))) + self.code = code + self.content = content + + +class HTTPRequest(): + def __init__(self, cookies=None, options=None): + self.c = pycurl.Curl() + self.rep = StringIO() + + self.cj = cookies #cookiejar + + self.lastURL = None + self.lastEffectiveURL = None + self.abort = False + self.code = 0 # last http code + + self.header = "" + + self.headers = [] #temporary request header + + self.initHandle() + self.setInterface(options) + self.setOptions(options) + + self.c.setopt(pycurl.WRITEFUNCTION, self.write) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + self.log = getLogger("log") + + + def initHandle(self): + """ sets common options to curl handle """ + self.c.setopt(pycurl.FOLLOWLOCATION, 1) + self.c.setopt(pycurl.MAXREDIRS, 5) + self.c.setopt(pycurl.CONNECTTIMEOUT, 30) + self.c.setopt(pycurl.NOSIGNAL, 1) + self.c.setopt(pycurl.NOPROGRESS, 1) + if hasattr(pycurl, "AUTOREFERER"): + self.c.setopt(pycurl.AUTOREFERER, 1) + self.c.setopt(pycurl.SSL_VERIFYPEER, 0) + # 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) + + self.c.setopt(pycurl.USERAGENT, + "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0") + if pycurl.version_info()[7]: + self.c.setopt(pycurl.ENCODING, "gzip, deflate") + self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*", + "Accept-Language: en-US,en", + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Connection: keep-alive", + "Keep-Alive: 300", + "Expect:"]) + + def setInterface(self, options): + + interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"] + + if interface and interface.lower() != "none": + self.c.setopt(pycurl.INTERFACE, str(interface)) + + if proxy: + if proxy["type"] == "socks4": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4) + elif proxy["type"] == "socks5": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5) + else: + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP) + + self.c.setopt(pycurl.PROXY, str(proxy["address"])) + self.c.setopt(pycurl.PROXYPORT, proxy["port"]) + + if proxy["username"]: + self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"]))) + + if ipv6: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) + else: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) + + if "auth" in options: + self.c.setopt(pycurl.USERPWD, str(options["auth"])) + + if "timeout" in options: + self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"]) + + def 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 """ + if self.cj: + self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST)) + + def getCookies(self): + """ add cookies from cj to curl handle """ + if self.cj: + for c in self.cj.getCookies(): + self.c.setopt(pycurl.COOKIELIST, c) + return + + def clearCookies(self): + self.c.setopt(pycurl.COOKIELIST, "") + + def setRequestContext(self, url, get, post, referer, cookies, multipart=False): + """ sets everything needed for the request """ + + url = myquote(url) + + if get: + get = urlencode(get) + url = "%s?%s" % (url, get) + + self.c.setopt(pycurl.URL, url) + self.lastURL = url + + if post: + self.c.setopt(pycurl.POST, 1) + if not multipart: + if type(post) == unicode: + post = str(post) #unicode not allowed + elif type(post) == str: + pass + else: + post = myurlencode(post) + + self.c.setopt(pycurl.POSTFIELDS, post) + else: + post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()] + self.c.setopt(pycurl.HTTPPOST, post) + else: + self.c.setopt(pycurl.POST, 0) + + if referer and self.lastURL: + self.c.setopt(pycurl.REFERER, str(self.lastURL)) + + if cookies: + self.c.setopt(pycurl.COOKIEFILE, "") + self.c.setopt(pycurl.COOKIEJAR, "") + self.getCookies() + + + def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False): + """ load and returns a given page """ + + self.setRequestContext(url, get, post, referer, cookies, multipart) + + # TODO: use http/rfc message instead + self.header = "" + + self.c.setopt(pycurl.HTTPHEADER, self.headers) + + if just_header: + self.c.setopt(pycurl.FOLLOWLOCATION, 0) + self.c.setopt(pycurl.NOBODY, 1) #TODO: nobody= no post? + + # 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() + rep = self.getResponse() + + self.c.setopt(pycurl.POSTFIELDS, "") + self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL) + self.code = self.verifyHeader() + + self.addCookies() + + if decode: + rep = self.decodeResponse(rep) + + return rep + + def verifyHeader(self): + """ raise an exceptions on bad headers """ + code = int(self.c.getinfo(pycurl.RESPONSE_CODE)) + # TODO: raise anyway to be consistent, also rename exception + if code in bad_headers: + #404 will NOT raise an exception + raise BadHeader(code, self.getResponse()) + return code + + def checkHeader(self): + """ check if header indicates failure""" + return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers + + def getResponse(self): + """ retrieve response from string io """ + if self.rep is None: return "" + value = self.rep.getvalue() + self.rep.close() + self.rep = StringIO() + return value + + def decodeResponse(self, rep): + """ decode with correct encoding, relies on header """ + header = self.header.splitlines() + encoding = "utf8" # default encoding + + for line in header: + line = line.lower().replace(" ", "") + if not line.startswith("content-type:") or\ + ("text" not in line and "application" not in line): + continue + + none, delemiter, charset = line.rpartition("charset=") + if delemiter: + charset = charset.split(";") + if charset: + encoding = charset[0] + + try: + #self.log.debug("Decoded %s" % encoding ) + if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8): + encoding = 'utf-8-sig' + + decoder = getincrementaldecoder(encoding)("replace") + rep = decoder.decode(rep, True) + + #TODO: html_unescape as default + + except LookupError: + self.log.debug("No Decoder found for %s" % encoding) + except Exception: + self.log.debug("Error when decoding string from %s." % encoding) + + return rep + + def write(self, buf): + """ writes response """ + if self.rep.tell() > 1000000 or self.abort: + rep = self.getResponse() + if self.abort: raise Abort() + f = open("response.dump", "wb") + f.write(rep) + f.close() + raise Exception("Loaded Url exceeded limit") + + self.rep.write(buf) + + def writeHeader(self, buf): + """ writes header """ + self.header += buf + + def putHeader(self, name, value): + self.headers.append("%s: %s" % (name, value)) + + def clearHeaders(self): + self.headers = [] + + def close(self): + """ cleanup, unusable after this """ + self.rep.close() + if hasattr(self, "cj"): + del self.cj + if hasattr(self, "c"): + self.c.close() + del self.c + +if __name__ == "__main__": + url = "http://pyload.org" + c = HTTPRequest() + print c.load(url) + diff --git a/pyload/network/RequestFactory.py b/pyload/network/RequestFactory.py new file mode 100644 index 000000000..472705409 --- /dev/null +++ b/pyload/network/RequestFactory.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 Bucket import Bucket + +from pyload.plugins.network.DefaultRequest import DefaultRequest, DefaultDownload + + +class RequestFactory: + def __init__(self, core): + self.core = core + self.bucket = Bucket() + self.updateBucket() + + def getURL(self, *args, **kwargs): + """ see HTTPRequest for argument list """ + h = DefaultRequest(self.getConfig()) + try: + rep = h.load(*args, **kwargs) + finally: + h.close() + + return rep + + ########## old api methods above + + def getRequest(self, context=None, klass=DefaultRequest): + """ Creates a request with new or given context """ + # also accepts the context class directly + if isinstance(context, klass.CONTEXT_CLASS): + return klass(self.getConfig(), context) + elif context: + return klass(*context) + else: + return klass(self.getConfig()) + + def getDownloadRequest(self, request=None, klass=DefaultDownload): + """ Instantiates a instance for downloading """ + # TODO: load with plugin manager + return klass(self.bucket, request) + + def getInterface(self): + return self.core.config["download"]["interface"] + + def getProxies(self): + """ returns a proxy list for the request classes """ + if not self.core.config["proxy"]["proxy"]: + return {} + else: + type = "http" + setting = self.core.config["proxy"]["type"].lower() + if setting == "socks4": + type = "socks4" + elif setting == "socks5": + type = "socks5" + + username = None + if self.core.config["proxy"]["username"] and self.core.config["proxy"]["username"].lower() != "none": + username = self.core.config["proxy"]["username"] + + pw = None + if self.core.config["proxy"]["password"] and self.core.config["proxy"]["password"].lower() != "none": + pw = self.core.config["proxy"]["password"] + + return { + "type": type, + "address": self.core.config["proxy"]["address"], + "port": self.core.config["proxy"]["port"], + "username": username, + "password": pw, + } + + def getConfig(self): + """returns options needed for pycurl""" + return {"interface": self.getInterface(), + "proxies": self.getProxies(), + "ipv6": self.core.config["download"]["ipv6"]} + + def updateBucket(self): + """ set values in the bucket according to settings""" + if not self.core.config["download"]["limit_speed"]: + self.bucket.setRate(-1) + else: + self.bucket.setRate(self.core.config["download"]["max_speed"] * 1024) + +# needs pyreq in global namespace +def getURL(*args, **kwargs): + return pyreq.getURL(*args, **kwargs) + + +def getRequest(*args, **kwargs): + return pyreq.getRequest(*args, **kwargs) diff --git a/module/network/__init__.py b/pyload/network/__init__.py index 8b1378917..8b1378917 100644 --- a/module/network/__init__.py +++ b/pyload/network/__init__.py diff --git a/pyload/plugins/Account.py b/pyload/plugins/Account.py new file mode 100644 index 000000000..ed0769fb4 --- /dev/null +++ b/pyload/plugins/Account.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- + +from time import time +from threading import RLock + +from pyload.Api import AccountInfo, ConfigItem +from pyload.network.CookieJar import CookieJar +from pyload.config.convert import from_string, to_configdata +from pyload.utils import to_string, compare_time, format_size, parseFileSize, lock + +from Base import Base + + +class WrongPassword(Exception): + pass + +#noinspection PyUnresolvedReferences +class Account(Base): + """ + 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. + """ + + # constants for special values + UNKNOWN = -1 + UNLIMITED = -2 + + # Default values + valid = True + validuntil = -1 + trafficleft = -1 + maxtraffic = -1 + premium = 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 + + @classmethod + def fromInfoData(cls, m, info, password, options): + return cls(m, info.loginname, info.owner, + True if info.activated else False, True if info.shared else False, password, options) + + def __init__(self, manager, loginname, owner, activated, shared, password, options): + Base.__init__(self, manager.core, owner) + + self.loginname = loginname + self.owner = owner + self.activated = activated + self.shared = shared + self.password = password + self.options = options + + self.manager = manager + + self.lock = RLock() + self.timestamp = 0 + self.login_ts = 0 # timestamp for login + self.cj = CookieJar() + self.error = None + + try: + self.config_data = dict(to_configdata(x) for x in self.__config__) + except Exception, e: + self.logError("Invalid config: %s" % e) + self.config_data = {} + + self.init() + + def toInfoData(self): + info = AccountInfo(self.__name__, self.loginname, self.owner, self.valid, self.validuntil, self.trafficleft, + self.maxtraffic, self.premium, self.activated, self.shared, self.options) + + info.config = [ConfigItem(name, item.label, item.description, item.input, + to_string(self.getConfig(name))) for name, item in + self.config_data.iteritems()] + return info + + def init(self): + pass + + def getConfig(self, option): + """ Gets an option that was configured via the account options dialog and + is only valid for this specific instance.""" + if option not in self.config_data: + return Base.getConfig(self, option) + + if option in self.options: + return self.options[option] + + return self.config_data[option].input.default_value + + def setConfig(self, option, value): + """ Sets a config value for this account instance. Modifying the global values is not allowed. """ + if option not in self.config_data: + return + + value = from_string(value, self.config_data[option].input.type) + # given value is the default value and does not need to be saved at all + if value == self.config_data[option].input.default_value: + if option in self.options: + del self.options[option] + else: + self.options[option] = from_string(value, self.config_data[option].input.type) + + def login(self, req): + """login into account, the cookies will be saved so the user can be recognized + + :param req: `Request` instance + """ + raise NotImplementedError + + def relogin(self): + """ Force a login. """ + req = self.getAccountRequest() + try: + return self._login(req) + finally: + req.close() + + @lock + def _login(self, req): + # set timestamp for login + self.login_ts = time() + + try: + 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": self.loginname + , "msg": _("Wrong Password")}) + self.valid = False + + except Exception, e: + self.logWarning( + _("Could not login with account %(user)s | %(msg)s") % {"user": self.loginname + , "msg": e}) + self.valid = False + self.core.print_exc() + + return self.valid + + def restoreDefaults(self): + self.validuntil = Account.validuntil + self.trafficleft = Account.trafficleft + self.maxtraffic = Account.maxtraffic + self.premium = Account.premium + + def setPassword(self, password): + """ updates the password and returns true if anything changed """ + + if password != self.password: + self.login_ts = 0 + self.valid = True #set valid, so the login will be retried + + self.password = password + return True + + return False + + def updateConfig(self, items): + """ Updates the accounts options from config items """ + for item in items: + # Check if a valid option + if item.name in self.config_data: + self.setConfig(item.name, item.value) + + def getAccountRequest(self): + return self.core.requestFactory.getRequest(self.cj) + + 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 + + # TODO: this method is ambiguous, it returns nothing, but just retrieves the data if needed + @lock + 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 + """ + if force or self.timestamp + self.info_threshold * 60 < time(): + + # make sure to login + req = self.getAccountRequest() + self.checkLogin(req) + self.logInfo(_("Get Account Info for %s") % self.loginname) + try: + 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)} + self.logError(_("Error: %s") % e) + finally: + req.close() + + self.logDebug("Account Info: %s" % str(infos)) + 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: + """ + pass + + def getAccountCookies(self, user): + self.logDebug("Deprecated method .getAccountCookies -> use account.cj") + return self.cj + + def getAccountData(self, *args): + self.logDebug("Deprecated method .getAccountData -> use fields directly") + return {"password": self.password, "premium": self.premium, "trafficleft": self.trafficleft, + "maxtraffic" : self.maxtraffic, "validuntil": self.validuntil} + + def isPremium(self, user=None): + if user: self.logDebug("Deprecated Argument user for .isPremium()", user) + return self.premium + + def isUsable(self): + """Check several constraints to determine if account should be used""" + + if not self.valid or not self.activated: return False + + # TODO: not in ui currently + if "time" in self.options and 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 + + 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=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) + + def expired(self, user=None): + if user: self.logDebug("Deprecated argument user for .expired()", user) + + self.logWarning(_("Account %s is expired, checking again in 1h") % user) + + self.validuntil = time() - 1 + self.scheduleRefresh(60 * 60) + + 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, 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.logInfo(_("Login with %s") % self.loginname) + + self._login(req) + return False + + return True diff --git a/pyload/plugins/Addon.py b/pyload/plugins/Addon.py new file mode 100644 index 000000000..ee8cbe62c --- /dev/null +++ b/pyload/plugins/Addon.py @@ -0,0 +1,171 @@ +# -*- 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 pyload.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 + + +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 + + #: periodic call interval in seconds + interval = 60 + + def __init__(self, core, manager, user=None): + Base.__init__(self, core, user) + + #: 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.listenTo(event, getattr(self,f)) + else: + self.evm.listenTo(event, getattr(self,funcs)) + + #delete for various reasons + self.event_map = None + + self.initPeriodical() + self.init() + + 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 getCategory(self): + return self.core.pluginManager.getCategory(self.__name__) + + def init(self): + 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
\ No newline at end of file diff --git a/pyload/plugins/Base.py b/pyload/plugins/Base.py new file mode 100644 index 000000000..abb59a7bc --- /dev/null +++ b/pyload/plugins/Base.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 sys +from time import time, sleep +from random import randint + +from pyload.utils import decode +from pyload.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 + #: When True this addon can be enabled by every user + __user_context__ = False + #: Config definition: list of (name, type, label, default_value) or + #: (name, label, desc, Input(...)) + __config__ = tuple() + #: Short description, one liner + __description__ = "" + #: More detailed text + __explanation__ = """""" + #: List of needed modules + __dependencies__ = tuple() + #: Used to assign a category for addon plugins + __category__ = "" + #: Tags to categorize the plugin, see documentation for further info + __tags__ = tuple() + #: Base64 encoded .png icon, should be 32x32, please don't use sizes above ~2KB, for bigger icons use url. + __icon__ = "" + #: Alternative, link to png icon + __icon_url__ = "" + #: Domain name of the service + __domain__ = "" + #: 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 is not None: + #: :class:`Api`, user api when user is set + self.api = self.core.api.withUserContext(user) + if not self.api: + raise Exception("Plugin running with invalid user") + + #: :class:`User`, user related to this plugin + self.user = self.api.user + else: + self.api = self.core.api + self.user = None + + #: last interaction task + self.task = None + + def __getitem__(self, item): + """ Retrieves meta data attribute """ + return getattr(self, "__%s__" % item) + + 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 getName(self): + """ Name of the plugin class """ + return self.__name__ + + 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.getConfig(option) + + def getConfig(self, option): + """ Returns config value for current plugin """ + return self.core.config.get(self.__name__, option) + + def setStorage(self, key, value): + """ Saves a value persistently to the database """ + self.core.db.setStorage(self.__name__, key, value) + + def store(self, key, value): + """ same as `setStorage` """ + self.core.db.setStorage(self.__name__, key, value) + + def getStorage(self, key=None, default=None): + """ Retrieves saved value or dict of all saved entries if key is None """ + if key is not None: + return self.core.db.getStorage(self.__name__, key) or default + return self.core.db.getStorage(self.__name__, key) + + def retrieve(self, *args, **kwargs): + """ same as `getStorage` """ + return self.getStorage(*args, **kwargs) + + def delStorage(self, key): + """ Delete entry in db """ + self.core.db.delStorage(self.__name__, key) + + 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.createCaptchaTask(img, imgtype, temp_file.name, self.__name__, result_type) + self.task = 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.")) + + 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/pyload/plugins/ContentProvider.py b/pyload/plugins/ContentProvider.py new file mode 100644 index 000000000..c30186376 --- /dev/null +++ b/pyload/plugins/ContentProvider.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 Base import Base + +class ContentProvider(Base): + """ + Base class to implement services that enables the user to search for content and add it for downloading. + """ + + def newest(self): + """ TODO """ + + def search(self, query): + """ TODO """ + + def suggest(self, query): + """ TODO """ diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py new file mode 100644 index 000000000..af3d5aba7 --- /dev/null +++ b/pyload/plugins/Crypter.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- + +from pyload.Api import LinkStatus, DownloadStatus as DS +from pyload.utils import to_list, has_method, uniqify +from pyload.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=None, links=None): + self.name = name + self.links = [] + + if links: + self.addLinks(links) + + # nested packages + self.packs = [] + + def addLinks(self, links): + """ Add one or multiple links to the package + + :param links: One or list of urls or :class:`LinkStatus` + """ + links = to_list(links) + for link in links: + if not isinstance(link, LinkStatus): + link = LinkStatus(link, link, -1, DS.Queued) + + self.links.append(link) + + def addPackage(self, pack): + self.packs.append(pack) + + def getURLs(self): + return [link.url for link in self.links] + + def getAllURLs(self): + urls = self.getURLs() + 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.links == other.links + + def __repr__(self): + return u"<CrypterPackage name=%s, links=[%s], packs=%s" % (self.name, ",".join(str(l) for l in self.links), + self.packs) + + def __hash__(self): + return hash(self.name) ^ hash(frozenset(self.links)) ^ hash(self.name) + + +class PyFileMockup: + """ Legacy class needed by old crypter plugins """ + + def __init__(self, url, pack): + self.url = url + self.name = url + self._package = None + 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:" + + #: Optional name of an account plugin that should be used, but does not guarantee that one is available + USE_ACCOUNT = None + + #: When True this crypter will not be decrypted directly and queued one by one. + # Needed for crypter that can't run in parallel or need to wait between decryption. + QUEUE_DECRYPT = False + + @classmethod + def decrypt(cls, core, url_or_urls, password=None): + """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 + :param password: optional password used for decrypting + + :raises Exception: No decryption errors are cascaded + :return: List of decrypted urls, all package info removed + """ + urls = to_list(url_or_urls) + p = cls(core, password) + try: + result = p._decrypt(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) + + # TODO: pass user to crypter + # TODO: crypter could not only know url, but also the name and size + def __init__(self, core, password=None): + Base.__init__(self, core) + + self.req = None + # load account if set + if self.USE_ACCOUNT: + self.account = self.core.accountManager.selectAccount(self.USE_ACCOUNT, self.user) + if self.account: + self.req = self.account.getAccountRequest() + + if self.req is None: + self.req = core.requestFactory.getRequest() + + #: Password supplied by user + self.password = password + + # For old style decrypter, do not use these! + self.packages = [] + self.urls = [] + 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 decryptFile(self, content): + """Decrypt file content + + :param content: content to decrypt as string + :return: See :class:`Crypter` Documentation + """ + raise NotImplementedError + + 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) + result = [] + + if urls and has_method(cls, "decrypt"): + self.logDebug("Deprecated .decrypt() method in Crypter plugin") + result = [] + for url in urls: + self.pyfile = PyFileMockup(url) + self.setup() + self.decrypt(self.pyfile) + result.extend(self.convertPackages()) + elif urls: + method = True + try: + self.setup() + result = to_list(self.decryptURLs(urls)) + except NotImplementedError: + method = False + + # this will raise error if not implemented + if not method: + for url in urls: + self.setup() + result.extend(to_list(self.decryptURL(url))) + + for f, c in content: + self.setup() + result.extend(to_list(self.decryptFile(c))) + try: + if f.startswith("tmp_"): remove(f) + except IOError: + self.logWarning(_("Could not remove file '%s'") % f) + self.core.print_exc() + + return result + + 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 + + def retry(self): + """ Retry decrypting, will only work once. Somewhat deprecated method, should be avoided. """ + raise Retry() + + 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/pyload/plugins/Download.py b/pyload/plugins/Download.py new file mode 100644 index 000000000..e86089dc3 --- /dev/null +++ b/pyload/plugins/Download.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from Request import Request + +class Download(Request): + """ Abstract class for download request """ + + __version__ = "0.1" + + def __init__(self, bucket, request=None): + # Copies the context + context = request.getContext() if request else [{}] + Request.__init__(self, *context) + + self._running = False + self._name = None + self._size = 0 + + #: bucket used for rate limiting + self.bucket = bucket + + def download(self, uri, path, *args, **kwargs): + """ Downloads the resource with additional options depending on implementation """ + raise NotImplementedError + + @property + def running(self): + return self._running + + @property + def size(self): + """ Size in bytes """ + return self._size + + @property + def name(self): + """ Name of the resource if known """ + return self._name + + @property + def speed(self): + """ Download rate in bytes per second """ + return 0 + + @property + def arrived(self): + """ Number of bytes already loaded """ + return 0
\ No newline at end of file diff --git a/pyload/plugins/Hoster.py b/pyload/plugins/Hoster.py new file mode 100644 index 000000000..976918c0d --- /dev/null +++ b/pyload/plugins/Hoster.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- + +import os +from time import time + +if os.name != "nt": + from pyload.utils.fs import chown + from pwd import getpwnam + from grp import getgrnam + +from pyload.utils import chunks as _chunks +from pyload.utils.fs import save_join, save_filename, fs_encode, fs_decode, \ + remove, makedirs, chmod, stat, exists, join + +from Base import Base, Fail, Retry +from network.DefaultRequest import DefaultRequest, DefaultDownload + +# 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. + """ + + #: Class used to make requests with `self.load` + REQUEST_CLASS = DefaultRequest + + #: Class used to make download + DOWNLOAD_CLASS = DefaultDownload + + @staticmethod + def getInfo(urls): + """This method is used to retrieve the online status of files for hoster plugins. + + :param urls: List of urls + :return: yield list of :class:`LinkStatus` as result + """ + pass + + def __init__(self, pyfile): + # TODO: pyfile.owner, but it's not correct yet + 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 + + #: plugin is waiting + self.waiting = False + + self.ocr = None #captcha reader instance + #: account handler instance, see :py:class:`Account` + self.account = self.core.accountManager.selectAccount(self.__name__, self.user) + + #: premium status + self.premium = False + + if self.account: + #: Request instance bound to account + 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(klass=self.REQUEST_CLASS) + + #: Will hold the download class + self.dl = None + + #: 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.reset() + + 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=None): + """Set a specific wait time later used with `wait` + + :param seconds: wait time in seconds + :param reconnect: True if a reconnect would avoid wait time + """ + if reconnect is not None: + self.wantReconnect = reconnect + self.pyfile.waitUntil = time() + int(seconds) + + def wait(self, seconds=None, reconnect=None): + """ Waits the time previously set or use these from arguments. See `setWait` + """ + if seconds is not None: + self.setWait(seconds, reconnect) + + self._wait() + + def _wait(self): + self.waiting = True + self.pyfile.setStatus("waiting") + + while self.pyfile.waitUntil > time(): + self.thread.m.reconnecting.wait(2) + + 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="", backoff=lambda x,y: x): + """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 + :param backoff: Function to backoff the wait time, takes initial time and number of retry as argument. + defaults to no backoff / fixed wait time + """ + if 0 < max_tries <= self.retries: + if not reason: reason = "Max retries reached" + raise Fail(reason) + + self.wantReconnect = False + self.retries += 1 + self.setWait(backoff(wait_time, self.retries)) + self.wait() + + raise Retry(reason) + + def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False): + """Downloads the content at url to download folder + + :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("download:start", self.pyfile, url, filename) + + # Create the class used for downloading + self.dl = self.core.requestFactory.getDownloadRequest(self.req, self.DOWNLOAD_CLASS) + try: + # TODO: hardcoded arguments + newname = self.dl.download(url, filename, get=get, post=post, referer=ref, chunks=self.getChunkCount(), + resume=self.resumeDownload, cookies=cookies, disposition=disposition) + finally: + self.dl.close() + self.pyfile.size = self.dl.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.cachedFiles(): + 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]) + + 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, "dl"): + del self.dl + if hasattr(self, "thread"): + del self.thread + if hasattr(self, "html"): + del self.html diff --git a/pyload/plugins/MultiHoster.py b/pyload/plugins/MultiHoster.py new file mode 100644 index 000000000..6b48e99fb --- /dev/null +++ b/pyload/plugins/MultiHoster.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from time import time + +from pyload.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"), ("uploaded.net", "uploaded.to")] + + #: 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/pyload/plugins/Plugin.py b/pyload/plugins/Plugin.py new file mode 100644 index 000000000..0abb2644f --- /dev/null +++ b/pyload/plugins/Plugin.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +print "Deprecated usage of plugins.Plugin -> use plugins.Base" +from .Base import * +from pyload.utils import chunks + +Plugin = Base + diff --git a/pyload/plugins/Request.py b/pyload/plugins/Request.py new file mode 100644 index 000000000..8e8e0cc6b --- /dev/null +++ b/pyload/plugins/Request.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +from logging import getLogger + + +class ResponseException(Exception): + def __init__(self, code, content=""): + Exception.__init__(self, "Server response error: %s %s" % (code, content)) + self.code = code + +class Request(object): + """ Abstract class to support different types of request, most methods should be overwritten """ + + __version__ = "0.1" + + #: Class that will be instantiated and associated with the request, and if needed copied and reused + CONTEXT_CLASS = None + + def __init__(self, config, context=None, options=None): + self.log = getLogger("log") + + # Global config, holds some configurable parameter + self.config = config + + # Create a new context if not given + if context is None and self.CONTEXT_CLASS is not None: + self.context = self.CONTEXT_CLASS() + else: + self.context = context + + # Store options in dict + self.options = {} if options is None else options + + # Last response code + self.code = 0 + self.doAbort = False + self.initContext() + + # TODO: content encoding? Could be handled globally + + def initContext(self): + """ Should be used to initialize everything from given context and options """ + pass + + def getContext(self): + """ Retrieves complete state that is needed to copy the request context """ + return self.config, self.context, self.options + + def setContext(self, *args): + """ Sets request context """ + self.config, self.context, self.options = args + + def setOption(self, name, value): + """ Sets an option """ + self.options[name] = value + + def unsetOption(self, name): + """ Removes a specific option or reset everything on empty string """ + if name == "": + self.options.clear() + else: + del self.options[name] + + def load(self, uri, *args, **kwargs): + """ Loads given resource from given uri. Args and kwargs depends on implementation""" + raise NotImplementedError + + def abort(self): + self.doAbort = True + + def reset(self): + """ Resets the context to initial state """ + self.unsetOption("") + + def close(self): + """ Close and clean everything """ + pass
\ No newline at end of file diff --git a/module/plugins/__init__.py b/pyload/plugins/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/__init__.py +++ b/pyload/plugins/__init__.py diff --git a/module/plugins/accounts/AlldebridCom.py b/pyload/plugins/accounts/AlldebridCom.py index 9fb050535..9fb050535 100644 --- a/module/plugins/accounts/AlldebridCom.py +++ b/pyload/plugins/accounts/AlldebridCom.py diff --git a/module/plugins/accounts/BayfilesCom.py b/pyload/plugins/accounts/BayfilesCom.py index bf5cc54af..bf5cc54af 100644 --- a/module/plugins/accounts/BayfilesCom.py +++ b/pyload/plugins/accounts/BayfilesCom.py diff --git a/module/plugins/accounts/BitshareCom.py b/pyload/plugins/accounts/BitshareCom.py index 39cff36eb..39cff36eb 100644 --- a/module/plugins/accounts/BitshareCom.py +++ b/pyload/plugins/accounts/BitshareCom.py diff --git a/module/plugins/accounts/BoltsharingCom.py b/pyload/plugins/accounts/BoltsharingCom.py index 76e010532..76e010532 100644 --- a/module/plugins/accounts/BoltsharingCom.py +++ b/pyload/plugins/accounts/BoltsharingCom.py diff --git a/module/plugins/accounts/CramitIn.py b/pyload/plugins/accounts/CramitIn.py index b0334b191..b0334b191 100644 --- a/module/plugins/accounts/CramitIn.py +++ b/pyload/plugins/accounts/CramitIn.py diff --git a/module/plugins/accounts/CyberlockerCh.py b/pyload/plugins/accounts/CyberlockerCh.py index 0eaa262eb..0eaa262eb 100644 --- a/module/plugins/accounts/CyberlockerCh.py +++ b/pyload/plugins/accounts/CyberlockerCh.py diff --git a/module/plugins/accounts/CzshareCom.py b/pyload/plugins/accounts/CzshareCom.py index 7b1a8edc5..7b1a8edc5 100644 --- a/module/plugins/accounts/CzshareCom.py +++ b/pyload/plugins/accounts/CzshareCom.py diff --git a/module/plugins/accounts/DdlstorageCom.py b/pyload/plugins/accounts/DdlstorageCom.py index 7404348a4..7404348a4 100644 --- a/module/plugins/accounts/DdlstorageCom.py +++ b/pyload/plugins/accounts/DdlstorageCom.py diff --git a/pyload/plugins/accounts/DebridItaliaCom.py b/pyload/plugins/accounts/DebridItaliaCom.py new file mode 100644 index 000000000..30bfa65f9 --- /dev/null +++ b/pyload/plugins/accounts/DebridItaliaCom.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# 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. # +# # +# 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 Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import re +import time + +from pyload.plugins.MultiHoster import MultiHoster + + +class DebridItaliaCom(MultiHoster): + __name__ = "DebridItaliaCom" + __version__ = "0.1" + __type__ = "account" + __description__ = """debriditalia.com account plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + WALID_UNTIL_PATTERN = r"Premium valid till: (?P<D>[^|]+) \|" + + def loadAccountInfo(self, user, req): + if 'Account premium not activated' in self.html: + return {"premium": False, "validuntil": None, "trafficleft": None} + + m = re.search(self.WALID_UNTIL_PATTERN, self.html) + if m: + validuntil = int(time.mktime(time.strptime(m.group('D'), "%d/%m/%Y %H:%M"))) + return {"premium": True, "validuntil": validuntil, "trafficleft": -1} + else: + self.logError('Unable to retrieve account information - Plugin may be out of date') + + def login(self, user, data, req): + self.html = req.load("http://debriditalia.com/login.php", + get={"u": user, "p": data["password"]}) + if 'NO' in self.html: + self.wrongPassword() + + def loadHosterList(self, req): + return ["netload.in", "hotfile.com", "rapidshare.com", "multiupload.com", + "uploading.com", "megashares.com", "crocko.com", "filepost.com", + "bitshare.com", "share-links.biz", "putlocker.com", "uploaded.to", + "speedload.org", "rapidgator.net", "likeupload.net", "cyberlocker.ch", + "depositfiles.com", "extabit.com", "filefactory.com", "sharefiles.co", + "ryushare.com", "tusfiles.net", "nowvideo.co", "cloudzer.net", "letitbit.net", + "easybytez.com", "uptobox.com", "ddlstorage.com"] diff --git a/module/plugins/accounts/DepositfilesCom.py b/pyload/plugins/accounts/DepositfilesCom.py index 5f2408e72..5f2408e72 100644 --- a/module/plugins/accounts/DepositfilesCom.py +++ b/pyload/plugins/accounts/DepositfilesCom.py diff --git a/module/plugins/accounts/EasybytezCom.py b/pyload/plugins/accounts/EasybytezCom.py index cd995fbe5..cd995fbe5 100644 --- a/module/plugins/accounts/EasybytezCom.py +++ b/pyload/plugins/accounts/EasybytezCom.py diff --git a/module/plugins/accounts/EgoFilesCom.py b/pyload/plugins/accounts/EgoFilesCom.py index 9c2b918c3..9c2b918c3 100644 --- a/module/plugins/accounts/EgoFilesCom.py +++ b/pyload/plugins/accounts/EgoFilesCom.py diff --git a/module/plugins/accounts/EuroshareEu.py b/pyload/plugins/accounts/EuroshareEu.py index 830c1db3f..830c1db3f 100644 --- a/module/plugins/accounts/EuroshareEu.py +++ b/pyload/plugins/accounts/EuroshareEu.py diff --git a/module/plugins/accounts/FastixRu.py b/pyload/plugins/accounts/FastixRu.py index 3e1896e44..3e1896e44 100644 --- a/module/plugins/accounts/FastixRu.py +++ b/pyload/plugins/accounts/FastixRu.py diff --git a/module/plugins/accounts/FastshareCz.py b/pyload/plugins/accounts/FastshareCz.py index c047ff766..c047ff766 100644 --- a/module/plugins/accounts/FastshareCz.py +++ b/pyload/plugins/accounts/FastshareCz.py diff --git a/module/plugins/accounts/FilecloudIo.py b/pyload/plugins/accounts/FilecloudIo.py index 93ae02006..93ae02006 100644 --- a/module/plugins/accounts/FilecloudIo.py +++ b/pyload/plugins/accounts/FilecloudIo.py diff --git a/module/plugins/accounts/FilefactoryCom.py b/pyload/plugins/accounts/FilefactoryCom.py index 679409058..679409058 100644 --- a/module/plugins/accounts/FilefactoryCom.py +++ b/pyload/plugins/accounts/FilefactoryCom.py diff --git a/module/plugins/accounts/FilejungleCom.py b/pyload/plugins/accounts/FilejungleCom.py index 2f2a6012d..2f2a6012d 100644 --- a/module/plugins/accounts/FilejungleCom.py +++ b/pyload/plugins/accounts/FilejungleCom.py diff --git a/module/plugins/accounts/FilerNet.py b/pyload/plugins/accounts/FilerNet.py index 45ce5ab37..45ce5ab37 100644 --- a/module/plugins/accounts/FilerNet.py +++ b/pyload/plugins/accounts/FilerNet.py diff --git a/module/plugins/accounts/FilerioCom.py b/pyload/plugins/accounts/FilerioCom.py index 8b0b5f54f..8b0b5f54f 100644 --- a/module/plugins/accounts/FilerioCom.py +++ b/pyload/plugins/accounts/FilerioCom.py diff --git a/module/plugins/accounts/FilesMailRu.py b/pyload/plugins/accounts/FilesMailRu.py index ea976bd44..ea976bd44 100644 --- a/module/plugins/accounts/FilesMailRu.py +++ b/pyload/plugins/accounts/FilesMailRu.py diff --git a/module/plugins/accounts/FileserveCom.py b/pyload/plugins/accounts/FileserveCom.py index d4056891a..d4056891a 100644 --- a/module/plugins/accounts/FileserveCom.py +++ b/pyload/plugins/accounts/FileserveCom.py diff --git a/module/plugins/accounts/FourSharedCom.py b/pyload/plugins/accounts/FourSharedCom.py index 69a465671..69a465671 100644 --- a/module/plugins/accounts/FourSharedCom.py +++ b/pyload/plugins/accounts/FourSharedCom.py diff --git a/module/plugins/accounts/FreakshareCom.py b/pyload/plugins/accounts/FreakshareCom.py index cdf45114a..cdf45114a 100644 --- a/module/plugins/accounts/FreakshareCom.py +++ b/pyload/plugins/accounts/FreakshareCom.py diff --git a/module/plugins/accounts/FshareVn.py b/pyload/plugins/accounts/FshareVn.py index 75191e74a..75191e74a 100644 --- a/module/plugins/accounts/FshareVn.py +++ b/pyload/plugins/accounts/FshareVn.py diff --git a/module/plugins/accounts/Ftp.py b/pyload/plugins/accounts/Ftp.py index 681d14cea..681d14cea 100644 --- a/module/plugins/accounts/Ftp.py +++ b/pyload/plugins/accounts/Ftp.py diff --git a/module/plugins/accounts/HellshareCz.py b/pyload/plugins/accounts/HellshareCz.py index 4718ade99..4718ade99 100644 --- a/module/plugins/accounts/HellshareCz.py +++ b/pyload/plugins/accounts/HellshareCz.py diff --git a/module/plugins/accounts/HellspyCz.py b/pyload/plugins/accounts/HellspyCz.py index 1bb574731..1bb574731 100644 --- a/module/plugins/accounts/HellspyCz.py +++ b/pyload/plugins/accounts/HellspyCz.py diff --git a/module/plugins/accounts/HotfileCom.py b/pyload/plugins/accounts/HotfileCom.py index 4c144a883..4c144a883 100644 --- a/module/plugins/accounts/HotfileCom.py +++ b/pyload/plugins/accounts/HotfileCom.py diff --git a/module/plugins/accounts/Http.py b/pyload/plugins/accounts/Http.py index 5701d1f03..5701d1f03 100644 --- a/module/plugins/accounts/Http.py +++ b/pyload/plugins/accounts/Http.py diff --git a/module/plugins/accounts/LetitbitNet.py b/pyload/plugins/accounts/LetitbitNet.py index bcc004b91..bcc004b91 100644 --- a/module/plugins/accounts/LetitbitNet.py +++ b/pyload/plugins/accounts/LetitbitNet.py diff --git a/module/plugins/accounts/MegasharesCom.py b/pyload/plugins/accounts/MegasharesCom.py index e7d5f9ca9..e7d5f9ca9 100644 --- a/module/plugins/accounts/MegasharesCom.py +++ b/pyload/plugins/accounts/MegasharesCom.py diff --git a/pyload/plugins/accounts/MultiDebridCom.py b/pyload/plugins/accounts/MultiDebridCom.py new file mode 100644 index 000000000..a98b8abae --- /dev/null +++ b/pyload/plugins/accounts/MultiDebridCom.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# 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. # +# # +# 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 Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +from time import time + +from pyload.plugins.MultiHoster import MultiHoster +from pyload.utils import json_loads + + +class MultiDebridCom(MultiHoster): + __name__ = "MultiDebridCom" + __version__ = "0.01" + __type__ = "account" + __description__ = """Multi-debrid.com account plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + def loadAccountInfo(self, user, req): + if 'days_left' in self.json_data: + validuntil = int(time() + self.json_data['days_left'] * 86400) + return {"premium": True, "validuntil": validuntil, "trafficleft": -1} + else: + self.logError('Unable to get account information') + + def login(self, user, data, req): + # Password to use is the API-Password written in http://multi-debrid.com/myaccount + self.html = req.load("http://multi-debrid.com/api.php", + get={"user": user, "pass": data["password"]}) + self.logDebug('JSON data: ' + self.html) + self.json_data = json_loads(self.html) + if self.json_data['status'] != 'ok': + self.logError('Invalid login. The password to use is the API-Password you find in your "My Account" page') + self.wrongPassword() + + def loadHosterList(self, req): + json_data = req.load('http://multi-debrid.com/api.php?hosts', decode=True) + self.logDebug('JSON data: ' + json_data) + json_data = json_loads(json_data) + + return json_data['hosts'] diff --git a/module/plugins/accounts/MultishareCz.py b/pyload/plugins/accounts/MultishareCz.py index 273936615..273936615 100644 --- a/module/plugins/accounts/MultishareCz.py +++ b/pyload/plugins/accounts/MultishareCz.py diff --git a/module/plugins/accounts/NetloadIn.py b/pyload/plugins/accounts/NetloadIn.py index 3e3bd93c1..3e3bd93c1 100755 --- a/module/plugins/accounts/NetloadIn.py +++ b/pyload/plugins/accounts/NetloadIn.py diff --git a/module/plugins/accounts/Premium4Me.py b/pyload/plugins/accounts/Premium4Me.py index 467c5943e..467c5943e 100644 --- a/module/plugins/accounts/Premium4Me.py +++ b/pyload/plugins/accounts/Premium4Me.py diff --git a/module/plugins/accounts/PremiumizeMe.py b/pyload/plugins/accounts/PremiumizeMe.py index c5c712c52..c5c712c52 100644 --- a/module/plugins/accounts/PremiumizeMe.py +++ b/pyload/plugins/accounts/PremiumizeMe.py diff --git a/module/plugins/accounts/QuickshareCz.py b/pyload/plugins/accounts/QuickshareCz.py index 1af7cbffd..1af7cbffd 100644 --- a/module/plugins/accounts/QuickshareCz.py +++ b/pyload/plugins/accounts/QuickshareCz.py diff --git a/module/plugins/accounts/RPNetBiz.py b/pyload/plugins/accounts/RPNetBiz.py index ceb5e6bbb..ceb5e6bbb 100644 --- a/module/plugins/accounts/RPNetBiz.py +++ b/pyload/plugins/accounts/RPNetBiz.py diff --git a/module/plugins/accounts/RapidgatorNet.py b/pyload/plugins/accounts/RapidgatorNet.py index 8a02b712c..8a02b712c 100644 --- a/module/plugins/accounts/RapidgatorNet.py +++ b/pyload/plugins/accounts/RapidgatorNet.py diff --git a/module/plugins/accounts/RapidshareCom.py b/pyload/plugins/accounts/RapidshareCom.py index b2066cd1e..b2066cd1e 100644 --- a/module/plugins/accounts/RapidshareCom.py +++ b/pyload/plugins/accounts/RapidshareCom.py diff --git a/module/plugins/accounts/RarefileNet.py b/pyload/plugins/accounts/RarefileNet.py index f4ae4a79a..f4ae4a79a 100644 --- a/module/plugins/accounts/RarefileNet.py +++ b/pyload/plugins/accounts/RarefileNet.py diff --git a/module/plugins/accounts/RealdebridCom.py b/pyload/plugins/accounts/RealdebridCom.py index a9980b088..a9980b088 100644 --- a/module/plugins/accounts/RealdebridCom.py +++ b/pyload/plugins/accounts/RealdebridCom.py diff --git a/module/plugins/accounts/RehostTo.py b/pyload/plugins/accounts/RehostTo.py index 58c91801b..58c91801b 100644 --- a/module/plugins/accounts/RehostTo.py +++ b/pyload/plugins/accounts/RehostTo.py diff --git a/module/plugins/accounts/ReloadCc.py b/pyload/plugins/accounts/ReloadCc.py index b3c26c6d3..b3c26c6d3 100644 --- a/module/plugins/accounts/ReloadCc.py +++ b/pyload/plugins/accounts/ReloadCc.py diff --git a/module/plugins/accounts/RyushareCom.py b/pyload/plugins/accounts/RyushareCom.py index 484de2d7a..484de2d7a 100644 --- a/module/plugins/accounts/RyushareCom.py +++ b/pyload/plugins/accounts/RyushareCom.py diff --git a/module/plugins/accounts/Share76Com.py b/pyload/plugins/accounts/Share76Com.py index 1dfcd8510..1dfcd8510 100644 --- a/module/plugins/accounts/Share76Com.py +++ b/pyload/plugins/accounts/Share76Com.py diff --git a/module/plugins/accounts/ShareFilesCo.py b/pyload/plugins/accounts/ShareFilesCo.py index cff52d570..cff52d570 100644 --- a/module/plugins/accounts/ShareFilesCo.py +++ b/pyload/plugins/accounts/ShareFilesCo.py diff --git a/module/plugins/accounts/ShareRapidCom.py b/pyload/plugins/accounts/ShareRapidCom.py index b532768b1..b532768b1 100644 --- a/module/plugins/accounts/ShareRapidCom.py +++ b/pyload/plugins/accounts/ShareRapidCom.py diff --git a/module/plugins/accounts/ShareonlineBiz.py b/pyload/plugins/accounts/ShareonlineBiz.py index 03babb5e6..03babb5e6 100644 --- a/module/plugins/accounts/ShareonlineBiz.py +++ b/pyload/plugins/accounts/ShareonlineBiz.py diff --git a/module/plugins/accounts/SimplydebridCom.py b/pyload/plugins/accounts/SimplydebridCom.py index 82b499bbd..82b499bbd 100644 --- a/module/plugins/accounts/SimplydebridCom.py +++ b/pyload/plugins/accounts/SimplydebridCom.py diff --git a/module/plugins/accounts/StahnuTo.py b/pyload/plugins/accounts/StahnuTo.py index 6e2b1c96d..6e2b1c96d 100644 --- a/module/plugins/accounts/StahnuTo.py +++ b/pyload/plugins/accounts/StahnuTo.py diff --git a/module/plugins/accounts/TurbobitNet.py b/pyload/plugins/accounts/TurbobitNet.py index 5d4471c30..5d4471c30 100644 --- a/module/plugins/accounts/TurbobitNet.py +++ b/pyload/plugins/accounts/TurbobitNet.py diff --git a/module/plugins/accounts/UlozTo.py b/pyload/plugins/accounts/UlozTo.py index 8a281389e..8a281389e 100644 --- a/module/plugins/accounts/UlozTo.py +++ b/pyload/plugins/accounts/UlozTo.py diff --git a/pyload/plugins/accounts/UnrestrictLi.py b/pyload/plugins/accounts/UnrestrictLi.py new file mode 100644 index 000000000..bfa81a6ef --- /dev/null +++ b/pyload/plugins/accounts/UnrestrictLi.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# 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. # +# # +# 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 Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +from pyload.plugins.MultiHoster import MultiHoster +from pyload.utils import json_loads + + +class UnrestrictLi(MultiHoster): + __name__ = "UnrestrictLi" + __version__ = "0.03" + __type__ = "account" + __description__ = """Unrestrict.li account plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + def loadAccountInfo(self, user, req): + json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json') + self.logDebug("JSON data: " + json_data) + json_data = json_loads(json_data) + + if 'vip' in json_data['result'] and json_data['result']['vip'] == 0: + return {"premium": False} + + validuntil = json_data['result']['expires'] + trafficleft = int(json_data['result']['traffic'] / 1024) + + return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft} + + def login(self, user, data, req): + req.cj.setCookie("unrestrict.li", "lang", "EN") + html = req.load("https://unrestrict.li/sign_in") + + if 'solvemedia' in html: + self.logError("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry") + return + + post_data = {"username": user, "password": data["password"], + "remember_me": "remember", "signin": "Sign in"} + self.html = req.load("https://unrestrict.li/sign_in", post=post_data) + + if 'sign_out' not in self.html: + self.wrongPassword() + + def loadHosterList(self, req): + json_data = req.load('http://unrestrict.li/api/jdownloader/hosts.php?format=json') + json_data = json_loads(json_data) + + host_list = [element['host'] for element in json_data['result']] + + return host_list diff --git a/module/plugins/accounts/UploadedTo.py b/pyload/plugins/accounts/UploadedTo.py index ac1f3fd35..ac1f3fd35 100644 --- a/module/plugins/accounts/UploadedTo.py +++ b/pyload/plugins/accounts/UploadedTo.py diff --git a/module/plugins/accounts/UploadheroCom.py b/pyload/plugins/accounts/UploadheroCom.py index 12463f285..12463f285 100644 --- a/module/plugins/accounts/UploadheroCom.py +++ b/pyload/plugins/accounts/UploadheroCom.py diff --git a/module/plugins/accounts/UploadingCom.py b/pyload/plugins/accounts/UploadingCom.py index d13df4f9d..d13df4f9d 100644 --- a/module/plugins/accounts/UploadingCom.py +++ b/pyload/plugins/accounts/UploadingCom.py diff --git a/module/plugins/accounts/UploadstationCom.py b/pyload/plugins/accounts/UploadstationCom.py index 040f6bbef..040f6bbef 100644 --- a/module/plugins/accounts/UploadstationCom.py +++ b/pyload/plugins/accounts/UploadstationCom.py diff --git a/module/plugins/accounts/UptoboxCom.py b/pyload/plugins/accounts/UptoboxCom.py index 7fc62694a..7fc62694a 100644 --- a/module/plugins/accounts/UptoboxCom.py +++ b/pyload/plugins/accounts/UptoboxCom.py diff --git a/module/plugins/accounts/WarserverCz.py b/pyload/plugins/accounts/WarserverCz.py index 2c871eb6d..2c871eb6d 100644 --- a/module/plugins/accounts/WarserverCz.py +++ b/pyload/plugins/accounts/WarserverCz.py diff --git a/module/plugins/accounts/WuploadCom.py b/pyload/plugins/accounts/WuploadCom.py index 2a8bfee8e..2a8bfee8e 100644 --- a/module/plugins/accounts/WuploadCom.py +++ b/pyload/plugins/accounts/WuploadCom.py diff --git a/module/plugins/accounts/X7To.py b/pyload/plugins/accounts/X7To.py index 34a7b99be..34a7b99be 100644 --- a/module/plugins/accounts/X7To.py +++ b/pyload/plugins/accounts/X7To.py diff --git a/module/plugins/accounts/YibaishiwuCom.py b/pyload/plugins/accounts/YibaishiwuCom.py index c4eab74dc..c4eab74dc 100644 --- a/module/plugins/accounts/YibaishiwuCom.py +++ b/pyload/plugins/accounts/YibaishiwuCom.py diff --git a/module/plugins/accounts/ZeveraCom.py b/pyload/plugins/accounts/ZeveraCom.py index 13ed95133..13ed95133 100644 --- a/module/plugins/accounts/ZeveraCom.py +++ b/pyload/plugins/accounts/ZeveraCom.py diff --git a/module/plugins/accounts/__init__.py b/pyload/plugins/accounts/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/accounts/__init__.py +++ b/pyload/plugins/accounts/__init__.py diff --git a/module/plugins/hooks/AlldebridCom.py b/pyload/plugins/addons/AlldebridCom.py index d0e9b1f77..d0e9b1f77 100644 --- a/module/plugins/hooks/AlldebridCom.py +++ b/pyload/plugins/addons/AlldebridCom.py diff --git a/module/plugins/hooks/BypassCaptcha.py b/pyload/plugins/addons/BypassCaptcha.py index bd718ea7e..bd718ea7e 100644 --- a/module/plugins/hooks/BypassCaptcha.py +++ b/pyload/plugins/addons/BypassCaptcha.py diff --git a/module/plugins/hooks/Captcha9kw.py b/pyload/plugins/addons/Captcha9kw.py index 6a9de24de..6a9de24de 100755 --- a/module/plugins/hooks/Captcha9kw.py +++ b/pyload/plugins/addons/Captcha9kw.py diff --git a/module/plugins/hooks/CaptchaBrotherhood.py b/pyload/plugins/addons/CaptchaBrotherhood.py index 69af96705..69af96705 100644 --- a/module/plugins/hooks/CaptchaBrotherhood.py +++ b/pyload/plugins/addons/CaptchaBrotherhood.py diff --git a/module/plugins/hooks/CaptchaTrader.py b/pyload/plugins/addons/CaptchaTrader.py index 51bb75a17..51bb75a17 100644 --- a/module/plugins/hooks/CaptchaTrader.py +++ b/pyload/plugins/addons/CaptchaTrader.py diff --git a/module/plugins/hooks/Checksum.py b/pyload/plugins/addons/Checksum.py index 081e8ac3b..081e8ac3b 100644 --- a/module/plugins/hooks/Checksum.py +++ b/pyload/plugins/addons/Checksum.py diff --git a/module/plugins/hooks/ClickAndLoad.py b/pyload/plugins/addons/ClickAndLoad.py index 0fc78abfe..0fc78abfe 100644 --- a/module/plugins/hooks/ClickAndLoad.py +++ b/pyload/plugins/addons/ClickAndLoad.py diff --git a/module/plugins/hooks/DeathByCaptcha.py b/pyload/plugins/addons/DeathByCaptcha.py index 7de4f4f2c..7de4f4f2c 100644 --- a/module/plugins/hooks/DeathByCaptcha.py +++ b/pyload/plugins/addons/DeathByCaptcha.py diff --git a/module/plugins/hooks/DebridItaliaCom.py b/pyload/plugins/addons/DebridItaliaCom.py index 71ebac85c..71ebac85c 100644 --- a/module/plugins/hooks/DebridItaliaCom.py +++ b/pyload/plugins/addons/DebridItaliaCom.py diff --git a/module/plugins/hooks/DeleteFinished.py b/pyload/plugins/addons/DeleteFinished.py index 3bc98a7b3..3bc98a7b3 100644 --- a/module/plugins/hooks/DeleteFinished.py +++ b/pyload/plugins/addons/DeleteFinished.py diff --git a/module/plugins/hooks/DownloadScheduler.py b/pyload/plugins/addons/DownloadScheduler.py index 4049d71c5..4049d71c5 100644 --- a/module/plugins/hooks/DownloadScheduler.py +++ b/pyload/plugins/addons/DownloadScheduler.py diff --git a/module/plugins/hooks/EasybytezCom.py b/pyload/plugins/addons/EasybytezCom.py index cc55da9c0..cc55da9c0 100644 --- a/module/plugins/hooks/EasybytezCom.py +++ b/pyload/plugins/addons/EasybytezCom.py diff --git a/module/plugins/hooks/Ev0InFetcher.py b/pyload/plugins/addons/Ev0InFetcher.py index 912cb5964..912cb5964 100644 --- a/module/plugins/hooks/Ev0InFetcher.py +++ b/pyload/plugins/addons/Ev0InFetcher.py diff --git a/module/plugins/hooks/ExpertDecoders.py b/pyload/plugins/addons/ExpertDecoders.py index f1b7ea352..f1b7ea352 100644 --- a/module/plugins/hooks/ExpertDecoders.py +++ b/pyload/plugins/addons/ExpertDecoders.py diff --git a/module/plugins/hooks/ExternalScripts.py b/pyload/plugins/addons/ExternalScripts.py index de8afda5f..de8afda5f 100644 --- a/module/plugins/hooks/ExternalScripts.py +++ b/pyload/plugins/addons/ExternalScripts.py diff --git a/module/plugins/hooks/ExtractArchive.py b/pyload/plugins/addons/ExtractArchive.py index be023301c..be023301c 100644 --- a/module/plugins/hooks/ExtractArchive.py +++ b/pyload/plugins/addons/ExtractArchive.py diff --git a/module/plugins/hooks/FastixRu.py b/pyload/plugins/addons/FastixRu.py index 25c9a1a67..25c9a1a67 100644 --- a/module/plugins/hooks/FastixRu.py +++ b/pyload/plugins/addons/FastixRu.py diff --git a/module/plugins/hooks/HotFolder.py b/pyload/plugins/addons/HotFolder.py index e44c1e172..e44c1e172 100644 --- a/module/plugins/hooks/HotFolder.py +++ b/pyload/plugins/addons/HotFolder.py diff --git a/pyload/plugins/addons/IRCInterface.py b/pyload/plugins/addons/IRCInterface.py new file mode 100644 index 000000000..66fd15dcc --- /dev/null +++ b/pyload/plugins/addons/IRCInterface.py @@ -0,0 +1,422 @@ +# -*- 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 + @author: jeix + @interface-version: 0.2 +""" + +from select import select +import socket +from threading import Thread +import time +from time import sleep +from traceback import print_exc +import re +from pycurl import FORM_FILE + +from module.plugins.Addon import Addon +from module.network.RequestFactory import getURL +from module.utils import formatSize +from module.Api import PackageDoesNotExists, FileDoesNotExists + + +class IRCInterface(Thread, Addon): + __name__ = "IRCInterface" + __version__ = "0.11" + __description__ = """connect to irc and let owner perform different tasks""" + __config__ = [("activated", "bool", "Activated", "False"), + ("host", "str", "IRC-Server Address", "Enter your server here!"), + ("port", "int", "IRC-Server Port", "6667"), + ("ident", "str", "Clients ident", "pyload-irc"), + ("realname", "str", "Realname", "pyload-irc"), + ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"), + ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"), + ("info_file", "bool", "Inform about every file finished", "False"), + ("info_pack", "bool", "Inform about every package finished", "True"), + ("captcha", "bool", "Send captcha requests", "True")] + __author_name__ = ("Jeix") + __author_mail__ = ("Jeix@hasnomail.com") + + def __init__(self, core, manager): + Thread.__init__(self) + Addon.__init__(self, core, manager) + self.setDaemon(True) + # self.sm = core.server_methods + self.api = core.api # todo, only use api + + def coreReady(self): + self.new_package = {} + + self.abort = False + + self.links_added = 0 + self.more = [] + + self.start() + + def packageFinished(self, pypack): + try: + if self.getConfig("info_pack"): + self.response(_("Package finished: %s") % pypack.name) + except: + pass + + def downloadFinished(self, pyfile): + try: + if self.getConfig("info_file"): + self.response( + _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname}) + except: + pass + + def newCaptchaTask(self, task): + if self.getConfig("captcha") and task.isTextual(): + task.handler.append(self) + task.setWaiting(60) + + page = getURL("http://www.freeimagehosting.net/upload.php", + post={"attached": (FORM_FILE, task.captchaFile)}, multipart=True) + + url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).group(1) + self.response(_("New Captcha Request: %s") % url) + self.response(_("Answer with 'c %s text on the captcha'") % task.id) + + def run(self): + # connect to IRC etc. + self.sock = socket.socket() + host = self.getConfig("host") + self.sock.connect((host, self.getConfig("port"))) + nick = self.getConfig("nick") + self.sock.send("NICK %s\r\n" % nick) + self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick)) + for t in self.getConfig("owner").split(): + if t.strip().startswith("#"): + self.sock.send("JOIN %s\r\n" % t.strip()) + self.logInfo("pyLoad IRC: Connected to %s!" % host) + self.logInfo("pyLoad IRC: Switching to listening mode!") + try: + self.main_loop() + + except IRCError, ex: + self.sock.send("QUIT :byebye\r\n") + print_exc() + self.sock.close() + + def main_loop(self): + readbuffer = "" + while True: + sleep(1) + fdset = select([self.sock], [], [], 0) + if self.sock not in fdset[0]: + continue + + if self.abort: + raise IRCError("quit") + + readbuffer += self.sock.recv(1024) + temp = readbuffer.split("\n") + readbuffer = temp.pop() + + for line in temp: + line = line.rstrip() + first = line.split() + + if first[0] == "PING": + self.sock.send("PONG %s\r\n" % first[1]) + + if first[0] == "ERROR": + raise IRCError(line) + + msg = line.split(None, 3) + if len(msg) < 4: + continue + + msg = { + "origin": msg[0][1:], + "action": msg[1], + "target": msg[2], + "text": msg[3][1:] + } + + self.handle_events(msg) + + def handle_events(self, msg): + if not msg["origin"].split("!", 1)[0] in self.getConfig("owner").split(): + return + + if msg["target"].split("!", 1)[0] != self.getConfig("nick"): + return + + if msg["action"] != "PRIVMSG": + return + + # HANDLE CTCP ANTI FLOOD/BOT PROTECTION + if msg["text"] == "\x01VERSION\x01": + self.logDebug("Sending CTCP VERSION.") + self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface")) + return + elif msg["text"] == "\x01TIME\x01": + self.logDebug("Sending CTCP TIME.") + self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time())) + return + elif msg["text"] == "\x01LAG\x01": + self.logDebug("Received CTCP LAG.") # don't know how to answer + return + + trigger = "pass" + args = None + + try: + temp = msg["text"].split() + trigger = temp[0] + if len(temp) > 1: + args = temp[1:] + except: + pass + + handler = getattr(self, "event_%s" % trigger, self.event_pass) + try: + res = handler(args) + for line in res: + self.response(line, msg["origin"]) + except Exception, e: + self.logError("pyLoad IRC: " + repr(e)) + + def response(self, msg, origin=""): + if origin == "": + for t in self.getConfig("owner").split(): + self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg)) + else: + self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg)) + + #### Events + + def event_pass(self, args): + return [] + + def event_status(self, args): + downloads = self.api.statusDownloads() + if not downloads: + return ["INFO: There are no active downloads currently."] + + temp_progress = "" + lines = ["ID - Name - Status - Speed - ETA - Progress"] + for data in downloads: + + if data.status == 5: + temp_progress = data.format_wait + else: + temp_progress = "%d%% (%s)" % (data.percent, data.format_size) + + lines.append("#%d - %s - %s - %s - %s - %s" % + ( + data.fid, + data.name, + data.statusmsg, + "%s/s" % formatSize(data.speed), + "%s" % data.format_eta, + temp_progress + )) + return lines + + def event_queue(self, args): + ps = self.api.getQueueData() + + if not ps: + return ["INFO: There are no packages in queue."] + + lines = [] + for pack in ps: + lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) + + return lines + + def event_collector(self, args): + ps = self.api.getCollectorData() + if not ps: + return ["INFO: No packages in collector!"] + + lines = [] + for pack in ps: + lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) + + return lines + + def event_info(self, args): + if not args: + return ['ERROR: Use info like this: info <id>'] + + info = None + try: + info = self.api.getFileData(int(args[0])) + + except FileDoesNotExists: + return ["ERROR: Link doesn't exists."] + + return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)] + + def event_packinfo(self, args): + if not args: + return ['ERROR: Use packinfo like this: packinfo <id>'] + + lines = [] + pack = None + try: + pack = self.api.getPackageData(int(args[0])) + + except PackageDoesNotExists: + return ["ERROR: Package doesn't exists."] + + id = args[0] + + self.more = [] + + lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links))) + for pyfile in pack.links: + self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size, + pyfile.statusmsg, pyfile.plugin)) + + if len(self.more) < 6: + lines.extend(self.more) + self.more = [] + else: + lines.extend(self.more[:6]) + self.more = self.more[6:] + lines.append("%d more links do display." % len(self.more)) + + return lines + + def event_more(self, args): + if not self.more: + return ["No more information to display."] + + lines = self.more[:6] + self.more = self.more[6:] + lines.append("%d more links do display." % len(self.more)) + + return lines + + def event_start(self, args): + + self.api.unpauseServer() + return ["INFO: Starting downloads."] + + def event_stop(self, args): + + self.api.pauseServer() + return ["INFO: No new downloads will be started."] + + def event_add(self, args): + if len(args) < 2: + return ['ERROR: Add links like this: "add <packagename|id> links". ', + 'This will add the link <link> to to the package <package> / the package with id <id>!'] + + pack = args[0].strip() + links = [x.strip() for x in args[1:]] + + count_added = 0 + count_failed = 0 + try: + id = int(pack) + pack = self.api.getPackageData(id) + if not pack: + return ["ERROR: Package doesn't exists."] + + #TODO add links + + return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack["name"], id)] + + except: + # create new package + id = self.api.addPackage(pack, links, 1) + return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))] + + def event_del(self, args): + if len(args) < 2: + return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] + + if args[0] == "-p": + ret = self.api.deletePackages(map(int, args[1:])) + return ["INFO: Deleted %d packages!" % len(args[1:])] + + elif args[0] == "-l": + ret = self.api.delLinks(map(int, args[1:])) + return ["INFO: Deleted %d links!" % len(args[1:])] + + else: + return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] + + def event_push(self, args): + if not args: + return ["ERROR: Push package to queue like this: push <package id>"] + + id = int(args[0]) + try: + info = self.api.getPackageInfo(id) + except PackageDoesNotExists: + return ["ERROR: Package #%d does not exist." % id] + + self.api.pushToQueue(id) + return ["INFO: Pushed package #%d to queue." % id] + + def event_pull(self, args): + if not args: + return ["ERROR: Pull package from queue like this: pull <package id>."] + + id = int(args[0]) + if not self.api.getPackageData(id): + return ["ERROR: Package #%d does not exist." % id] + + self.api.pullFromQueue(id) + return ["INFO: Pulled package #%d from queue to collector." % id] + + def event_c(self, args): + """ captcha answer """ + if not args: + return ["ERROR: Captcha ID missing."] + + task = self.core.captchaManager.getTaskByID(args[0]) + if not task: + return ["ERROR: Captcha Task with ID %s does not exists." % args[0]] + + task.setResult(" ".join(args[1:])) + return ["INFO: Result %s saved." % " ".join(args[1:])] + + def event_help(self, args): + lines = ["The following commands are available:", + "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)", + "queue Shows all packages in the queue", + "collector Shows all packages in collector", + "del -p|-l <id> [...] Deletes all packages|links with the ids specified", + "info <id> Shows info of the link with id <id>", + "packinfo <id> Shows info of the package with id <id>", + "more Shows more info when the result was truncated", + "start Starts all downloads", + "stop Stops the download (but not abort active downloads)", + "push <id> Push package to queue", + "pull <id> Pull package from queue", + "status Show general download status", + "help Shows this help message"] + return lines + + +class IRCError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) diff --git a/module/plugins/hooks/ImageTyperz.py b/pyload/plugins/addons/ImageTyperz.py index c9e43b8ae..c9e43b8ae 100644 --- a/module/plugins/hooks/ImageTyperz.py +++ b/pyload/plugins/addons/ImageTyperz.py diff --git a/module/plugins/hooks/LinkdecrypterCom.py b/pyload/plugins/addons/LinkdecrypterCom.py index f2176e799..f2176e799 100644 --- a/module/plugins/hooks/LinkdecrypterCom.py +++ b/pyload/plugins/addons/LinkdecrypterCom.py diff --git a/module/plugins/hooks/MergeFiles.py b/pyload/plugins/addons/MergeFiles.py index 869b5b6f8..869b5b6f8 100644 --- a/module/plugins/hooks/MergeFiles.py +++ b/pyload/plugins/addons/MergeFiles.py diff --git a/module/plugins/hooks/MultiHome.py b/pyload/plugins/addons/MultiHome.py index 473e6dcb1..473e6dcb1 100644 --- a/module/plugins/hooks/MultiHome.py +++ b/pyload/plugins/addons/MultiHome.py diff --git a/pyload/plugins/addons/MultiHoster.py b/pyload/plugins/addons/MultiHoster.py new file mode 100644 index 000000000..2d4029dd6 --- /dev/null +++ b/pyload/plugins/addons/MultiHoster.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from types import MethodType + +from pyload.plugins.MultiHoster import MultiHoster as MultiHosterAccount, normalize +from pyload.plugins.Addon import Addon, AddEventListener +from pyload.PluginManager import PluginMatcher + +class MultiHoster(Addon, PluginMatcher): + __version__ = "0.1" + __internal__ = True + __description__ = "Gives ability to use MultiHoster services." + __config__ = [] + __author__ = ("pyLoad Team",) + __author_mail__ = ("support@pyload.org",) + + #TODO: multiple accounts - multihoster / config options + # TODO: rewrite for new plugin manager + + def init(self): + + # overwritten plugins + self.plugins = {} + + def addHoster(self, account): + + self.logInfo(_("Activated %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 + patterns = [x.replace(".", "\\.") for x in new_supported] + + if klass.__pattern__: + patterns.append(klass.__pattern__) + + regexp = r".*(%s).*" % "|".join(patterns) + + # 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.category, p.user, p.path) + hoster[account.__name__] = new + + + @AddEventListener("account:deleted") + def refreshAccounts(self, plugin=None, loginname=None): + self.logDebug("Re-checking accounts") + + self.plugins = {} + for plugin, account in self.core.accountManager.iterAccounts(): + if isinstance(account, MultiHosterAccount) and account.isUsable(): + self.addHoster(account) + + @AddEventListener("account:updated") + def refreshAccount(self, acc): + + account = self.core.accountManager.getAccount(acc.plugin, acc.loginname) + if isinstance(account, MultiHosterAccount) and account.isUsable(): + self.addHoster(account) + + def activate(self): + self.refreshAccounts() + + self.core.pluginManager.addMatcher(self) + def deactivate(self): + + self.core.pluginManager.removeMatcher(self) + diff --git a/module/plugins/hooks/MultishareCz.py b/pyload/plugins/addons/MultishareCz.py index fc35bb785..fc35bb785 100644 --- a/module/plugins/hooks/MultishareCz.py +++ b/pyload/plugins/addons/MultishareCz.py diff --git a/module/plugins/hooks/Premium4Me.py b/pyload/plugins/addons/Premium4Me.py index 4bcc79b25..4bcc79b25 100644 --- a/module/plugins/hooks/Premium4Me.py +++ b/pyload/plugins/addons/Premium4Me.py diff --git a/module/plugins/hooks/PremiumizeMe.py b/pyload/plugins/addons/PremiumizeMe.py index 07630420c..07630420c 100644 --- a/module/plugins/hooks/PremiumizeMe.py +++ b/pyload/plugins/addons/PremiumizeMe.py diff --git a/module/plugins/hooks/RPNetBiz.py b/pyload/plugins/addons/RPNetBiz.py index 69976ffc9..69976ffc9 100644 --- a/module/plugins/hooks/RPNetBiz.py +++ b/pyload/plugins/addons/RPNetBiz.py diff --git a/module/plugins/hooks/RealdebridCom.py b/pyload/plugins/addons/RealdebridCom.py index 41e988495..41e988495 100644 --- a/module/plugins/hooks/RealdebridCom.py +++ b/pyload/plugins/addons/RealdebridCom.py diff --git a/module/plugins/hooks/RehostTo.py b/pyload/plugins/addons/RehostTo.py index 6e24988c8..6e24988c8 100644 --- a/module/plugins/hooks/RehostTo.py +++ b/pyload/plugins/addons/RehostTo.py diff --git a/module/plugins/hooks/ReloadCc.py b/pyload/plugins/addons/ReloadCc.py index d07923624..d07923624 100644 --- a/module/plugins/hooks/ReloadCc.py +++ b/pyload/plugins/addons/ReloadCc.py diff --git a/module/plugins/hooks/RestartFailed.py b/pyload/plugins/addons/RestartFailed.py index 3bf6fe365..3bf6fe365 100644 --- a/module/plugins/hooks/RestartFailed.py +++ b/pyload/plugins/addons/RestartFailed.py diff --git a/module/plugins/hooks/SimplydebridCom.py b/pyload/plugins/addons/SimplydebridCom.py index 3272df567..3272df567 100644 --- a/module/plugins/hooks/SimplydebridCom.py +++ b/pyload/plugins/addons/SimplydebridCom.py diff --git a/module/plugins/captcha/__init__.py b/pyload/plugins/addons/SkipRev.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/captcha/__init__.py +++ b/pyload/plugins/addons/SkipRev.py diff --git a/module/plugins/hooks/UnSkipOnFail.py b/pyload/plugins/addons/UnSkipOnFail.py index 455832b09..455832b09 100644 --- a/module/plugins/hooks/UnSkipOnFail.py +++ b/pyload/plugins/addons/UnSkipOnFail.py diff --git a/module/plugins/hooks/UpdateManager.py b/pyload/plugins/addons/UpdateManager.py index 62031e6a4..62031e6a4 100644 --- a/module/plugins/hooks/UpdateManager.py +++ b/pyload/plugins/addons/UpdateManager.py diff --git a/module/plugins/hooks/WindowsPhoneToastNotify.py b/pyload/plugins/addons/WindowsPhoneToastNotify.py index d110f7896..d110f7896 100644 --- a/module/plugins/hooks/WindowsPhoneToastNotify.py +++ b/pyload/plugins/addons/WindowsPhoneToastNotify.py diff --git a/module/plugins/hooks/XFileSharingPro.py b/pyload/plugins/addons/XFileSharingPro.py index fe2df840d..fe2df840d 100644 --- a/module/plugins/hooks/XFileSharingPro.py +++ b/pyload/plugins/addons/XFileSharingPro.py diff --git a/module/plugins/hooks/XMPPInterface.py b/pyload/plugins/addons/XMPPInterface.py index adffc04e3..adffc04e3 100644 --- a/module/plugins/hooks/XMPPInterface.py +++ b/pyload/plugins/addons/XMPPInterface.py diff --git a/module/plugins/hooks/ZeveraCom.py b/pyload/plugins/addons/ZeveraCom.py index fb84886d1..fb84886d1 100644 --- a/module/plugins/hooks/ZeveraCom.py +++ b/pyload/plugins/addons/ZeveraCom.py diff --git a/module/plugins/container/__init__.py b/pyload/plugins/addons/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/container/__init__.py +++ b/pyload/plugins/addons/__init__.py diff --git a/module/plugins/crypter/BitshareComFolder.py b/pyload/plugins/crypter/BitshareComFolder.py index b77ddb9d9..b77ddb9d9 100644 --- a/module/plugins/crypter/BitshareComFolder.py +++ b/pyload/plugins/crypter/BitshareComFolder.py diff --git a/module/plugins/crypter/C1neonCom.py b/pyload/plugins/crypter/C1neonCom.py index c1e9013b3..c1e9013b3 100644 --- a/module/plugins/crypter/C1neonCom.py +++ b/pyload/plugins/crypter/C1neonCom.py diff --git a/pyload/plugins/crypter/CCF.py b/pyload/plugins/crypter/CCF.py new file mode 100644 index 000000000..ab7ff1099 --- /dev/null +++ b/pyload/plugins/crypter/CCF.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from urllib2 import build_opener + +from module.plugins.Crypter import Crypter +from module.lib.MultipartPostHandler import MultipartPostHandler + +from os import makedirs +from os.path import exists, join + +class CCF(Crypter): + __name__ = "CCF" + __version__ = "0.2" + __pattern__ = r"(?!http://).*\.ccf$" + __description__ = """CCF Container Convert Plugin""" + __author_name__ = ("Willnix") + __author_mail__ = ("Willnix@pyload.org") + + def decrypt(self, pyfile): + + infile = pyfile.url.replace("\n", "") + + opener = build_opener(MultipartPostHandler) + params = {"src": "ccf", + "filename": "test.ccf", + "upload": open(infile, "rb")} + tempdlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', params).read() + + download_folder = self.config['general']['download_folder'] + location = download_folder #join(download_folder, self.pyfile.package().folder.decode(sys.getfilesystemencoding())) + if not exists(location): + makedirs(location) + + tempdlc_name = join(location, "tmp_%s.dlc" % pyfile.name) + tempdlc = open(tempdlc_name, "w") + tempdlc.write(re.search(r'<dlc>(.*)</dlc>', tempdlc_content, re.DOTALL).group(1)) + tempdlc.close() + + self.packages.append((tempdlc_name, [tempdlc_name], tempdlc_name)) + diff --git a/module/plugins/crypter/CrockoComFolder.py b/pyload/plugins/crypter/CrockoComFolder.py index ffa8039a1..ffa8039a1 100644 --- a/module/plugins/crypter/CrockoComFolder.py +++ b/pyload/plugins/crypter/CrockoComFolder.py diff --git a/module/plugins/crypter/CryptItCom.py b/pyload/plugins/crypter/CryptItCom.py index 679e891b9..679e891b9 100644 --- a/module/plugins/crypter/CryptItCom.py +++ b/pyload/plugins/crypter/CryptItCom.py diff --git a/module/plugins/crypter/CzshareComFolder.py b/pyload/plugins/crypter/CzshareComFolder.py index 9ccda3ed2..9ccda3ed2 100644 --- a/module/plugins/crypter/CzshareComFolder.py +++ b/pyload/plugins/crypter/CzshareComFolder.py diff --git a/module/plugins/crypter/DDLMusicOrg.py b/pyload/plugins/crypter/DDLMusicOrg.py index 822addca1..822addca1 100644 --- a/module/plugins/crypter/DDLMusicOrg.py +++ b/pyload/plugins/crypter/DDLMusicOrg.py diff --git a/pyload/plugins/crypter/DLC_25.pyc b/pyload/plugins/crypter/DLC_25.pyc Binary files differnew file mode 100644 index 000000000..e3c9150c3 --- /dev/null +++ b/pyload/plugins/crypter/DLC_25.pyc diff --git a/pyload/plugins/crypter/DLC_26.pyc b/pyload/plugins/crypter/DLC_26.pyc Binary files differnew file mode 100644 index 000000000..e20700eb1 --- /dev/null +++ b/pyload/plugins/crypter/DLC_26.pyc diff --git a/pyload/plugins/crypter/DLC_27.pyc b/pyload/plugins/crypter/DLC_27.pyc Binary files differnew file mode 100644 index 000000000..6f04d88f4 --- /dev/null +++ b/pyload/plugins/crypter/DLC_27.pyc diff --git a/module/plugins/crypter/DataHuFolder.py b/pyload/plugins/crypter/DataHuFolder.py index f710f60d7..f710f60d7 100644 --- a/module/plugins/crypter/DataHuFolder.py +++ b/pyload/plugins/crypter/DataHuFolder.py diff --git a/module/plugins/crypter/DdlstorageComFolder.py b/pyload/plugins/crypter/DdlstorageComFolder.py index d76988c92..d76988c92 100644 --- a/module/plugins/crypter/DdlstorageComFolder.py +++ b/pyload/plugins/crypter/DdlstorageComFolder.py diff --git a/module/plugins/crypter/DepositfilesComFolder.py b/pyload/plugins/crypter/DepositfilesComFolder.py index 15c317acf..15c317acf 100644 --- a/module/plugins/crypter/DepositfilesComFolder.py +++ b/pyload/plugins/crypter/DepositfilesComFolder.py diff --git a/module/plugins/crypter/Dereferer.py b/pyload/plugins/crypter/Dereferer.py index 7b7c34fee..7b7c34fee 100644 --- a/module/plugins/crypter/Dereferer.py +++ b/pyload/plugins/crypter/Dereferer.py diff --git a/module/plugins/crypter/DontKnowMe.py b/pyload/plugins/crypter/DontKnowMe.py index 0791e3c22..0791e3c22 100644 --- a/module/plugins/crypter/DontKnowMe.py +++ b/pyload/plugins/crypter/DontKnowMe.py diff --git a/module/plugins/crypter/DownloadVimeoCom.py b/pyload/plugins/crypter/DownloadVimeoCom.py index 562672599..562672599 100644 --- a/module/plugins/crypter/DownloadVimeoCom.py +++ b/pyload/plugins/crypter/DownloadVimeoCom.py diff --git a/module/plugins/crypter/DuckCryptInfo.py b/pyload/plugins/crypter/DuckCryptInfo.py index 64302c800..64302c800 100644 --- a/module/plugins/crypter/DuckCryptInfo.py +++ b/pyload/plugins/crypter/DuckCryptInfo.py diff --git a/module/plugins/crypter/DuploadOrgFolder.py b/pyload/plugins/crypter/DuploadOrgFolder.py index a1918f055..a1918f055 100644 --- a/module/plugins/crypter/DuploadOrgFolder.py +++ b/pyload/plugins/crypter/DuploadOrgFolder.py diff --git a/module/plugins/crypter/EasybytezComFolder.py b/pyload/plugins/crypter/EasybytezComFolder.py index 56be72669..56be72669 100644 --- a/module/plugins/crypter/EasybytezComFolder.py +++ b/pyload/plugins/crypter/EasybytezComFolder.py diff --git a/module/plugins/crypter/EmbeduploadCom.py b/pyload/plugins/crypter/EmbeduploadCom.py index 122c0f671..122c0f671 100644 --- a/module/plugins/crypter/EmbeduploadCom.py +++ b/pyload/plugins/crypter/EmbeduploadCom.py diff --git a/module/plugins/crypter/FilebeerInfoFolder.py b/pyload/plugins/crypter/FilebeerInfoFolder.py index b6bf4fd07..b6bf4fd07 100644 --- a/module/plugins/crypter/FilebeerInfoFolder.py +++ b/pyload/plugins/crypter/FilebeerInfoFolder.py diff --git a/module/plugins/crypter/FilefactoryComFolder.py b/pyload/plugins/crypter/FilefactoryComFolder.py index 64b8ac37e..64b8ac37e 100644 --- a/module/plugins/crypter/FilefactoryComFolder.py +++ b/pyload/plugins/crypter/FilefactoryComFolder.py diff --git a/module/plugins/crypter/FileserveComFolder.py b/pyload/plugins/crypter/FileserveComFolder.py index 42481c15e..42481c15e 100644 --- a/module/plugins/crypter/FileserveComFolder.py +++ b/pyload/plugins/crypter/FileserveComFolder.py diff --git a/module/plugins/crypter/FilestubeCom.py b/pyload/plugins/crypter/FilestubeCom.py index 1e5712cb5..1e5712cb5 100644 --- a/module/plugins/crypter/FilestubeCom.py +++ b/pyload/plugins/crypter/FilestubeCom.py diff --git a/module/plugins/crypter/FiletramCom.py b/pyload/plugins/crypter/FiletramCom.py index 886b8be30..886b8be30 100644 --- a/module/plugins/crypter/FiletramCom.py +++ b/pyload/plugins/crypter/FiletramCom.py diff --git a/module/plugins/crypter/FourChanOrg.py b/pyload/plugins/crypter/FourChanOrg.py index 700a09b97..700a09b97 100644 --- a/module/plugins/crypter/FourChanOrg.py +++ b/pyload/plugins/crypter/FourChanOrg.py diff --git a/module/plugins/crypter/FreakhareComFolder.py b/pyload/plugins/crypter/FreakhareComFolder.py index 519a468f3..519a468f3 100644 --- a/module/plugins/crypter/FreakhareComFolder.py +++ b/pyload/plugins/crypter/FreakhareComFolder.py diff --git a/module/plugins/crypter/FreetexthostCom.py b/pyload/plugins/crypter/FreetexthostCom.py index 8d165abe4..8d165abe4 100644 --- a/module/plugins/crypter/FreetexthostCom.py +++ b/pyload/plugins/crypter/FreetexthostCom.py diff --git a/module/plugins/crypter/FshareVnFolder.py b/pyload/plugins/crypter/FshareVnFolder.py index 2d7a3c490..2d7a3c490 100644 --- a/module/plugins/crypter/FshareVnFolder.py +++ b/pyload/plugins/crypter/FshareVnFolder.py diff --git a/module/plugins/crypter/GooGl.py b/pyload/plugins/crypter/GooGl.py index bcb1d7494..bcb1d7494 100644 --- a/module/plugins/crypter/GooGl.py +++ b/pyload/plugins/crypter/GooGl.py diff --git a/pyload/plugins/crypter/HoerbuchIn.py b/pyload/plugins/crypter/HoerbuchIn.py new file mode 100644 index 000000000..9d58873e8 --- /dev/null +++ b/pyload/plugins/crypter/HoerbuchIn.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re + +from module.plugins.Crypter import Crypter +from module.lib.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup + + +class HoerbuchIn(Crypter): + __name__ = "HoerbuchIn" + __type__ = "container" + __pattern__ = r"http://(www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out.php\?.+|protection/folder_\d+\.html)" + __version__ = "0.7" + __description__ = """Hoerbuch.in Container Plugin""" + __author_name__ = ("spoob", "mkaay") + __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de") + + article = re.compile("http://(www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/") + protection = re.compile("http://(www\.)?hoerbuch\.in/protection/folder_\d+.html") + + def decrypt(self, pyfile): + self.pyfile = pyfile + + if self.article.match(self.pyfile.url): + src = self.load(self.pyfile.url) + soup = BeautifulSoup(src, convertEntities=BeautifulStoneSoup.HTML_ENTITIES) + + abookname = soup.find("a", attrs={"rel": "bookmark"}).text + for a in soup.findAll("a", attrs={"href": self.protection}): + package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1]) + links = self.decryptFolder(a["href"]) + + self.packages.append((package, links, self.pyfile.package().folder)) + else: + links = self.decryptFolder(self.pyfile.url) + + self.packages.append((self.pyfile.package().name, links, self.pyfile.package().folder)) + + def decryptFolder(self, url): + m = self.protection.search(url) + if not m: + self.fail("Bad URL") + url = m.group(0) + + self.pyfile.url = url + src = self.req.load(url, post={"viewed": "adpg"}) + + links = [] + pattern = re.compile(r'<div class="container"><a href="(.*?)"') + for hoster_url in pattern.findall(src): + self.req.lastURL = url + self.load(hoster_url) + links.append(self.req.lastEffectiveURL) + + return links diff --git a/module/plugins/crypter/HotfileFolderCom.py b/pyload/plugins/crypter/HotfileFolderCom.py index 79691ad01..79691ad01 100644 --- a/module/plugins/crypter/HotfileFolderCom.py +++ b/pyload/plugins/crypter/HotfileFolderCom.py diff --git a/module/plugins/crypter/ILoadTo.py b/pyload/plugins/crypter/ILoadTo.py index d155f4bb6..d155f4bb6 100644 --- a/module/plugins/crypter/ILoadTo.py +++ b/pyload/plugins/crypter/ILoadTo.py diff --git a/module/plugins/crypter/LetitbitNetFolder.py b/pyload/plugins/crypter/LetitbitNetFolder.py index 68aad9dd7..68aad9dd7 100644 --- a/module/plugins/crypter/LetitbitNetFolder.py +++ b/pyload/plugins/crypter/LetitbitNetFolder.py diff --git a/pyload/plugins/crypter/LinkList.py b/pyload/plugins/crypter/LinkList.py new file mode 100644 index 000000000..ebfa373eb --- /dev/null +++ b/pyload/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/LinkSaveIn.py b/pyload/plugins/crypter/LinkSaveIn.py index 129da6608..129da6608 100644 --- a/module/plugins/crypter/LinkSaveIn.py +++ b/pyload/plugins/crypter/LinkSaveIn.py diff --git a/module/plugins/crypter/LinkdecrypterCom.py b/pyload/plugins/crypter/LinkdecrypterCom.py index 6a11c98b0..6a11c98b0 100644 --- a/module/plugins/crypter/LinkdecrypterCom.py +++ b/pyload/plugins/crypter/LinkdecrypterCom.py diff --git a/module/plugins/crypter/LixIn.py b/pyload/plugins/crypter/LixIn.py index 52abe1b3c..52abe1b3c 100644 --- a/module/plugins/crypter/LixIn.py +++ b/pyload/plugins/crypter/LixIn.py diff --git a/module/plugins/crypter/LofCc.py b/pyload/plugins/crypter/LofCc.py index 458609881..458609881 100644 --- a/module/plugins/crypter/LofCc.py +++ b/pyload/plugins/crypter/LofCc.py diff --git a/module/plugins/crypter/MBLinkInfo.py b/pyload/plugins/crypter/MBLinkInfo.py index 434819d07..434819d07 100644 --- a/module/plugins/crypter/MBLinkInfo.py +++ b/pyload/plugins/crypter/MBLinkInfo.py diff --git a/module/plugins/crypter/MediafireComFolder.py b/pyload/plugins/crypter/MediafireComFolder.py index 3709d3349..3709d3349 100644 --- a/module/plugins/crypter/MediafireComFolder.py +++ b/pyload/plugins/crypter/MediafireComFolder.py diff --git a/module/plugins/crypter/Movie2kTo.py b/pyload/plugins/crypter/Movie2kTo.py index c9c3f8f2d..c9c3f8f2d 100644 --- a/module/plugins/crypter/Movie2kTo.py +++ b/pyload/plugins/crypter/Movie2kTo.py diff --git a/module/plugins/crypter/MultiloadCz.py b/pyload/plugins/crypter/MultiloadCz.py index 055d72375..055d72375 100644 --- a/module/plugins/crypter/MultiloadCz.py +++ b/pyload/plugins/crypter/MultiloadCz.py diff --git a/module/plugins/crypter/MultiuploadCom.py b/pyload/plugins/crypter/MultiuploadCom.py index 111ecdf6c..111ecdf6c 100644 --- a/module/plugins/crypter/MultiuploadCom.py +++ b/pyload/plugins/crypter/MultiuploadCom.py diff --git a/module/plugins/crypter/NCryptIn.py b/pyload/plugins/crypter/NCryptIn.py index 3a474a1c6..3a474a1c6 100644 --- a/module/plugins/crypter/NCryptIn.py +++ b/pyload/plugins/crypter/NCryptIn.py diff --git a/module/plugins/crypter/NetfolderIn.py b/pyload/plugins/crypter/NetfolderIn.py index c5c602c27..c5c602c27 100644 --- a/module/plugins/crypter/NetfolderIn.py +++ b/pyload/plugins/crypter/NetfolderIn.py diff --git a/module/plugins/crypter/OneKhDe.py b/pyload/plugins/crypter/OneKhDe.py index a2e6d039e..a2e6d039e 100644 --- a/module/plugins/crypter/OneKhDe.py +++ b/pyload/plugins/crypter/OneKhDe.py diff --git a/module/plugins/crypter/OronComFolder.py b/pyload/plugins/crypter/OronComFolder.py index 98d7e8242..98d7e8242 100755 --- a/module/plugins/crypter/OronComFolder.py +++ b/pyload/plugins/crypter/OronComFolder.py diff --git a/module/plugins/crypter/PastebinCom.py b/pyload/plugins/crypter/PastebinCom.py index 942ab613b..942ab613b 100644 --- a/module/plugins/crypter/PastebinCom.py +++ b/pyload/plugins/crypter/PastebinCom.py diff --git a/module/plugins/crypter/QuickshareCzFolder.py b/pyload/plugins/crypter/QuickshareCzFolder.py index 18ac68eec..18ac68eec 100644 --- a/module/plugins/crypter/QuickshareCzFolder.py +++ b/pyload/plugins/crypter/QuickshareCzFolder.py diff --git a/pyload/plugins/crypter/RSDF.py b/pyload/plugins/crypter/RSDF.py new file mode 100644 index 000000000..cbc9864b1 --- /dev/null +++ b/pyload/plugins/crypter/RSDF.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import base64 +import binascii +import re + +from module.plugins.Crypter import Crypter + +class RSDF(Crypter): + __name__ = "RSDF" + __version__ = "0.21" + __pattern__ = r".*\.rsdf" + __description__ = """RSDF Container Decode Plugin""" + __author_name__ = ("RaNaN", "spoob") + __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org") + + + def decrypt(self, pyfile): + + from Crypto.Cipher import AES + + infile = pyfile.url.replace("\n", "") + Key = binascii.unhexlify('8C35192D964DC3182C6F84F3252239EB4A320D2500000000') + + IV = binascii.unhexlify('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') + IV_Cipher = AES.new(Key, AES.MODE_ECB) + IV = IV_Cipher.encrypt(IV) + + obj = AES.new(Key, AES.MODE_CFB, IV) + + rsdf = open(infile, 'r') + + data = rsdf.read() + rsdf.close() + + if re.search(r"<title>404 - Not Found</title>", data) is None: + data = binascii.unhexlify(''.join(data.split())) + data = data.splitlines() + + links = [] + for link in data: + link = base64.b64decode(link) + link = obj.decrypt(link) + decryptedUrl = link.replace('CCF: ', '') + links.append(decryptedUrl) + + self.log.debug("%s: adding package %s with %d links" % (self.__name__,pyfile.package().name,len(links))) + self.packages.append((pyfile.package().name, links)) diff --git a/module/plugins/crypter/RSLayerCom.py b/pyload/plugins/crypter/RSLayerCom.py index 0dc7ddf4e..0dc7ddf4e 100644 --- a/module/plugins/crypter/RSLayerCom.py +++ b/pyload/plugins/crypter/RSLayerCom.py diff --git a/module/plugins/crypter/RelinkUs.py b/pyload/plugins/crypter/RelinkUs.py index 104968e06..104968e06 100644 --- a/module/plugins/crypter/RelinkUs.py +++ b/pyload/plugins/crypter/RelinkUs.py diff --git a/module/plugins/crypter/SecuredIn.py b/pyload/plugins/crypter/SecuredIn.py index 4c3bbf4a6..4c3bbf4a6 100644 --- a/module/plugins/crypter/SecuredIn.py +++ b/pyload/plugins/crypter/SecuredIn.py diff --git a/pyload/plugins/crypter/SerienjunkiesOrg.py b/pyload/plugins/crypter/SerienjunkiesOrg.py new file mode 100644 index 000000000..a682a44fa --- /dev/null +++ b/pyload/plugins/crypter/SerienjunkiesOrg.py @@ -0,0 +1,322 @@ +# -*- coding: utf-8 -*- + +import re +from time import sleep +import random + +from BeautifulSoup import BeautifulSoup + +from module.plugins.Crypter import Crypter +from module.unescape import unescape + + +class SerienjunkiesOrg(Crypter): + __name__ = "SerienjunkiesOrg" + __type__ = "container" + __pattern__ = r"http://.*?(serienjunkies.org|dokujunkies.org)/.*?" + __version__ = "0.39" + __config__ = [ + ("changeNameSJ", "Packagename;Show;Season;Format;Episode", "Take SJ.org name", "Show"), + ("changeNameDJ", "Packagename;Show;Format;Episode", "Take DJ.org name", "Show"), + ("randomPreferred", "bool", "Randomize Preferred-List", False), + ("hosterListMode", "OnlyOne;OnlyPreferred(One);OnlyPreferred(All);All", + "Use for hosters (if supported)", "All"), + ("hosterList", "str", "Preferred Hoster list (comma separated)", + "RapidshareCom,UploadedTo,NetloadIn,FilefactoryCom,FreakshareNet,FilebaseTo,HotfileCom,DepositfilesCom,EasyshareCom,KickloadCom"), + ("ignoreList", "str", "Ignored Hoster list (comma separated)", "MegauploadCom") + ] + __description__ = """serienjunkies.org Container Plugin""" + __author_name__ = ("mkaay", "godofdream") + __author_mail__ = ("mkaay@mkaay.de", "soilfiction@gmail.com") + + def setup(self): + self.multiDL = False + + def getSJSrc(self, url): + src = self.req.load(str(url)) + if "This website is not available in your country" in src: + self.fail("Not available in your country") + if not src.find("Enter Serienjunkies") == -1: + sleep(1) + src = self.req.load(str(url)) + return src + + def handleShow(self, url): + src = self.getSJSrc(url) + soup = BeautifulSoup(src) + packageName = self.pyfile.package().name + if self.getConfig("changeNameSJ") == "Show": + found = unescape(soup.find("h2").find("a").string.split(' –')[0]) + if found: + packageName = found + + nav = soup.find("div", attrs={"id": "scb"}) + + package_links = [] + for a in nav.findAll("a"): + if self.getConfig("changeNameSJ") == "Show": + package_links.append(a["href"]) + else: + package_links.append(a["href"] + "#hasName") + if self.getConfig("changeNameSJ") == "Show": + self.packages.append((packageName, package_links, packageName)) + else: + self.core.files.addLinks(package_links, self.pyfile.package().id) + + def handleSeason(self, url): + src = self.getSJSrc(url) + soup = BeautifulSoup(src) + post = soup.find("div", attrs={"class": "post-content"}) + ps = post.findAll("p") + + seasonName = unescape(soup.find("a", attrs={"rel": "bookmark"}).string).replace("–", "-") + groups = {} + gid = -1 + for p in ps: + if re.search("<strong>Sprache|<strong>Format", str(p)): + var = p.findAll("strong") + opts = {"Sprache": "", "Format": ""} + for v in var: + n = unescape(v.string).strip() + n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n) + if n.strip() not in opts: + continue + val = v.nextSibling + if not val: + continue + val = val.replace("|", "").strip() + val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val) + opts[n.strip()] = val.strip() + gid += 1 + groups[gid] = {} + groups[gid]["ep"] = {} + groups[gid]["opts"] = opts + elif re.search("<strong>Download:", str(p)): + parts = str(p).split("<br />") + if re.search("<strong>", parts[0]): + ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace( + "–", "-") + groups[gid]["ep"][ename] = {} + parts.remove(parts[0]) + for part in parts: + hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part) + if hostername: + hostername = hostername.group(1) + groups[gid]["ep"][ename][hostername] = [] + links = re.findall('href="(.*?)"', part) + for link in links: + groups[gid]["ep"][ename][hostername].append(link + "#hasName") + + links = [] + for g in groups.values(): + for ename in g["ep"]: + links.extend(self.getpreferred(g["ep"][ename])) + if self.getConfig("changeNameSJ") == "Episode": + self.packages.append((ename, links, ename)) + links = [] + package = "%s (%s, %s)" % (seasonName, g["opts"]["Format"], g["opts"]["Sprache"]) + if self.getConfig("changeNameSJ") == "Format": + self.packages.append((package, links, package)) + links = [] + if (self.getConfig("changeNameSJ") == "Packagename") or re.search("#hasName", url): + self.core.files.addLinks(links, self.pyfile.package().id) + elif (self.getConfig("changeNameSJ") == "Season") or not re.search("#hasName", url): + self.packages.append((seasonName, links, seasonName)) + + def handleEpisode(self, url): + src = self.getSJSrc(url) + if not src.find( + "Du hast das Download-Limit überschritten! Bitte versuche es später nocheinmal.") == -1: + self.fail(_("Downloadlimit reached")) + else: + soup = BeautifulSoup(src) + form = soup.find("form") + h1 = soup.find("h1") + + if h1.get("class") == "wrap": + captchaTag = soup.find(attrs={"src": re.compile("^/secure/")}) + if not captchaTag: + sleep(5) + self.retry() + + captchaUrl = "http://download.serienjunkies.org" + captchaTag["src"] + result = self.decryptCaptcha(str(captchaUrl), imgtype="png") + sinp = form.find(attrs={"name": "s"}) + + self.req.lastURL = str(url) + sj = self.load(str(url), post={'s': sinp["value"], 'c': result, 'action': "Download"}) + + soup = BeautifulSoup(sj) + rawLinks = soup.findAll(attrs={"action": re.compile("^http://download.serienjunkies.org/")}) + + if not len(rawLinks) > 0: + sleep(1) + self.retry() + return + + self.correctCaptcha() + + links = [] + for link in rawLinks: + frameUrl = link["action"].replace("/go-", "/frame/go-") + links.append(self.handleFrame(frameUrl)) + if re.search("#hasName", url) or ((self.getConfig("changeNameSJ") == "Packagename") and + (self.getConfig("changeNameDJ") == "Packagename")): + self.core.files.addLinks(links, self.pyfile.package().id) + else: + if h1.text[2] == "_": + eName = h1.text[3:] + else: + eName = h1.text + self.packages.append((eName, links, eName)) + + def handleOldStyleLink(self, url): + sj = self.req.load(str(url)) + soup = BeautifulSoup(sj) + form = soup.find("form", attrs={"action": re.compile("^http://serienjunkies.org")}) + captchaTag = form.find(attrs={"src": re.compile("^/safe/secure/")}) + captchaUrl = "http://serienjunkies.org" + captchaTag["src"] + result = self.decryptCaptcha(str(captchaUrl)) + url = form["action"] + sinp = form.find(attrs={"name": "s"}) + + self.req.load(str(url), post={'s': sinp["value"], 'c': result, 'dl.start': "Download"}, cookies=False, + just_header=True) + decrypted = self.req.lastEffectiveURL + if decrypted == str(url): + self.retry() + self.core.files.addLinks([decrypted], self.pyfile.package().id) + + def handleFrame(self, url): + self.req.load(str(url)) + return self.req.lastEffectiveURL + + def handleShowDJ(self, url): + src = self.getSJSrc(url) + soup = BeautifulSoup(src) + post = soup.find("div", attrs={"id": "page_post"}) + ps = post.findAll("p") + found = unescape(soup.find("h2").find("a").string.split(' –')[0]) + if found: + seasonName = found + + groups = {} + gid = -1 + for p in ps: + if re.search("<strong>Sprache|<strong>Format", str(p)): + var = p.findAll("strong") + opts = {"Sprache": "", "Format": ""} + for v in var: + n = unescape(v.string).strip() + n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n) + if n.strip() not in opts: + continue + val = v.nextSibling + if not val: + continue + val = val.replace("|", "").strip() + val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val) + opts[n.strip()] = val.strip() + gid += 1 + groups[gid] = {} + groups[gid]["ep"] = {} + groups[gid]["opts"] = opts + elif re.search("<strong>Download:", str(p)): + parts = str(p).split("<br />") + if re.search("<strong>", parts[0]): + ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace( + "–", "-") + groups[gid]["ep"][ename] = {} + parts.remove(parts[0]) + for part in parts: + hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part) + if hostername: + hostername = hostername.group(1) + groups[gid]["ep"][ename][hostername] = [] + links = re.findall('href="(.*?)"', part) + for link in links: + groups[gid]["ep"][ename][hostername].append(link + "#hasName") + + links = [] + for g in groups.values(): + for ename in g["ep"]: + links.extend(self.getpreferred(g["ep"][ename])) + if self.getConfig("changeNameDJ") == "Episode": + self.packages.append((ename, links, ename)) + links = [] + package = "%s (%s, %s)" % (seasonName, g["opts"]["Format"], g["opts"]["Sprache"]) + if self.getConfig("changeNameDJ") == "Format": + self.packages.append((package, links, package)) + links = [] + if (self.getConfig("changeNameDJ") == "Packagename") or re.search("#hasName", url): + self.core.files.addLinks(links, self.pyfile.package().id) + elif (self.getConfig("changeNameDJ") == "Show") or not re.search("#hasName", url): + self.packages.append((seasonName, links, seasonName)) + + def handleCategoryDJ(self, url): + package_links = [] + src = self.getSJSrc(url) + soup = BeautifulSoup(src) + content = soup.find("div", attrs={"id": "content"}) + for a in content.findAll("a", attrs={"rel": "bookmark"}): + package_links.append(a["href"]) + self.core.files.addLinks(package_links, self.pyfile.package().id) + + def decrypt(self, pyfile): + showPattern = re.compile("^http://serienjunkies.org/serie/(.*)/$") + seasonPattern = re.compile("^http://serienjunkies.org/.*?/(.*)/$") + episodePattern = re.compile("^http://download.serienjunkies.org/f-.*?.html(#hasName)?$") + oldStyleLink = re.compile("^http://serienjunkies.org/safe/(.*)$") + categoryPatternDJ = re.compile("^http://dokujunkies.org/.*?(.*)$") + showPatternDJ = re.compile(r"^http://dokujunkies.org/.*?/(.*)\.html(#hasName)?$") + framePattern = re.compile("^http://download.(serienjunkies.org|dokujunkies.org)/frame/go-.*?/$") + url = pyfile.url + if framePattern.match(url): + self.packages.append((self.pyfile.package().name, [self.handleFrame(url)], self.pyfile.package().name)) + elif episodePattern.match(url): + self.handleEpisode(url) + elif oldStyleLink.match(url): + self.handleOldStyleLink(url) + elif showPattern.match(url): + self.handleShow(url) + elif showPatternDJ.match(url): + self.handleShowDJ(url) + elif seasonPattern.match(url): + self.handleSeason(url) + elif categoryPatternDJ.match(url): + self.handleCategoryDJ(url) + + #selects the preferred hoster, after that selects any hoster (ignoring the one to ignore) + def getpreferred(self, hosterlist): + + result = [] + preferredList = self.getConfig("hosterList").strip().lower().replace( + '|', ',').replace('.', '').replace(';', ',').split(',') + if (self.getConfig("randomPreferred") is True) and ( + self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]): + random.shuffle(preferredList) + # we don't want hosters be read two times + hosterlist2 = hosterlist.copy() + + for preferred in preferredList: + for Hoster in hosterlist: + if preferred == Hoster.lower().replace('.', ''): + for Part in hosterlist[Hoster]: + self.logDebug("selected " + Part) + result.append(str(Part)) + del (hosterlist2[Hoster]) + if self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]: + return result + + ignorelist = self.getConfig("ignoreList").strip().lower().replace( + '|', ',').replace('.', '').replace(';', ',').split(',') + if self.getConfig('hosterListMode') in ["OnlyOne", "All"]: + for Hoster in hosterlist2: + if Hoster.strip().lower().replace('.', '') not in ignorelist: + for Part in hosterlist2[Hoster]: + self.logDebug("selected2 " + Part) + result.append(str(Part)) + + if self.getConfig('hosterListMode') == "OnlyOne": + return result + return result diff --git a/module/plugins/crypter/ShareLinksBiz.py b/pyload/plugins/crypter/ShareLinksBiz.py index 09ac21873..09ac21873 100644 --- a/module/plugins/crypter/ShareLinksBiz.py +++ b/pyload/plugins/crypter/ShareLinksBiz.py diff --git a/module/plugins/crypter/ShareRapidComFolder.py b/pyload/plugins/crypter/ShareRapidComFolder.py index 951c09d45..951c09d45 100644 --- a/module/plugins/crypter/ShareRapidComFolder.py +++ b/pyload/plugins/crypter/ShareRapidComFolder.py diff --git a/module/plugins/crypter/SpeedLoadOrgFolder.py b/pyload/plugins/crypter/SpeedLoadOrgFolder.py index 7472e28fe..7472e28fe 100644 --- a/module/plugins/crypter/SpeedLoadOrgFolder.py +++ b/pyload/plugins/crypter/SpeedLoadOrgFolder.py diff --git a/module/plugins/crypter/StealthTo.py b/pyload/plugins/crypter/StealthTo.py index 45a14f5a2..45a14f5a2 100644 --- a/module/plugins/crypter/StealthTo.py +++ b/pyload/plugins/crypter/StealthTo.py diff --git a/module/plugins/crypter/TrailerzoneInfo.py b/pyload/plugins/crypter/TrailerzoneInfo.py index cdd84bbc6..cdd84bbc6 100644 --- a/module/plugins/crypter/TrailerzoneInfo.py +++ b/pyload/plugins/crypter/TrailerzoneInfo.py diff --git a/module/plugins/crypter/TurbobitNetFolder.py b/pyload/plugins/crypter/TurbobitNetFolder.py index e172f8037..e172f8037 100644 --- a/module/plugins/crypter/TurbobitNetFolder.py +++ b/pyload/plugins/crypter/TurbobitNetFolder.py diff --git a/module/plugins/crypter/UlozToFolder.py b/pyload/plugins/crypter/UlozToFolder.py index a5ccfc753..a5ccfc753 100644 --- a/module/plugins/crypter/UlozToFolder.py +++ b/pyload/plugins/crypter/UlozToFolder.py diff --git a/module/plugins/crypter/UploadedToFolder.py b/pyload/plugins/crypter/UploadedToFolder.py index 88d4e04e8..88d4e04e8 100644 --- a/module/plugins/crypter/UploadedToFolder.py +++ b/pyload/plugins/crypter/UploadedToFolder.py diff --git a/module/plugins/crypter/WiiReloadedOrg.py b/pyload/plugins/crypter/WiiReloadedOrg.py index df70c5aea..df70c5aea 100644 --- a/module/plugins/crypter/WiiReloadedOrg.py +++ b/pyload/plugins/crypter/WiiReloadedOrg.py diff --git a/pyload/plugins/crypter/XfilesharingProFolder.py b/pyload/plugins/crypter/XfilesharingProFolder.py new file mode 100644 index 000000000..90e3044a3 --- /dev/null +++ b/pyload/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/crypter/XupPl.py b/pyload/plugins/crypter/XupPl.py index 09832f037..09832f037 100644 --- a/module/plugins/crypter/XupPl.py +++ b/pyload/plugins/crypter/XupPl.py diff --git a/module/plugins/crypter/YoutubeBatch.py b/pyload/plugins/crypter/YoutubeBatch.py index b6178448d..b6178448d 100644 --- a/module/plugins/crypter/YoutubeBatch.py +++ b/pyload/plugins/crypter/YoutubeBatch.py diff --git a/module/plugins/crypter/__init__.py b/pyload/plugins/crypter/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/crypter/__init__.py +++ b/pyload/plugins/crypter/__init__.py diff --git a/module/plugins/hoster/ARD.py b/pyload/plugins/hoster/ARD.py index 12cb6c95a..12cb6c95a 100644 --- a/module/plugins/hoster/ARD.py +++ b/pyload/plugins/hoster/ARD.py diff --git a/module/plugins/hoster/AlldebridCom.py b/pyload/plugins/hoster/AlldebridCom.py index 82f4531a6..82f4531a6 100644 --- a/module/plugins/hoster/AlldebridCom.py +++ b/pyload/plugins/hoster/AlldebridCom.py diff --git a/module/plugins/hoster/BasePlugin.py b/pyload/plugins/hoster/BasePlugin.py index 2bdfda7c4..2bdfda7c4 100644 --- a/module/plugins/hoster/BasePlugin.py +++ b/pyload/plugins/hoster/BasePlugin.py diff --git a/module/plugins/hoster/BayfilesCom.py b/pyload/plugins/hoster/BayfilesCom.py index a696bac26..a696bac26 100644 --- a/module/plugins/hoster/BayfilesCom.py +++ b/pyload/plugins/hoster/BayfilesCom.py diff --git a/pyload/plugins/hoster/BezvadataCz.py b/pyload/plugins/hoster/BezvadataCz.py new file mode 100644 index 000000000..daed65095 --- /dev/null +++ b/pyload/plugins/hoster/BezvadataCz.py @@ -0,0 +1,101 @@ +# -*- 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: zoidberg +""" + +import re +from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo + + + +class BezvadataCz(SimpleHoster): + __name__ = "BezvadataCz" + __type__ = "hoster" + __pattern__ = r"http://(\w*\.)*bezvadata.cz/stahnout/.*" + __version__ = "0.24" + __description__ = """BezvaData.cz""" + __author_name__ = ("zoidberg") + __author_mail__ = ("zoidberg@mujmail.cz") + + FILE_NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>' + FILE_SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>' + FILE_OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>' + + def setup(self): + self.multiDL = self.resumeDownload = True + + def handleFree(self): + #download button + found = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html) + if not found: + self.parseError("page1 URL") + url = "http://bezvadata.cz%s" % found.group(1) + + #captcha form + self.html = self.load(url) + self.checkErrors() + for i in range(5): + action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm') + if not inputs: + self.parseError("FreeForm") + + found = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html) + if not found: + self.parseError("captcha img") + + #captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url + self.load, proper_load = self.loadcaptcha, self.load + try: + inputs['captcha'] = self.decryptCaptcha(found.group(1), imgtype='png') + finally: + self.load = proper_load + + if '<img src="data:image/png;base64' in self.html: + self.invalidCaptcha() + else: + self.correctCaptcha() + break + else: + self.fail("No valid captcha code entered") + + #download url + self.html = self.load("http://bezvadata.cz%s" % action, post=inputs) + self.checkErrors() + found = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html) + if not found: + self.parseError("page2 URL") + url = "http://bezvadata.cz%s" % found.group(1) + self.logDebug("DL URL %s" % url) + + #countdown + found = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html) + wait_time = (int(found.group(1)) * 60 + int(found.group(2)) + 1) if found else 120 + self.setWait(wait_time, False) + self.wait() + + self.download(url) + + def checkErrors(self): + if 'images/button-download-disable.png' in self.html: + self.longWait(300, 24) # parallel dl limit + elif '<div class="infobox' in self.html: + self.tempOffline() + + def loadcaptcha(self, data, *args, **kwargs): + return data.decode("base64") + + +getInfo = create_getInfo(BezvadataCz) diff --git a/module/plugins/hoster/BillionuploadsCom.py b/pyload/plugins/hoster/BillionuploadsCom.py index 91e869c59..91e869c59 100644 --- a/module/plugins/hoster/BillionuploadsCom.py +++ b/pyload/plugins/hoster/BillionuploadsCom.py diff --git a/module/plugins/hoster/BitshareCom.py b/pyload/plugins/hoster/BitshareCom.py index 2b3f7777f..2b3f7777f 100644 --- a/module/plugins/hoster/BitshareCom.py +++ b/pyload/plugins/hoster/BitshareCom.py diff --git a/module/plugins/hoster/BoltsharingCom.py b/pyload/plugins/hoster/BoltsharingCom.py index f9cc91ca5..f9cc91ca5 100644 --- a/module/plugins/hoster/BoltsharingCom.py +++ b/pyload/plugins/hoster/BoltsharingCom.py diff --git a/module/plugins/hoster/CatShareNet.py b/pyload/plugins/hoster/CatShareNet.py index 66d46c2e8..66d46c2e8 100644 --- a/module/plugins/hoster/CatShareNet.py +++ b/pyload/plugins/hoster/CatShareNet.py diff --git a/module/plugins/hoster/ChipDe.py b/pyload/plugins/hoster/ChipDe.py index 8e13b914d..8e13b914d 100644 --- a/module/plugins/hoster/ChipDe.py +++ b/pyload/plugins/hoster/ChipDe.py diff --git a/module/plugins/hoster/CloudzerNet.py b/pyload/plugins/hoster/CloudzerNet.py index e95f90792..e95f90792 100644 --- a/module/plugins/hoster/CloudzerNet.py +++ b/pyload/plugins/hoster/CloudzerNet.py diff --git a/module/plugins/hoster/CramitIn.py b/pyload/plugins/hoster/CramitIn.py index 68df322f5..68df322f5 100644 --- a/module/plugins/hoster/CramitIn.py +++ b/pyload/plugins/hoster/CramitIn.py diff --git a/module/plugins/hoster/CrockoCom.py b/pyload/plugins/hoster/CrockoCom.py index 7d5336cd9..7d5336cd9 100644 --- a/module/plugins/hoster/CrockoCom.py +++ b/pyload/plugins/hoster/CrockoCom.py diff --git a/module/plugins/hoster/CyberlockerCh.py b/pyload/plugins/hoster/CyberlockerCh.py index 19a4473b3..19a4473b3 100644 --- a/module/plugins/hoster/CyberlockerCh.py +++ b/pyload/plugins/hoster/CyberlockerCh.py diff --git a/module/plugins/hoster/CzshareCom.py b/pyload/plugins/hoster/CzshareCom.py index 58573d329..58573d329 100644 --- a/module/plugins/hoster/CzshareCom.py +++ b/pyload/plugins/hoster/CzshareCom.py diff --git a/module/plugins/hoster/DailymotionCom.py b/pyload/plugins/hoster/DailymotionCom.py index 7d33540f8..7d33540f8 100644 --- a/module/plugins/hoster/DailymotionCom.py +++ b/pyload/plugins/hoster/DailymotionCom.py diff --git a/module/plugins/hoster/DataHu.py b/pyload/plugins/hoster/DataHu.py index 7abd93d1f..7abd93d1f 100644 --- a/module/plugins/hoster/DataHu.py +++ b/pyload/plugins/hoster/DataHu.py diff --git a/module/plugins/hoster/DataportCz.py b/pyload/plugins/hoster/DataportCz.py index 1e24388d7..1e24388d7 100644 --- a/module/plugins/hoster/DataportCz.py +++ b/pyload/plugins/hoster/DataportCz.py diff --git a/module/plugins/hoster/DateiTo.py b/pyload/plugins/hoster/DateiTo.py index d7760d940..d7760d940 100644 --- a/module/plugins/hoster/DateiTo.py +++ b/pyload/plugins/hoster/DateiTo.py diff --git a/module/plugins/hoster/DdlstorageCom.py b/pyload/plugins/hoster/DdlstorageCom.py index 82072aadb..82072aadb 100644 --- a/module/plugins/hoster/DdlstorageCom.py +++ b/pyload/plugins/hoster/DdlstorageCom.py diff --git a/pyload/plugins/hoster/DebridItaliaCom.py b/pyload/plugins/hoster/DebridItaliaCom.py new file mode 100644 index 000000000..ebea0ac13 --- /dev/null +++ b/pyload/plugins/hoster/DebridItaliaCom.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# 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. # +# # +# 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 Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import re + +from pyload.plugins.Hoster import Hoster + + +class DebridItaliaCom(Hoster): + __name__ = "DebridItaliaCom" + __version__ = "0.05" + __type__ = "hoster" + __config__ = [("activated", "bool", "Activated", "False"), + ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), + ("hosterList", "str", "Hoster list (comma separated)", ""), + ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), + ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + __pattern__ = r"https?://.*debriditalia\.com" + __description__ = """Debriditalia.com hoster plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + def setup(self): + self.chunkLimit = -1 + self.resumeDownload = True + + def process(self, pyfile): + if re.match(self.__pattern__, pyfile.url): + new_url = pyfile.url + elif not self.account: + self.logError(_("Please enter your %s account or deactivate this plugin") % "DebridItalia") + self.fail("No DebridItalia account provided") + else: + self.logDebug("Old URL: %s" % pyfile.url) + url = "http://debriditalia.com/linkgen2.php?xjxfun=convertiLink&xjxargs[]=S<![CDATA[%s]]>" % pyfile.url + page = self.load(url) + self.logDebug("XML data: %s" % page) + + if 'File not available' in page: + self.fail('File not available') + else: + new_url = re.search(r'<a href="(?:[^"]+)">(?P<direct>[^<]+)</a>', page).group('direct') + + if new_url != pyfile.url: + self.logDebug("New URL: %s" % new_url) + + self.download(new_url, disposition=True) + + check = self.checkDownload({"empty": re.compile(r"^$")}) + + if check == "empty": + self.retry(5, 120, 'Empty file downloaded') diff --git a/module/plugins/hoster/DepositfilesCom.py b/pyload/plugins/hoster/DepositfilesCom.py index 99c0d13a1..99c0d13a1 100644 --- a/module/plugins/hoster/DepositfilesCom.py +++ b/pyload/plugins/hoster/DepositfilesCom.py diff --git a/module/plugins/hoster/DlFreeFr.py b/pyload/plugins/hoster/DlFreeFr.py index 35b9ca6b8..35b9ca6b8 100644 --- a/module/plugins/hoster/DlFreeFr.py +++ b/pyload/plugins/hoster/DlFreeFr.py diff --git a/module/plugins/hoster/DuploadOrg.py b/pyload/plugins/hoster/DuploadOrg.py index 6ee2bcc7b..6ee2bcc7b 100644 --- a/module/plugins/hoster/DuploadOrg.py +++ b/pyload/plugins/hoster/DuploadOrg.py diff --git a/module/plugins/hoster/EasybytezCom.py b/pyload/plugins/hoster/EasybytezCom.py index 98691a641..98691a641 100644 --- a/module/plugins/hoster/EasybytezCom.py +++ b/pyload/plugins/hoster/EasybytezCom.py diff --git a/module/plugins/hoster/EdiskCz.py b/pyload/plugins/hoster/EdiskCz.py index c172e7447..c172e7447 100644 --- a/module/plugins/hoster/EdiskCz.py +++ b/pyload/plugins/hoster/EdiskCz.py diff --git a/module/plugins/hoster/EgoFilesCom.py b/pyload/plugins/hoster/EgoFilesCom.py index 22ca99931..22ca99931 100644 --- a/module/plugins/hoster/EgoFilesCom.py +++ b/pyload/plugins/hoster/EgoFilesCom.py diff --git a/module/plugins/hoster/EuroshareEu.py b/pyload/plugins/hoster/EuroshareEu.py index 03bec8bbd..03bec8bbd 100644 --- a/module/plugins/hoster/EuroshareEu.py +++ b/pyload/plugins/hoster/EuroshareEu.py diff --git a/module/plugins/hoster/ExtabitCom.py b/pyload/plugins/hoster/ExtabitCom.py index f68627b56..f68627b56 100644 --- a/module/plugins/hoster/ExtabitCom.py +++ b/pyload/plugins/hoster/ExtabitCom.py diff --git a/module/plugins/hoster/FastixRu.py b/pyload/plugins/hoster/FastixRu.py index 8805081c9..8805081c9 100644 --- a/module/plugins/hoster/FastixRu.py +++ b/pyload/plugins/hoster/FastixRu.py diff --git a/module/plugins/hoster/FastshareCz.py b/pyload/plugins/hoster/FastshareCz.py index 3e33d0fd9..3e33d0fd9 100644 --- a/module/plugins/hoster/FastshareCz.py +++ b/pyload/plugins/hoster/FastshareCz.py diff --git a/module/plugins/hoster/FileApeCom.py b/pyload/plugins/hoster/FileApeCom.py index f07fbfc8a..f07fbfc8a 100644 --- a/module/plugins/hoster/FileApeCom.py +++ b/pyload/plugins/hoster/FileApeCom.py diff --git a/module/plugins/hoster/FilebeerInfo.py b/pyload/plugins/hoster/FilebeerInfo.py index 216ecfbca..216ecfbca 100644 --- a/module/plugins/hoster/FilebeerInfo.py +++ b/pyload/plugins/hoster/FilebeerInfo.py diff --git a/module/plugins/hoster/FilecloudIo.py b/pyload/plugins/hoster/FilecloudIo.py index c7684a05d..c7684a05d 100644 --- a/module/plugins/hoster/FilecloudIo.py +++ b/pyload/plugins/hoster/FilecloudIo.py diff --git a/module/plugins/hoster/FilefactoryCom.py b/pyload/plugins/hoster/FilefactoryCom.py index fdde1f9d7..fdde1f9d7 100644 --- a/module/plugins/hoster/FilefactoryCom.py +++ b/pyload/plugins/hoster/FilefactoryCom.py diff --git a/module/plugins/hoster/FilejungleCom.py b/pyload/plugins/hoster/FilejungleCom.py index 0fb0b6150..0fb0b6150 100644 --- a/module/plugins/hoster/FilejungleCom.py +++ b/pyload/plugins/hoster/FilejungleCom.py diff --git a/module/plugins/hoster/FilepostCom.py b/pyload/plugins/hoster/FilepostCom.py index 0c3cb925b..0c3cb925b 100644 --- a/module/plugins/hoster/FilepostCom.py +++ b/pyload/plugins/hoster/FilepostCom.py diff --git a/module/plugins/hoster/FilerNet.py b/pyload/plugins/hoster/FilerNet.py index 8e8cee526..8e8cee526 100644 --- a/module/plugins/hoster/FilerNet.py +++ b/pyload/plugins/hoster/FilerNet.py diff --git a/module/plugins/hoster/FilerioCom.py b/pyload/plugins/hoster/FilerioCom.py index 7adffc4ed..7adffc4ed 100644 --- a/module/plugins/hoster/FilerioCom.py +++ b/pyload/plugins/hoster/FilerioCom.py diff --git a/pyload/plugins/hoster/FilesMailRu.py b/pyload/plugins/hoster/FilesMailRu.py new file mode 100644 index 000000000..2b87c309e --- /dev/null +++ b/pyload/plugins/hoster/FilesMailRu.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from module.plugins.Hoster import Hoster, chunks +from module.network.RequestFactory import getURL + + + +def getInfo(urls): + result = [] + for chunk in chunks(urls, 10): + for url in chunk: + src = getURL(url) + if r'<div class="errorMessage mb10">' in src: + result.append((url, 0, 1, url)) + elif r'Page cannot be displayed' in src: + result.append((url, 0, 1, url)) + else: + try: + url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' + file_name = re.search(url_pattern, src).group(0).split(', event)">')[1].split('</a>')[0] + result.append((file_name, 0, 2, url)) + except: + pass + + # status 1=OFFLINE, 2=OK, 3=UNKNOWN + # result.append((#name,#size,#status,#url)) + yield result + + +class FilesMailRu(Hoster): + __name__ = "FilesMailRu" + __type__ = "hoster" + __pattern__ = r"http://files\.mail\.ru/.*" + __version__ = "0.31" + __description__ = """Files.Mail.Ru One-Klick Hoster""" + __author_name__ = ("oZiRiz") + __author_mail__ = ("ich@oziriz.de") + + def setup(self): + if not self.account: + self.multiDL = False + + def process(self, pyfile): + self.html = self.load(pyfile.url) + self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' + + #marks the file as "offline" when the pattern was found on the html-page''' + if r'<div class="errorMessage mb10">' in self.html: + self.offline() + + elif r'Page cannot be displayed' in self.html: + self.offline() + + #the filename that will be showed in the list (e.g. test.part1.rar)''' + pyfile.name = self.getFileName() + + #prepare and download''' + if not self.account: + self.prepare() + self.download(self.getFileUrl()) + self.myPostProcess() + else: + self.download(self.getFileUrl()) + self.myPostProcess() + + def prepare(self): + """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected""" + self.setWait(10) + self.wait() + return True + + def getFileUrl(self): + """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html""" + file_url = re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[ + 0] + return file_url + + def getFileName(self): + """gives you the Name for each file. Also extracted from the HTML-Page""" + file_name = re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0] + return file_name + + def myPostProcess(self): + # searches the file for HTMl-Code. Sometimes the Redirect + # doesn't work (maybe a curl Problem) and you get only a small + # HTML file and the Download is marked as "finished" + # then the download will be restarted. It's only bad for these + # who want download a HTML-File (it's one in a million ;-) ) + # + # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB + # so i set it to check every download because sometimes there are downloads + # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file + # (Loading 100MB in to ram is not an option) + check = self.checkDownload({"html": "<meta name="}, read_size=50000) + if check == "html": + self.logInfo(_( + "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." % + self.pyfile.name)) + self.retry() diff --git a/module/plugins/hoster/FileserveCom.py b/pyload/plugins/hoster/FileserveCom.py index a9ff24d19..a9ff24d19 100644 --- a/module/plugins/hoster/FileserveCom.py +++ b/pyload/plugins/hoster/FileserveCom.py diff --git a/module/plugins/hoster/FileshareInUa.py b/pyload/plugins/hoster/FileshareInUa.py index 11adc4e9c..11adc4e9c 100644 --- a/module/plugins/hoster/FileshareInUa.py +++ b/pyload/plugins/hoster/FileshareInUa.py diff --git a/module/plugins/hoster/FilezyNet.py b/pyload/plugins/hoster/FilezyNet.py index c31707089..c31707089 100644 --- a/module/plugins/hoster/FilezyNet.py +++ b/pyload/plugins/hoster/FilezyNet.py diff --git a/module/plugins/hoster/FlyFilesNet.py b/pyload/plugins/hoster/FlyFilesNet.py index 8d0ff0a08..8d0ff0a08 100644 --- a/module/plugins/hoster/FlyFilesNet.py +++ b/pyload/plugins/hoster/FlyFilesNet.py diff --git a/module/plugins/hoster/FourSharedCom.py b/pyload/plugins/hoster/FourSharedCom.py index 6b3e33284..6b3e33284 100644 --- a/module/plugins/hoster/FourSharedCom.py +++ b/pyload/plugins/hoster/FourSharedCom.py diff --git a/module/plugins/hoster/FreakshareCom.py b/pyload/plugins/hoster/FreakshareCom.py index cb84a468a..cb84a468a 100644 --- a/module/plugins/hoster/FreakshareCom.py +++ b/pyload/plugins/hoster/FreakshareCom.py diff --git a/module/plugins/hoster/FreevideoCz.py b/pyload/plugins/hoster/FreevideoCz.py index 3d8921c38..3d8921c38 100644 --- a/module/plugins/hoster/FreevideoCz.py +++ b/pyload/plugins/hoster/FreevideoCz.py diff --git a/module/plugins/hoster/FshareVn.py b/pyload/plugins/hoster/FshareVn.py index fa0bcb253..fa0bcb253 100644 --- a/module/plugins/hoster/FshareVn.py +++ b/pyload/plugins/hoster/FshareVn.py diff --git a/module/plugins/hoster/Ftp.py b/pyload/plugins/hoster/Ftp.py index 77992372a..77992372a 100644 --- a/module/plugins/hoster/Ftp.py +++ b/pyload/plugins/hoster/Ftp.py diff --git a/module/plugins/hoster/GamefrontCom.py b/pyload/plugins/hoster/GamefrontCom.py index c82cfdf50..c82cfdf50 100644 --- a/module/plugins/hoster/GamefrontCom.py +++ b/pyload/plugins/hoster/GamefrontCom.py diff --git a/module/plugins/hoster/GigapetaCom.py b/pyload/plugins/hoster/GigapetaCom.py index de5d94513..de5d94513 100644 --- a/module/plugins/hoster/GigapetaCom.py +++ b/pyload/plugins/hoster/GigapetaCom.py diff --git a/module/plugins/hoster/GooIm.py b/pyload/plugins/hoster/GooIm.py index f96e6e6cc..f96e6e6cc 100644 --- a/module/plugins/hoster/GooIm.py +++ b/pyload/plugins/hoster/GooIm.py diff --git a/module/plugins/hoster/HellshareCz.py b/pyload/plugins/hoster/HellshareCz.py index 5fdcca7ae..5fdcca7ae 100644 --- a/module/plugins/hoster/HellshareCz.py +++ b/pyload/plugins/hoster/HellshareCz.py diff --git a/module/plugins/hoster/HellspyCz.py b/pyload/plugins/hoster/HellspyCz.py index 2e0746ff4..2e0746ff4 100644 --- a/module/plugins/hoster/HellspyCz.py +++ b/pyload/plugins/hoster/HellspyCz.py diff --git a/pyload/plugins/hoster/HotfileCom.py b/pyload/plugins/hoster/HotfileCom.py new file mode 100644 index 000000000..255acad33 --- /dev/null +++ b/pyload/plugins/hoster/HotfileCom.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from module.plugins.Hoster import Hoster +from module.plugins.internal.CaptchaService import ReCaptcha + +from module.network.RequestFactory import getURL +from module.utils import chunks + + + +def getInfo(urls): + api_url_base = "http://api.hotfile.com/" + + for chunk in chunks(urls, 90): + api_param_file = {"action": "checklinks", "links": ",".join(chunk), + "fields": "id,status,name,size"} #api only supports old style links + src = getURL(api_url_base, post=api_param_file, decode=True) + result = [] + for i, res in enumerate(src.split("\n")): + if not res: + continue + fields = res.split(",") + + if fields[1] in ("1", "2"): + status = 2 + else: + status = 1 + + result.append((fields[2], int(fields[3]), status, chunk[i])) + yield result + + +class HotfileCom(Hoster): + __name__ = "HotfileCom" + __type__ = "hoster" + __pattern__ = r"https?://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+/" + __version__ = "0.36" + __description__ = """Hotfile.com Download Hoster""" + __author_name__ = ("sitacuisses", "spoob", "mkaay", "JoKoT3") + __author_mail__ = ("sitacuisses@yhoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "jokot3@gmail.com") + + FILE_OFFLINE_PATTERN = r'File is removed' + + def setup(self): + self.html = [None, None] + self.wantReconnect = False + self.htmlwithlink = None + self.url = None + + if self.premium: + self.multiDL = self.resumeDownload = True + self.chunkLimit = -1 + else: + self.multiDL = False + self.chunkLimit = 1 + + def apiCall(self, method, post, login=False): + if not self.account and login: + return + elif self.account and login: + return self.account.apiCall(method, post, self.user) + post.update({"action": method}) + return self.load("http://api.hotfile.com/", post=post, decode=True) + + def process(self, pyfile): + self.wantReconnect = False + + args = {"links": self.pyfile.url, "fields": "id,status,name,size,sha1"} + resp = self.apiCall("checklinks", args) + self.api_data = {} + for k, v in zip(args["fields"].split(","), resp.strip().split(",")): + self.api_data[k] = v + + if self.api_data["status"] == "0": + self.offline() + + pyfile.name = self.api_data["name"] + + if not self.premium: + self.downloadHTML() + + if self.FILE_OFFLINE_PATTERN in self.html[0]: + self.offline() + + self.setWait(self.getWaitTime()) + self.wait() + + self.freeDownload() + else: + dl = self.account.apiCall("getdirectdownloadlink", {"link": self.pyfile.url}, self.user) + #dl = unquote(dl).strip() <- Made problems + dl = dl.strip() + self.download(dl) + + def downloadHTML(self): + self.html[0] = self.load(self.pyfile.url, get={"lang": "en"}) + + def freeDownload(self): + + form_content = re.search(r"<form style=.*(\n<.*>\s*)*?[\n\t]?<tr>", self.html[0]) + if form_content is None: + print self.html[0] + self.fail("Form not found in HTML. Can not proceed.") + + form_content = form_content.group(0) + form_posts = dict(re.findall(r"<input\stype=hidden\sname=(\S*)\svalue=(\S*)>", form_content)) + + self.html[1] = self.load(self.pyfile.url, post=form_posts) + + challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=([0-9A-Za-z]+)", self.html[1]) + + if challenge: + re_captcha = ReCaptcha(self) + challenge, result = re_captcha.challenge(challenge.group(1)) + + url = re.search(r'<form action="(/dl/[^"]+)', self.html[1]) + + self.html[1] = self.load("http://hotfile.com" + url.group(1), post={"action": "checkcaptcha", + "recaptcha_challenge_field": challenge, + "recaptcha_response_field": result}) + + if "Wrong Code. Please try again." in self.html[1]: + self.freeDownload() + return + + file_url = re.search(r'a href="(http://hotfile\.com/get/\S*)"', self.html[1]).group(1) + self.download(file_url) + + def getWaitTime(self): + free_limit_pattern = re.compile(r"timerend=d\.getTime\(\)\+(\d+);") + matches = free_limit_pattern.findall(self.html[0]) + if matches: + wait_time = (sum([int(match) for match in matches]) / 1000) or 60 + if wait_time > 300: + self.wantReconnect = True + return wait_time + 1 + else: + self.fail("Don't know how long to wait. Cannot proceed.") diff --git a/module/plugins/hoster/HundredEightyUploadCom.py b/pyload/plugins/hoster/HundredEightyUploadCom.py index 3cf32d338..3cf32d338 100644 --- a/module/plugins/hoster/HundredEightyUploadCom.py +++ b/pyload/plugins/hoster/HundredEightyUploadCom.py diff --git a/module/plugins/hoster/IFileWs.py b/pyload/plugins/hoster/IFileWs.py index 160fe641c..160fe641c 100644 --- a/module/plugins/hoster/IFileWs.py +++ b/pyload/plugins/hoster/IFileWs.py diff --git a/module/plugins/hoster/IcyFilesCom.py b/pyload/plugins/hoster/IcyFilesCom.py index 53c934675..53c934675 100644 --- a/module/plugins/hoster/IcyFilesCom.py +++ b/pyload/plugins/hoster/IcyFilesCom.py diff --git a/module/plugins/hoster/IfileIt.py b/pyload/plugins/hoster/IfileIt.py index 0e3bdd227..0e3bdd227 100644 --- a/module/plugins/hoster/IfileIt.py +++ b/pyload/plugins/hoster/IfileIt.py diff --git a/module/plugins/hoster/IfolderRu.py b/pyload/plugins/hoster/IfolderRu.py index 14e568f8f..14e568f8f 100644 --- a/module/plugins/hoster/IfolderRu.py +++ b/pyload/plugins/hoster/IfolderRu.py diff --git a/module/plugins/hoster/JumbofilesCom.py b/pyload/plugins/hoster/JumbofilesCom.py index 1b8a2d73b..1b8a2d73b 100644 --- a/module/plugins/hoster/JumbofilesCom.py +++ b/pyload/plugins/hoster/JumbofilesCom.py diff --git a/module/plugins/hoster/Keep2shareCC.py b/pyload/plugins/hoster/Keep2shareCC.py index d2a13e35b..d2a13e35b 100644 --- a/module/plugins/hoster/Keep2shareCC.py +++ b/pyload/plugins/hoster/Keep2shareCC.py diff --git a/module/plugins/hoster/LetitbitNet.py b/pyload/plugins/hoster/LetitbitNet.py index 45f029c7b..45f029c7b 100644 --- a/module/plugins/hoster/LetitbitNet.py +++ b/pyload/plugins/hoster/LetitbitNet.py diff --git a/module/plugins/hoster/LoadTo.py b/pyload/plugins/hoster/LoadTo.py index 0f99c272a..0f99c272a 100644 --- a/module/plugins/hoster/LoadTo.py +++ b/pyload/plugins/hoster/LoadTo.py diff --git a/module/plugins/hoster/LuckyShareNet.py b/pyload/plugins/hoster/LuckyShareNet.py index 08e44d9f6..08e44d9f6 100644 --- a/module/plugins/hoster/LuckyShareNet.py +++ b/pyload/plugins/hoster/LuckyShareNet.py diff --git a/module/plugins/hoster/MediafireCom.py b/pyload/plugins/hoster/MediafireCom.py index 494d0049e..494d0049e 100644 --- a/module/plugins/hoster/MediafireCom.py +++ b/pyload/plugins/hoster/MediafireCom.py diff --git a/module/plugins/hoster/MegaNz.py b/pyload/plugins/hoster/MegaNz.py index bf4223213..bf4223213 100644 --- a/module/plugins/hoster/MegaNz.py +++ b/pyload/plugins/hoster/MegaNz.py diff --git a/module/plugins/hoster/MegacrypterCom.py b/pyload/plugins/hoster/MegacrypterCom.py index c166146dd..c166146dd 100644 --- a/module/plugins/hoster/MegacrypterCom.py +++ b/pyload/plugins/hoster/MegacrypterCom.py diff --git a/module/plugins/hoster/MegasharesCom.py b/pyload/plugins/hoster/MegasharesCom.py index 26cf8ab8e..26cf8ab8e 100644 --- a/module/plugins/hoster/MegasharesCom.py +++ b/pyload/plugins/hoster/MegasharesCom.py diff --git a/module/plugins/hoster/MovReelCom.py b/pyload/plugins/hoster/MovReelCom.py index 5d8754ee7..5d8754ee7 100644 --- a/module/plugins/hoster/MovReelCom.py +++ b/pyload/plugins/hoster/MovReelCom.py diff --git a/pyload/plugins/hoster/MultiDebridCom.py b/pyload/plugins/hoster/MultiDebridCom.py new file mode 100644 index 000000000..e38a16cbb --- /dev/null +++ b/pyload/plugins/hoster/MultiDebridCom.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# 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. # +# # +# 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 Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import re + +from pyload.plugins.Hoster import Hoster +from pyload.utils import json_loads + + +class MultiDebridCom(Hoster): + __name__ = "MultiDebridCom" + __version__ = "0.03" + __type__ = "hoster" + __config__ = [("activated", "bool", "Activated", "False"), + ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), + ("hosterList", "str", "Hoster list (comma separated)", ""), + ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), + ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + __pattern__ = r"http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/" + __description__ = """Multi-debrid.com hoster plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + def setup(self): + self.chunkLimit = -1 + self.resumeDownload = True + + def process(self, pyfile): + if re.match(self.__pattern__, pyfile.url): + new_url = pyfile.url + elif not self.account: + self.logError(_("Please enter your %s account or deactivate this plugin") % "Multi-debrid.com") + self.fail("No Multi-debrid.com account provided") + else: + self.logDebug("Original URL: %s" % pyfile.url) + page = self.req.load('http://multi-debrid.com/api.php', + get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'], + 'link': pyfile.url}) + self.logDebug("JSON data: " + page) + page = json_loads(page) + if page['status'] != 'ok': + self.fail('Unable to unrestrict link') + new_url = page['link'] + + if new_url != pyfile.url: + self.logDebug("Unrestricted URL: " + new_url) + + self.download(new_url, disposition=True) diff --git a/module/plugins/hoster/MultishareCz.py b/pyload/plugins/hoster/MultishareCz.py index f9289a923..f9289a923 100644 --- a/module/plugins/hoster/MultishareCz.py +++ b/pyload/plugins/hoster/MultishareCz.py diff --git a/module/plugins/hoster/MyvideoDe.py b/pyload/plugins/hoster/MyvideoDe.py index 1bd73e376..1bd73e376 100644 --- a/module/plugins/hoster/MyvideoDe.py +++ b/pyload/plugins/hoster/MyvideoDe.py diff --git a/module/plugins/hoster/NarodRu.py b/pyload/plugins/hoster/NarodRu.py index 590268910..590268910 100644 --- a/module/plugins/hoster/NarodRu.py +++ b/pyload/plugins/hoster/NarodRu.py diff --git a/module/plugins/hoster/NetloadIn.py b/pyload/plugins/hoster/NetloadIn.py index 71c8dd4f2..71c8dd4f2 100644 --- a/module/plugins/hoster/NetloadIn.py +++ b/pyload/plugins/hoster/NetloadIn.py diff --git a/module/plugins/hoster/NovafileCom.py b/pyload/plugins/hoster/NovafileCom.py index 599ec5f7d..599ec5f7d 100644 --- a/module/plugins/hoster/NovafileCom.py +++ b/pyload/plugins/hoster/NovafileCom.py diff --git a/module/plugins/hoster/NowDownloadEu.py b/pyload/plugins/hoster/NowDownloadEu.py index b5e6b6493..b5e6b6493 100644 --- a/module/plugins/hoster/NowDownloadEu.py +++ b/pyload/plugins/hoster/NowDownloadEu.py diff --git a/module/plugins/hoster/OneFichierCom.py b/pyload/plugins/hoster/OneFichierCom.py index 54bf1d1fa..54bf1d1fa 100644 --- a/module/plugins/hoster/OneFichierCom.py +++ b/pyload/plugins/hoster/OneFichierCom.py diff --git a/module/plugins/hoster/PornhostCom.py b/pyload/plugins/hoster/PornhostCom.py index 1d340694b..1d340694b 100644 --- a/module/plugins/hoster/PornhostCom.py +++ b/pyload/plugins/hoster/PornhostCom.py diff --git a/module/plugins/hoster/PornhubCom.py b/pyload/plugins/hoster/PornhubCom.py index 7bbb8d919..7bbb8d919 100644 --- a/module/plugins/hoster/PornhubCom.py +++ b/pyload/plugins/hoster/PornhubCom.py diff --git a/module/plugins/hoster/Premium4Me.py b/pyload/plugins/hoster/Premium4Me.py index d6c154693..d6c154693 100644 --- a/module/plugins/hoster/Premium4Me.py +++ b/pyload/plugins/hoster/Premium4Me.py diff --git a/module/plugins/hoster/PremiumizeMe.py b/pyload/plugins/hoster/PremiumizeMe.py index c5c09857f..c5c09857f 100644 --- a/module/plugins/hoster/PremiumizeMe.py +++ b/pyload/plugins/hoster/PremiumizeMe.py diff --git a/module/plugins/hoster/PutlockerCom.py b/pyload/plugins/hoster/PutlockerCom.py index 7b9ac1f84..7b9ac1f84 100644 --- a/module/plugins/hoster/PutlockerCom.py +++ b/pyload/plugins/hoster/PutlockerCom.py diff --git a/module/plugins/hoster/QuickshareCz.py b/pyload/plugins/hoster/QuickshareCz.py index a781d7eff..a781d7eff 100644 --- a/module/plugins/hoster/QuickshareCz.py +++ b/pyload/plugins/hoster/QuickshareCz.py diff --git a/module/plugins/hoster/RPNetBiz.py b/pyload/plugins/hoster/RPNetBiz.py index ae8ccf8a9..ae8ccf8a9 100644 --- a/module/plugins/hoster/RPNetBiz.py +++ b/pyload/plugins/hoster/RPNetBiz.py diff --git a/module/plugins/hoster/RapidgatorNet.py b/pyload/plugins/hoster/RapidgatorNet.py index 611d2ba5d..611d2ba5d 100644 --- a/module/plugins/hoster/RapidgatorNet.py +++ b/pyload/plugins/hoster/RapidgatorNet.py diff --git a/module/plugins/hoster/RapidshareCom.py b/pyload/plugins/hoster/RapidshareCom.py index 9828bf4ce..9828bf4ce 100644 --- a/module/plugins/hoster/RapidshareCom.py +++ b/pyload/plugins/hoster/RapidshareCom.py diff --git a/module/plugins/hoster/RarefileNet.py b/pyload/plugins/hoster/RarefileNet.py index 1ede51953..1ede51953 100644 --- a/module/plugins/hoster/RarefileNet.py +++ b/pyload/plugins/hoster/RarefileNet.py diff --git a/module/plugins/hoster/RealdebridCom.py b/pyload/plugins/hoster/RealdebridCom.py index 40ee96df9..40ee96df9 100644 --- a/module/plugins/hoster/RealdebridCom.py +++ b/pyload/plugins/hoster/RealdebridCom.py diff --git a/module/plugins/hoster/RedtubeCom.py b/pyload/plugins/hoster/RedtubeCom.py index caf33eeac..caf33eeac 100644 --- a/module/plugins/hoster/RedtubeCom.py +++ b/pyload/plugins/hoster/RedtubeCom.py diff --git a/module/plugins/hoster/RehostTo.py b/pyload/plugins/hoster/RehostTo.py index bb6110415..bb6110415 100644 --- a/module/plugins/hoster/RehostTo.py +++ b/pyload/plugins/hoster/RehostTo.py diff --git a/module/plugins/hoster/ReloadCc.py b/pyload/plugins/hoster/ReloadCc.py index 2295c792a..2295c792a 100644 --- a/module/plugins/hoster/ReloadCc.py +++ b/pyload/plugins/hoster/ReloadCc.py diff --git a/module/plugins/hoster/RgHostNet.py b/pyload/plugins/hoster/RgHostNet.py index a46b51733..a46b51733 100644 --- a/module/plugins/hoster/RgHostNet.py +++ b/pyload/plugins/hoster/RgHostNet.py diff --git a/module/plugins/hoster/RyushareCom.py b/pyload/plugins/hoster/RyushareCom.py index 675eaddde..675eaddde 100644 --- a/module/plugins/hoster/RyushareCom.py +++ b/pyload/plugins/hoster/RyushareCom.py diff --git a/module/plugins/hoster/SecureUploadEu.py b/pyload/plugins/hoster/SecureUploadEu.py index dc220c508..dc220c508 100644 --- a/module/plugins/hoster/SecureUploadEu.py +++ b/pyload/plugins/hoster/SecureUploadEu.py diff --git a/module/plugins/hoster/SendmywayCom.py b/pyload/plugins/hoster/SendmywayCom.py index 9b4fc14bc..9b4fc14bc 100644 --- a/module/plugins/hoster/SendmywayCom.py +++ b/pyload/plugins/hoster/SendmywayCom.py diff --git a/module/plugins/hoster/SendspaceCom.py b/pyload/plugins/hoster/SendspaceCom.py index 729ee17bc..729ee17bc 100644 --- a/module/plugins/hoster/SendspaceCom.py +++ b/pyload/plugins/hoster/SendspaceCom.py diff --git a/module/plugins/hoster/Share4webCom.py b/pyload/plugins/hoster/Share4webCom.py index ead808024..ead808024 100644 --- a/module/plugins/hoster/Share4webCom.py +++ b/pyload/plugins/hoster/Share4webCom.py diff --git a/module/plugins/hoster/Share76Com.py b/pyload/plugins/hoster/Share76Com.py index b48780652..b48780652 100644 --- a/module/plugins/hoster/Share76Com.py +++ b/pyload/plugins/hoster/Share76Com.py diff --git a/module/plugins/hoster/ShareFilesCo.py b/pyload/plugins/hoster/ShareFilesCo.py index 245e20ea6..245e20ea6 100644 --- a/module/plugins/hoster/ShareFilesCo.py +++ b/pyload/plugins/hoster/ShareFilesCo.py diff --git a/module/plugins/hoster/ShareRapidCom.py b/pyload/plugins/hoster/ShareRapidCom.py index 82f98d73c..82f98d73c 100644 --- a/module/plugins/hoster/ShareRapidCom.py +++ b/pyload/plugins/hoster/ShareRapidCom.py diff --git a/module/plugins/hoster/SharebeesCom.py b/pyload/plugins/hoster/SharebeesCom.py index cc1173fea..cc1173fea 100644 --- a/module/plugins/hoster/SharebeesCom.py +++ b/pyload/plugins/hoster/SharebeesCom.py diff --git a/module/plugins/hoster/ShareonlineBiz.py b/pyload/plugins/hoster/ShareonlineBiz.py index 7c0f9c069..7c0f9c069 100644 --- a/module/plugins/hoster/ShareonlineBiz.py +++ b/pyload/plugins/hoster/ShareonlineBiz.py diff --git a/module/plugins/hoster/ShareplaceCom.py b/pyload/plugins/hoster/ShareplaceCom.py index e51c3160f..e51c3160f 100644 --- a/module/plugins/hoster/ShareplaceCom.py +++ b/pyload/plugins/hoster/ShareplaceCom.py diff --git a/module/plugins/hoster/ShragleCom.py b/pyload/plugins/hoster/ShragleCom.py index 5d19afbc7..5d19afbc7 100644 --- a/module/plugins/hoster/ShragleCom.py +++ b/pyload/plugins/hoster/ShragleCom.py diff --git a/module/plugins/hoster/SimplydebridCom.py b/pyload/plugins/hoster/SimplydebridCom.py index 67cc39255..67cc39255 100644 --- a/module/plugins/hoster/SimplydebridCom.py +++ b/pyload/plugins/hoster/SimplydebridCom.py diff --git a/module/plugins/hoster/SockshareCom.py b/pyload/plugins/hoster/SockshareCom.py index b2635d8bc..b2635d8bc 100644 --- a/module/plugins/hoster/SockshareCom.py +++ b/pyload/plugins/hoster/SockshareCom.py diff --git a/module/plugins/hoster/SpeedLoadOrg.py b/pyload/plugins/hoster/SpeedLoadOrg.py index 5687fae85..5687fae85 100644 --- a/module/plugins/hoster/SpeedLoadOrg.py +++ b/pyload/plugins/hoster/SpeedLoadOrg.py diff --git a/module/plugins/hoster/SpeedfileCz.py b/pyload/plugins/hoster/SpeedfileCz.py index 3475ea29e..3475ea29e 100644 --- a/module/plugins/hoster/SpeedfileCz.py +++ b/pyload/plugins/hoster/SpeedfileCz.py diff --git a/module/plugins/hoster/StreamCz.py b/pyload/plugins/hoster/StreamCz.py index a575b205e..a575b205e 100644 --- a/module/plugins/hoster/StreamCz.py +++ b/pyload/plugins/hoster/StreamCz.py diff --git a/module/plugins/hoster/StreamcloudEu.py b/pyload/plugins/hoster/StreamcloudEu.py index dd9bf9429..dd9bf9429 100644 --- a/module/plugins/hoster/StreamcloudEu.py +++ b/pyload/plugins/hoster/StreamcloudEu.py diff --git a/module/plugins/hoster/TurbobitNet.py b/pyload/plugins/hoster/TurbobitNet.py index d574d1fa7..d574d1fa7 100644 --- a/module/plugins/hoster/TurbobitNet.py +++ b/pyload/plugins/hoster/TurbobitNet.py diff --git a/module/plugins/hoster/TurbouploadCom.py b/pyload/plugins/hoster/TurbouploadCom.py index 12dad7906..12dad7906 100644 --- a/module/plugins/hoster/TurbouploadCom.py +++ b/pyload/plugins/hoster/TurbouploadCom.py diff --git a/module/plugins/hoster/TusfilesNet.py b/pyload/plugins/hoster/TusfilesNet.py index e4a64cfdc..e4a64cfdc 100644 --- a/module/plugins/hoster/TusfilesNet.py +++ b/pyload/plugins/hoster/TusfilesNet.py diff --git a/module/plugins/hoster/TwoSharedCom.py b/pyload/plugins/hoster/TwoSharedCom.py index 5d1cd835b..5d1cd835b 100644 --- a/module/plugins/hoster/TwoSharedCom.py +++ b/pyload/plugins/hoster/TwoSharedCom.py diff --git a/module/plugins/hoster/UlozTo.py b/pyload/plugins/hoster/UlozTo.py index fe0bc671e..fe0bc671e 100644 --- a/module/plugins/hoster/UlozTo.py +++ b/pyload/plugins/hoster/UlozTo.py diff --git a/module/plugins/hoster/UloziskoSk.py b/pyload/plugins/hoster/UloziskoSk.py index 7060dff65..7060dff65 100644 --- a/module/plugins/hoster/UloziskoSk.py +++ b/pyload/plugins/hoster/UloziskoSk.py diff --git a/module/plugins/hoster/UnibytesCom.py b/pyload/plugins/hoster/UnibytesCom.py index d13f01ef3..d13f01ef3 100644 --- a/module/plugins/hoster/UnibytesCom.py +++ b/pyload/plugins/hoster/UnibytesCom.py diff --git a/module/plugins/hoster/UnrestrictLi.py b/pyload/plugins/hoster/UnrestrictLi.py index a3fe5ff5d..a3fe5ff5d 100644 --- a/module/plugins/hoster/UnrestrictLi.py +++ b/pyload/plugins/hoster/UnrestrictLi.py diff --git a/module/plugins/hoster/UploadStationCom.py b/pyload/plugins/hoster/UploadStationCom.py index 2831facac..2831facac 100644 --- a/module/plugins/hoster/UploadStationCom.py +++ b/pyload/plugins/hoster/UploadStationCom.py diff --git a/module/plugins/hoster/UploadedTo.py b/pyload/plugins/hoster/UploadedTo.py index 88a8edebb..88a8edebb 100644 --- a/module/plugins/hoster/UploadedTo.py +++ b/pyload/plugins/hoster/UploadedTo.py diff --git a/module/plugins/hoster/UploadheroCom.py b/pyload/plugins/hoster/UploadheroCom.py index 7b047e028..7b047e028 100644 --- a/module/plugins/hoster/UploadheroCom.py +++ b/pyload/plugins/hoster/UploadheroCom.py diff --git a/module/plugins/hoster/UploadingCom.py b/pyload/plugins/hoster/UploadingCom.py index 1da571460..1da571460 100644 --- a/module/plugins/hoster/UploadingCom.py +++ b/pyload/plugins/hoster/UploadingCom.py diff --git a/module/plugins/hoster/UptoboxCom.py b/pyload/plugins/hoster/UptoboxCom.py index fe05bf916..fe05bf916 100644 --- a/module/plugins/hoster/UptoboxCom.py +++ b/pyload/plugins/hoster/UptoboxCom.py diff --git a/module/plugins/hoster/VeehdCom.py b/pyload/plugins/hoster/VeehdCom.py index 88bcb20ad..88bcb20ad 100644 --- a/module/plugins/hoster/VeehdCom.py +++ b/pyload/plugins/hoster/VeehdCom.py diff --git a/module/plugins/hoster/WarserverCz.py b/pyload/plugins/hoster/WarserverCz.py index ee580fbdd..ee580fbdd 100644 --- a/module/plugins/hoster/WarserverCz.py +++ b/pyload/plugins/hoster/WarserverCz.py diff --git a/module/plugins/hoster/WebshareCz.py b/pyload/plugins/hoster/WebshareCz.py index 1c9ddb290..1c9ddb290 100644 --- a/module/plugins/hoster/WebshareCz.py +++ b/pyload/plugins/hoster/WebshareCz.py diff --git a/module/plugins/hoster/WrzucTo.py b/pyload/plugins/hoster/WrzucTo.py index 861e7f11e..861e7f11e 100644 --- a/module/plugins/hoster/WrzucTo.py +++ b/pyload/plugins/hoster/WrzucTo.py diff --git a/module/plugins/hoster/WuploadCom.py b/pyload/plugins/hoster/WuploadCom.py index aaeeb59fd..aaeeb59fd 100644 --- a/module/plugins/hoster/WuploadCom.py +++ b/pyload/plugins/hoster/WuploadCom.py diff --git a/module/plugins/hoster/X7To.py b/pyload/plugins/hoster/X7To.py index 59ec6ed06..59ec6ed06 100644 --- a/module/plugins/hoster/X7To.py +++ b/pyload/plugins/hoster/X7To.py diff --git a/module/plugins/hoster/XFileSharingPro.py b/pyload/plugins/hoster/XFileSharingPro.py index d6fb31307..d6fb31307 100644 --- a/module/plugins/hoster/XFileSharingPro.py +++ b/pyload/plugins/hoster/XFileSharingPro.py diff --git a/module/plugins/hoster/XHamsterCom.py b/pyload/plugins/hoster/XHamsterCom.py index 721f1cb75..721f1cb75 100644 --- a/module/plugins/hoster/XHamsterCom.py +++ b/pyload/plugins/hoster/XHamsterCom.py diff --git a/module/plugins/hoster/XVideosCom.py b/pyload/plugins/hoster/XVideosCom.py index 48a1f934d..48a1f934d 100644 --- a/module/plugins/hoster/XVideosCom.py +++ b/pyload/plugins/hoster/XVideosCom.py diff --git a/module/plugins/hoster/Xdcc.py b/pyload/plugins/hoster/Xdcc.py index bd8c6025a..bd8c6025a 100644 --- a/module/plugins/hoster/Xdcc.py +++ b/pyload/plugins/hoster/Xdcc.py diff --git a/module/plugins/hoster/YibaishiwuCom.py b/pyload/plugins/hoster/YibaishiwuCom.py index 37eaa17e5..37eaa17e5 100644 --- a/module/plugins/hoster/YibaishiwuCom.py +++ b/pyload/plugins/hoster/YibaishiwuCom.py diff --git a/module/plugins/hoster/YoupornCom.py b/pyload/plugins/hoster/YoupornCom.py index b0d594ca5..b0d594ca5 100644 --- a/module/plugins/hoster/YoupornCom.py +++ b/pyload/plugins/hoster/YoupornCom.py diff --git a/module/plugins/hoster/YourfilesTo.py b/pyload/plugins/hoster/YourfilesTo.py index 0fd094f61..0fd094f61 100644 --- a/module/plugins/hoster/YourfilesTo.py +++ b/pyload/plugins/hoster/YourfilesTo.py diff --git a/module/plugins/hoster/YoutubeCom.py b/pyload/plugins/hoster/YoutubeCom.py index 319eb36e6..319eb36e6 100644 --- a/module/plugins/hoster/YoutubeCom.py +++ b/pyload/plugins/hoster/YoutubeCom.py diff --git a/module/plugins/hoster/ZDF.py b/pyload/plugins/hoster/ZDF.py index 9940fd078..9940fd078 100644 --- a/module/plugins/hoster/ZDF.py +++ b/pyload/plugins/hoster/ZDF.py diff --git a/module/plugins/hoster/ZeveraCom.py b/pyload/plugins/hoster/ZeveraCom.py index e8b832a13..e8b832a13 100644 --- a/module/plugins/hoster/ZeveraCom.py +++ b/pyload/plugins/hoster/ZeveraCom.py diff --git a/module/plugins/hoster/ZippyshareCom.py b/pyload/plugins/hoster/ZippyshareCom.py index c98679a22..c98679a22 100644 --- a/module/plugins/hoster/ZippyshareCom.py +++ b/pyload/plugins/hoster/ZippyshareCom.py diff --git a/module/plugins/hooks/__init__.py b/pyload/plugins/hoster/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/hooks/__init__.py +++ b/pyload/plugins/hoster/__init__.py diff --git a/pyload/plugins/internal/AbstractExtractor.py b/pyload/plugins/internal/AbstractExtractor.py new file mode 100644 index 000000000..3ce76b6fa --- /dev/null +++ b/pyload/plugins/internal/AbstractExtractor.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class ArchiveError(Exception): + pass + +class CRCError(Exception): + pass + +class WrongPassword(Exception): + pass + +class AbtractExtractor: + + __version__ = "0.1" + + @staticmethod + def checkDeps(): + """ Check if system satisfies dependencies + :return: boolean + """ + return True + + @staticmethod + def getTargets(files_ids): + """ Filter suited targets from list of filename id tuple list + :param files_ids: List of file paths + :return: List of targets, id tuple list + """ + raise NotImplementedError + + + def __init__(self, m, file, out, fullpath, overwrite, excludefiles, renice): + """Initialize extractor for specific file + + :param m: ExtractArchive addon plugin + :param file: Absolute file path + :param out: Absolute path to destination directory + :param fullpath: Extract to fullpath + :param overwrite: Overwrite existing archives + :param renice: Renice value + """ + self.m = m + self.file = file + self.out = out + self.fullpath = fullpath + self.overwrite = overwrite + self.excludefiles = excludefiles + self.renice = renice + self.files = [] # Store extracted files here + + + def init(self): + """ Initialize additional data structures """ + pass + + + def checkArchive(self): + """Check if password is needed. Raise ArchiveError if integrity is + questionable. + + :return: boolean + :raises ArchiveError + """ + return False + + def checkPassword(self, password): + """ Check if the given password is/might be correct. + If it can not be decided at this point return true. + + :param password: + :return: boolean + """ + return True + + def extract(self, progress, password=None): + """Extract the archive. Raise specific errors in case of failure. + + :param progress: Progress function, call this to update status + :param password password to use + :raises WrongPassword + :raises CRCError + :raises ArchiveError + :return: + """ + raise NotImplementedError + + def getDeleteFiles(self): + """Return list of files to delete, do *not* delete them here. + + :return: List with paths of files to delete + """ + raise NotImplementedError + + def getExtractedFiles(self): + """Populate self.files at some point while extracting""" + return self.files diff --git a/pyload/plugins/internal/CaptchaService.py b/pyload/plugins/internal/CaptchaService.py new file mode 100644 index 000000000..4f903e3e6 --- /dev/null +++ b/pyload/plugins/internal/CaptchaService.py @@ -0,0 +1,75 @@ +# -*- 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: zoidberg +""" + +import re + +class CaptchaService(): + __version__ = "0.02" + + def __init__(self, plugin): + self.plugin = plugin + +class ReCaptcha(): + def __init__(self, plugin): + self.plugin = plugin + + def challenge(self, id): + js = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", get={"k":id}, cookies=True) + + try: + challenge = re.search("challenge : '(.*?)',", js).group(1) + server = re.search("server : '(.*?)',", js).group(1) + except: + self.plugin.fail("recaptcha error") + result = self.result(server,challenge) + + return challenge, result + + def result(self, server, challenge): + return self.plugin.decryptCaptcha("%simage"%server, get={"c":challenge}, cookies=True, forceUser=True, imgtype="jpg") + +class AdsCaptcha(CaptchaService): + def challenge(self, src): + js = self.plugin.req.load(src, cookies=True) + + try: + challenge = re.search("challenge: '(.*?)',", js).group(1) + server = re.search("server: '(.*?)',", js).group(1) + except: + self.plugin.fail("adscaptcha error") + result = self.result(server,challenge) + + return challenge, result + + def result(self, server, challenge): + return self.plugin.decryptCaptcha("%sChallenge.aspx" % server, get={"cid": challenge, "dummy": random()}, cookies=True, imgtype="jpg") + +class SolveMedia(CaptchaService): + + def challenge(self, src): + html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript?k=%s" % src, cookies=True) + try: + challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="([^"]+)">', html).group(1) + except: + self.plugin.fail("solvmedia error") + result = self.result(challenge) + + return challenge, result + + def result(self,challenge): + return self.plugin.decryptCaptcha("http://api.solvemedia.com/papi/media?c=%s" % challenge,imgtype="gif")
\ No newline at end of file diff --git a/module/plugins/internal/DeadCrypter.py b/pyload/plugins/internal/DeadCrypter.py index 805f781af..805f781af 100644 --- a/module/plugins/internal/DeadCrypter.py +++ b/pyload/plugins/internal/DeadCrypter.py diff --git a/module/plugins/internal/DeadHoster.py b/pyload/plugins/internal/DeadHoster.py index e180e2384..e180e2384 100644 --- a/module/plugins/internal/DeadHoster.py +++ b/pyload/plugins/internal/DeadHoster.py diff --git a/pyload/plugins/internal/NetloadInOCR.py b/pyload/plugins/internal/NetloadInOCR.py new file mode 100644 index 000000000..e50978701 --- /dev/null +++ b/pyload/plugins/internal/NetloadInOCR.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from OCR import OCR + +class NetloadInOCR(OCR): + __version__ = 0.1 + + def __init__(self): + OCR.__init__(self) + + def get_captcha(self, image): + self.load_image(image) + self.to_greyscale() + self.clean(3) + self.clean(3) + self.run_tesser(True, True, False, False) + + self.result_captcha = self.result_captcha.replace(" ", "")[:4] # cut to 4 numbers + + return self.result_captcha + +if __name__ == '__main__': + import urllib + ocr = NetloadInOCR() + urllib.urlretrieve("http://netload.in/share/includes/captcha.php", "captcha.png") + + print ocr.get_captcha('captcha.png') diff --git a/pyload/plugins/internal/OCR.py b/pyload/plugins/internal/OCR.py new file mode 100644 index 000000000..9f8b7ef8c --- /dev/null +++ b/pyload/plugins/internal/OCR.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2009 kingzero, RaNaN +# +#This program is free software; you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation; either version 3 of the License, +#or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +### +from __future__ import with_statement +import os +from os.path import join +from os.path import abspath +import logging +import subprocess +#import tempfile + +import Image +import TiffImagePlugin +import PngImagePlugin +import GifImagePlugin +import JpegImagePlugin + + +class OCR(object): + __version__ = 0.1 + + def __init__(self): + self.logger = logging.getLogger("log") + + def load_image(self, image): + self.image = Image.open(image) + self.pixels = self.image.load() + self.result_captcha = '' + + def unload(self): + """delete all tmp images""" + pass + + def threshold(self, value): + self.image = self.image.point(lambda a: a * value + 10) + + def run(self, command): + """Run a command""" + + popen = subprocess.Popen(command, bufsize = -1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + popen.wait() + output = popen.stdout.read() +" | "+ popen.stderr.read() + popen.stdout.close() + popen.stderr.close() + self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output)) + + def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True): + #self.logger.debug("create tmp tif") + + + #tmp = tempfile.NamedTemporaryFile(suffix=".tif") + tmp = open(join("tmp", "tmpTif_%s.tif" % self.__name__), "wb") + tmp.close() + #self.logger.debug("create tmp txt") + #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt") + tmpTxt = open(join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb") + tmpTxt.close() + + self.logger.debug("save tiff") + self.image.save(tmp.name, 'TIFF') + + if os.name == "nt": + tessparams = [join(pypath,"tesseract","tesseract.exe")] + else: + tessparams = ['tesseract'] + + tessparams.extend( [abspath(tmp.name), abspath(tmpTxt.name).replace(".txt", "")] ) + + if subset and (digits or lowercase or uppercase): + #self.logger.debug("create temp subset config") + #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset") + tmpSub = open(join("tmp", "tmpSub_%s.subset" % self.__name__), "wb") + tmpSub.write("tessedit_char_whitelist ") + if digits: + tmpSub.write("0123456789") + if lowercase: + tmpSub.write("abcdefghijklmnopqrstuvwxyz") + if uppercase: + tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + tmpSub.write("\n") + tessparams.append("nobatch") + tessparams.append(abspath(tmpSub.name)) + tmpSub.close() + + self.logger.debug("run tesseract") + self.run(tessparams) + self.logger.debug("read txt") + + try: + with open(tmpTxt.name, 'r') as f: + self.result_captcha = f.read().replace("\n", "") + except: + self.result_captcha = "" + + self.logger.debug(self.result_captcha) + try: + os.remove(tmp.name) + os.remove(tmpTxt.name) + if subset and (digits or lowercase or uppercase): + os.remove(tmpSub.name) + except: + pass + + def get_captcha(self, name): + raise NotImplementedError + + def to_greyscale(self): + if self.image.mode != 'L': + self.image = self.image.convert('L') + + self.pixels = self.image.load() + + def eval_black_white(self, limit): + self.pixels = self.image.load() + w, h = self.image.size + for x in xrange(w): + for y in xrange(h): + if self.pixels[x, y] > limit: + self.pixels[x, y] = 255 + else: + self.pixels[x, y] = 0 + + def clean(self, allowed): + pixels = self.pixels + + w, h = self.image.size + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 255: continue + # no point in processing white pixels since we only want to remove black pixel + count = 0 + + try: + if pixels[x-1, y-1] != 255: count += 1 + if pixels[x-1, y] != 255: count += 1 + if pixels[x-1, y + 1] != 255: count += 1 + if pixels[x, y + 1] != 255: count += 1 + if pixels[x + 1, y + 1] != 255: count += 1 + if pixels[x + 1, y] != 255: count += 1 + if pixels[x + 1, y-1] != 255: count += 1 + if pixels[x, y-1] != 255: count += 1 + except: + pass + + # not enough neighbors are dark pixels so mark this pixel + # to be changed to white + if count < allowed: + pixels[x, y] = 1 + + # second pass: this time set all 1's to 255 (white) + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 1: pixels[x, y] = 255 + + self.pixels = pixels + + def derotate_by_average(self): + """rotate by checking each angle and guess most suitable""" + + w, h = self.image.size + pixels = self.pixels + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 155 + + highest = {} + counts = {} + + for angle in range(-45, 45): + + tmpimage = self.image.rotate(angle) + + pixels = tmpimage.load() + + w, h = self.image.size + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 255 + + + count = {} + + for x in xrange(w): + count[x] = 0 + for y in xrange(h): + if pixels[x, y] == 155: + count[x] += 1 + + sum = 0 + cnt = 0 + + for x in count.values(): + if x != 0: + sum += x + cnt += 1 + + avg = sum / cnt + counts[angle] = cnt + highest[angle] = 0 + for x in count.values(): + if x > highest[angle]: + highest[angle] = x + + highest[angle] = highest[angle] - avg + + hkey = 0 + hvalue = 0 + + for key, value in highest.iteritems(): + if value > hvalue: + hkey = key + hvalue = value + + self.image = self.image.rotate(hkey) + pixels = self.image.load() + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 255 + + if pixels[x, y] == 155: + pixels[x, y] = 0 + + self.pixels = pixels + + def split_captcha_letters(self): + captcha = self.image + started = False + letters = [] + width, height = captcha.size + bottomY, topY = 0, height + pixels = captcha.load() + + for x in xrange(width): + black_pixel_in_col = False + for y in xrange(height): + if pixels[x, y] != 255: + if not started: + started = True + firstX = x + lastX = x + + if y > bottomY: bottomY = y + if y < topY: topY = y + if x > lastX: lastX = x + + black_pixel_in_col = True + + if black_pixel_in_col == False and started == True: + rect = (firstX, topY, lastX, bottomY) + new_captcha = captcha.crop(rect) + + w, h = new_captcha.size + if w > 5 and h > 5: + letters.append(new_captcha) + + started = False + bottomY, topY = 0, height + + return letters + + def correct(self, values, var=None): + + if var: + result = var + else: + result = self.result_captcha + + for key, item in values.iteritems(): + + if key.__class__ == str: + result = result.replace(key, item) + else: + for expr in key: + result = result.replace(expr, item) + + if var: + return result + else: + self.result_captcha = result + + +if __name__ == '__main__': + ocr = OCR() + ocr.load_image("B.jpg") + ocr.to_greyscale() + ocr.eval_black_white(140) + ocr.derotate_by_average() + ocr.run_tesser() + print "Tesseract", ocr.result_captcha + ocr.image.save("derotated.jpg") + diff --git a/pyload/plugins/internal/ShareonlineBizOCR.py b/pyload/plugins/internal/ShareonlineBizOCR.py new file mode 100644 index 000000000..c5c2e92e8 --- /dev/null +++ b/pyload/plugins/internal/ShareonlineBizOCR.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2009 kingzero, RaNaN +# +#This program is free software; you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation; either version 3 of the License, +#or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +### +from OCR import OCR + +class ShareonlineBizOCR(OCR): + __version__ = 0.1 + + def __init__(self): + OCR.__init__(self) + + def get_captcha(self, image): + self.load_image(image) + self.to_greyscale() + self.image = self.image.resize((160, 50)) + self.pixels = self.image.load() + self.threshold(1.85) + #self.eval_black_white(240) + #self.derotate_by_average() + + letters = self.split_captcha_letters() + + final = "" + for letter in letters: + self.image = letter + self.run_tesser(True, True, False, False) + final += self.result_captcha + + return final + + #tesseract at 60% + +if __name__ == '__main__': + import urllib + ocr = ShareonlineBizOCR() + urllib.urlretrieve("http://www.share-online.biz/captcha.php", "captcha.jpeg") + print ocr.get_captcha('captcha.jpeg') diff --git a/pyload/plugins/internal/SimpleCrypter.py b/pyload/plugins/internal/SimpleCrypter.py new file mode 100644 index 000000000..d8132f4b3 --- /dev/null +++ b/pyload/plugins/internal/SimpleCrypter.py @@ -0,0 +1,98 @@ +# -*- 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: zoidberg +""" + +import re + +from pyload.plugins.Crypter import Crypter, Package +from pyload.utils import html_unescape + + +class SimpleCrypter(Crypter): + __name__ = "SimpleCrypter" + __version__ = "0.06" + __pattern__ = None + __type__ = "crypter" + __description__ = """Base crypter plugin""" + __author_name__ = ("stickell", "zoidberg") + __author_mail__ = ("l.stickell@yahoo.it", "zoidberg@mujmail.cz") + """ + These patterns should be defined by each crypter: + + LINK_PATTERN: group(1) must be a download link + example: <div class="link"><a href="(http://speedload.org/\w+) + + TITLE_PATTERN: (optional) the group defined by 'title' should be the title + example: <title>Files of: (?P<title>[^<]+) folder</title> + + If it's impossible to extract the links using the LINK_PATTERN only you can override the getLinks method. + + If the links are disposed on multiple pages you need to define a pattern: + + PAGES_PATTERN: the group defined by 'pages' must be the total number of pages + + and a function: + + loadPage(self, page_n): + must return the html of the page number 'page_n' + """ + + def decryptURL(self, url): + self.html = self.load(url, decode=True) + + package_name = self.getPackageName() + self.package_links = self.getLinks() + + if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'): + self.handleMultiPages() + + self.logDebug('Package has %d links' % len(self.package_links)) + + if self.package_links: + return Package(package_name, self.package_links) + else: + self.fail('Could not extract any links') + + + def getLinks(self): + """ + Returns the links extracted from self.html + You should override this only if it's impossible to extract links using only the LINK_PATTERN. + """ + return re.findall(self.LINK_PATTERN, self.html) + + def getPackageName(self): + if hasattr(self, 'TITLE_PATTERN'): + m = re.search(self.TITLE_PATTERN, self.html) + if m: + name = html_unescape(m.group('title').strip()) + self.logDebug("Found name [%s] in package info" % (name)) + return name + + return None + + def handleMultiPages(self): + pages = re.search(self.PAGES_PATTERN, self.html) + if pages: + pages = int(pages.group('pages')) + else: + pages = 1 + + for p in range(2, pages + 1): + self.html = self.loadPage(p) + self.package_links += self.getLinks() diff --git a/module/plugins/internal/SimpleHoster.py b/pyload/plugins/internal/SimpleHoster.py index 856d3fde6..856d3fde6 100644 --- a/module/plugins/internal/SimpleHoster.py +++ b/pyload/plugins/internal/SimpleHoster.py diff --git a/pyload/plugins/internal/UnRar.py b/pyload/plugins/internal/UnRar.py new file mode 100644 index 000000000..e406f124e --- /dev/null +++ b/pyload/plugins/internal/UnRar.py @@ -0,0 +1,215 @@ +# -*- 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 os +import re +from glob import glob +from subprocess import Popen, PIPE +from string import digits + +from module.utils.fs import save_join, decode, fs_encode +from module.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError + +class UnRar(AbtractExtractor): + __name__ = "UnRar" + __version__ = "0.14" + + # there are some more uncovered rar formats + re_splitfile = re.compile(r"(.*)\.part(\d+)\.rar$", re.I) + re_partfiles = re.compile(r".*\.(rar|r[0-9]+)", re.I) + re_filelist = re.compile(r"(.+)\s+(\d+)\s+(\d+)\s+") + re_wrongpwd = re.compile("(Corrupt file or wrong password|password incorrect)", re.I) + CMD = "unrar" + + @staticmethod + def checkDeps(): + if os.name == "nt": + UnRar.CMD = save_join(pypath, "UnRAR.exe") + p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) + p.communicate() + else: + try: + p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) + p.communicate() + except OSError: + + #fallback to rar + UnRar.CMD = "rar" + p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) + p.communicate() + + return True + + @staticmethod + def getTargets(files_ids): + result = [] + + for file, id in files_ids: + if not file.endswith(".rar"): continue + + match = UnRar.re_splitfile.findall(file) + if match: + #only add first parts + if int(match[0][1]) == 1: + result.append((file, id)) + else: + result.append((file, id)) + + return result + + + def init(self): + self.passwordProtected = False + self.headerProtected = False #list files will not work without password + self.smallestFile = None #small file to test passwords + self.password = "" #save the correct password + + def checkArchive(self): + p = self.call_unrar("l", "-v", fs_encode(self.file)) + out, err = p.communicate() + if self.re_wrongpwd.search(err): + self.passwordProtected = True + self.headerProtected = True + return True + + # output only used to check if passworded files are present + for name, size, packed in self.re_filelist.findall(out): + if name.startswith("*"): + self.passwordProtected = True + return True + + self.listContent() + if not self.files: + raise ArchiveError("Empty Archive") + + return False + + def checkPassword(self, password): + #at this point we can only verify header protected files + if self.headerProtected: + p = self.call_unrar("l", "-v", fs_encode(self.file), password=password) + out, err = p.communicate() + if self.re_wrongpwd.search(err): + return False + + return True + + + def extract(self, progress, password=None): + command = "x" if self.fullpath else "e" + + p = self.call_unrar(command, fs_encode(self.file), self.out, password=password) + renice(p.pid, self.renice) + + progress(0) + progressstring = "" + while True: + c = p.stdout.read(1) + # quit loop on eof + if not c: + break + # reading a percentage sign -> set progress and restart + if c == '%': + progress(int(progressstring)) + progressstring = "" + # not reading a digit -> therefore restart + elif c not in digits: + progressstring = "" + # add digit to progressstring + else: + progressstring = progressstring + c + progress(100) + + # retrieve stderr + err = p.stderr.read() + + if "CRC failed" in err and not password and not self.passwordProtected: + raise CRCError + elif "CRC failed" in err: + raise WrongPassword + if err.strip(): #raise error if anything is on stderr + raise ArchiveError(err.strip()) + if p.returncode: + raise ArchiveError("Process terminated") + + if not self.files: + self.password = password + self.listContent() + + + def getDeleteFiles(self): + if ".part" in self.file: + return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.IGNORECASE)) + # get files which matches .r* and filter unsuited files out + parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.IGNORECASE)) + return filter(lambda x: self.re_partfiles.match(x), parts) + + def listContent(self): + command = "vb" if self.fullpath else "lb" + p = self.call_unrar(command, "-v", fs_encode(self.file), password=self.password) + out, err = p.communicate() + + if "Cannot open" in err: + raise ArchiveError("Cannot open file") + + if err.strip(): # only log error at this point + self.m.logError(err.strip()) + + result = set() + + for f in decode(out).splitlines(): + f = f.strip() + result.add(save_join(self.out, f)) + + self.files = result + + + def call_unrar(self, command, *xargs, **kwargs): + args = [] + #overwrite flag + args.append("-o+") if self.overwrite else args.append("-o-") + + if self.excludefiles: + for word in self.excludefiles.split(';'): + args.append("-x%s" % word ) + + # assume yes on all queries + args.append("-y") + + #set a password + if "password" in kwargs and kwargs["password"]: + args.append("-p%s" % kwargs["password"]) + else: + args.append("-p-") + + #NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue + call = [self.CMD, command] + args + list(xargs) + self.m.logDebug(" ".join([decode(arg) for arg in call])) + + p = Popen(call, stdout=PIPE, stderr=PIPE) + + return p + + +def renice(pid, value): + if os.name != "nt" and value: + try: + Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1) + except: + print "Renice failed" diff --git a/module/plugins/internal/UnZip.py b/pyload/plugins/internal/UnZip.py index 9aa9ac75c..9aa9ac75c 100644 --- a/module/plugins/internal/UnZip.py +++ b/pyload/plugins/internal/UnZip.py diff --git a/module/plugins/internal/XFSPAccount.py b/pyload/plugins/internal/XFSPAccount.py index 8333c7265..8333c7265 100644 --- a/module/plugins/internal/XFSPAccount.py +++ b/pyload/plugins/internal/XFSPAccount.py diff --git a/module/plugins/hoster/__init__.py b/pyload/plugins/internal/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/hoster/__init__.py +++ b/pyload/plugins/internal/__init__.py diff --git a/pyload/plugins/network/CurlChunk.py b/pyload/plugins/network/CurlChunk.py new file mode 100644 index 000000000..75be9ce6c --- /dev/null +++ b/pyload/plugins/network/CurlChunk.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 import remove, stat, fsync +from os.path import exists +from time import sleep +from re import search + +import codecs +import pycurl + +from pyload.utils import remove_chars +from pyload.utils.fs import fs_encode, fs_decode + +from CurlRequest import CurlRequest + +class WrongFormat(Exception): + pass + + +class ChunkInfo(): + def __init__(self, name): + self.name = fs_decode(name) + self.size = 0 + self.resume = False + self.chunks = [] + + def __repr__(self): + ret = "ChunkInfo: %s, %s\n" % (self.name, self.size) + for i, c in enumerate(self.chunks): + ret += "%s# %s\n" % (i, c[1]) + + return ret + + def setSize(self, size): + self.size = int(size) + + def addChunk(self, name, range): + self.chunks.append((name, range)) + + def clear(self): + self.chunks = [] + + def createChunks(self, chunks): + self.clear() + chunk_size = self.size / chunks + + current = 0 + for i in range(chunks): + end = self.size - 1 if (i == chunks - 1) else current + chunk_size + self.addChunk("%s.chunk%s" % (self.name, i), (current, end)) + current += chunk_size + 1 + + + def save(self): + fs_name = fs_encode("%s.chunks" % self.name) + fh = codecs.open(fs_name, "w", "utf_8") + fh.write("name:%s\n" % self.name) + fh.write("size:%s\n" % self.size) + for i, c in enumerate(self.chunks): + fh.write("#%d:\n" % i) + fh.write("\tname:%s\n" % c[0]) + fh.write("\trange:%i-%i\n" % c[1]) + fh.close() + + @staticmethod + def load(name): + fs_name = fs_encode("%s.chunks" % name) + if not exists(fs_name): + raise IOError() + fh = codecs.open(fs_name, "r", "utf_8") + name = fh.readline()[:-1] + size = fh.readline()[:-1] + if name.startswith("name:") and size.startswith("size:"): + name = name[5:] + size = size[5:] + else: + fh.close() + raise WrongFormat() + ci = ChunkInfo(name) + ci.loaded = True + ci.setSize(size) + while True: + if not fh.readline(): #skip line + break + name = fh.readline()[1:-1] + range = fh.readline()[1:-1] + if name.startswith("name:") and range.startswith("range:"): + name = name[5:] + range = range[6:].split("-") + else: + raise WrongFormat() + + ci.addChunk(name, (long(range[0]), long(range[1]))) + fh.close() + return ci + + def remove(self): + fs_name = fs_encode("%s.chunks" % self.name) + if exists(fs_name): remove(fs_name) + + def getCount(self): + return len(self.chunks) + + def getChunkName(self, index): + return self.chunks[index][0] + + def getChunkRange(self, index): + return self.chunks[index][1] + + +class CurlChunk(CurlRequest): + def __init__(self, id, parent, range=None, resume=False): + self.setContext(*parent.getContext()) + + self.id = id + self.p = parent # CurlDownload instance + self.range = range # tuple (start, end) + self.resume = resume + self.log = parent.log + + self.size = range[1] - range[0] if range else -1 + self.arrived = 0 + self.lastURL = self.p.referer + + self.c = pycurl.Curl() + + self.header = "" + self.headerParsed = False #indicates if the header has been processed + + self.fp = None #file handle + + self.initContext() + + self.BOMChecked = False # check and remove byte order mark + + self.rep = None + + self.sleep = 0.000 + self.lastSize = 0 + # next to last size + self.nLastSize = 0 + + def __repr__(self): + return "<CurlChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived) + + @property + def cj(self): + return self.p.context + + def getHandle(self): + """ returns a Curl handle ready to use for perform/multiperform """ + + self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cookies) + self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + # request all bytes, since some servers in russia seems to have a defect arihmetic unit + + fs_name = fs_encode(self.p.info.getChunkName(self.id)) + if self.resume: + self.fp = open(fs_name, "ab") + self.arrived = self.fp.tell() + if not self.arrived: + self.arrived = stat(fs_name).st_size + + if self.range: + #do nothing if chunk already finished + if self.arrived + self.range[0] >= self.range[1]: return None + + if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything + range = "%i-" % (self.arrived + self.range[0]) + else: + range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked resume with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + else: + self.log.debug("Resume File from %i" % self.arrived) + self.c.setopt(pycurl.RESUME_FROM, self.arrived) + + else: + if self.range: + if self.id == len(self.p.info.chunks) - 1: # see above + range = "%i-" % self.range[0] + else: + range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + + self.fp = open(fs_name, "wb") + + return self.c + + def writeHeader(self, buf): + self.header += buf + #@TODO forward headers?, this is possibly 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() + elif not self.range and buf.startswith("150") and "data connection" in buf: #ftp file size parsing + size = search(r"(\d+) bytes", buf) + if size: + self.p._size = int(size.group(1)) + self.p.chunkSupport = True + + self.headerParsed = True + + def writeBody(self, buf): + #ignore BOM, it confuses unrar + if not self.BOMChecked: + if [ord(b) for b in buf[:3]] == [239, 187, 191]: + buf = buf[3:] + self.BOMChecked = True + + size = len(buf) + self.nLastSize = self.lastSize + self.lastSize = size + + self.arrived += size + + self.fp.write(buf) + + if self.p.bucket: + sleep(self.p.bucket.consumed(size)) + + # if the buffer sizes are stable no sleep will be made + elif size != self.lastSize or size != self.nLastSize: + # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller + # 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 + else: + self.sleep *= 0.7 + + sleep(self.sleep) + + if self.range and self.arrived > self.size: + return 0 #close if we have enough data + + + def parseHeader(self): + """parse data from 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 "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]) + + self.headerParsed = True + + def stop(self): + """The download will not proceed after next call of writeBody""" + self.range = [0,0] + self.size = 0 + + def resetRange(self): + """ Reset the range, so the download will load all data available """ + self.range = None + + def setRange(self, range): + self.range = range + self.size = range[1] - range[0] + + def flushFile(self): + """ flush and close file """ + self.fp.flush() + fsync(self.fp.fileno()) #make sure everything was written to disk + self.fp.close() #needs to be closed, or merging chunks will fail + + def close(self): + """ closes everything, unusable after this """ + if self.fp: self.fp.close() + self.c.close() + if hasattr(self, "p"): del self.p diff --git a/pyload/plugins/network/CurlDownload.py b/pyload/plugins/network/CurlDownload.py new file mode 100644 index 000000000..985513691 --- /dev/null +++ b/pyload/plugins/network/CurlDownload.py @@ -0,0 +1,325 @@ +#!/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 os import remove +from os.path import dirname +from time import time +from shutil import move + +import pycurl + +from pyload.plugins.Base import Abort +from pyload.network.CookieJar import CookieJar +from pyload.utils.fs import save_join, fs_encode + +from ..Download import Download +from CurlChunk import ChunkInfo, CurlChunk +from CurlRequest import ResponseException + +# TODO: save content-disposition for resuming + +class CurlDownload(Download): + """ loads an url, http + ftp supported """ + + # def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None, + # options={}, disposition=False): + + CONTEXT_CLASS = CookieJar + + def __init__(self, *args, **kwargs): + Download.__init__(self, *args, **kwargs) + + self.path = None + self.disposition = False + + self.chunks = [] + self.chunkSupport = None + + self.m = pycurl.CurlMulti() + + #needed for speed calculation + self.lastArrived = [] + self.speeds = [] + self.lastSpeeds = [0, 0] + + @property + def speed(self): + last = [sum(x) for x in self.lastSpeeds if x] + return (sum(self.speeds) + sum(last)) / (1 + len(last)) + + @property + def arrived(self): + return sum(c.arrived for c in self.chunks) if self.chunks else self._size + + @property + def name(self): + return self._name if self.disposition else None + + def _copyChunks(self): + init = fs_encode(self.info.getChunkName(0)) #initial chunk name + + if self.info.getCount() > 1: + fo = open(init, "rb+") #first chunkfile + for i in range(1, self.info.getCount()): + #input file + fo.seek( + self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks + fname = fs_encode("%s.chunk%d" % (self.path, i)) + fi = open(fname, "rb") + buf = 32 * 1024 + while True: #copy in chunks, consumes less memory + data = fi.read(buf) + if not data: + break + fo.write(data) + fi.close() + if fo.tell() < self.info.getChunkRange(i)[1]: + fo.close() + remove(init) + self.info.remove() #there are probably invalid chunks + raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.") + remove(fname) #remove chunk + fo.close() + + if self.name: + self.filename = save_join(dirname(self.path), self.name) + + move(init, fs_encode(self.path)) + self.info.remove() #remove info file + + def checkResume(self): + try: + self.info = ChunkInfo.load(self.path) + self.info.resume = True #resume is only possible with valid info file + self._size = self.info.size + self.infoSaved = True + except IOError: + self.info = ChunkInfo(self.path) + + def download(self, uri, path, get={}, post={}, referer=True, disposition=False, chunks=1, resume=False, cookies=True): + """ returns new filename or None """ + self.url = uri + self.path = path + self.disposition = disposition + self.get = get + self.post = post + self.referer = referer + self.cookies = cookies + + self.checkResume() + chunks = max(1, chunks) + resume = self.info.resume and resume + + try: + self._download(chunks, resume) + except pycurl.error, e: + #code 33 - no resume + code = e.args[0] + if code == 33: + # try again without resume + self.log.debug("Errno 33 -> Restart without resume") + + #remove old handles + for chunk in self.chunks: + self.closeChunk(chunk) + + return self._download(chunks, False) + else: + raise + finally: + self.close() + + return self.name + + def _download(self, chunks, resume): + if not resume: + self.info.clear() + self.info.addChunk("%s.chunk0" % self.path, (0, 0)) #create an initial entry + + self.chunks = [] + + init = CurlChunk(0, self, None, resume) #initial chunk that will load complete file (if needed) + + self.chunks.append(init) + self.m.add_handle(init.getHandle()) + + lastFinishCheck = 0 + lastTimeCheck = 0 + chunksDone = set() # list of curl handles that are finished + chunksCreated = False + done = False + if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can + self.chunkSupport = True + + while 1: + #need to create chunks + if not chunksCreated and self.chunkSupport and self.size: #will be set later by first chunk + + if not resume: + self.info.setSize(self.size) + self.info.createChunks(chunks) + self.info.save() + + chunks = self.info.getCount() + + init.setRange(self.info.getChunkRange(0)) + + for i in range(1, chunks): + c = CurlChunk(i, self, self.info.getChunkRange(i), resume) + + handle = c.getHandle() + if handle: + self.chunks.append(c) + self.m.add_handle(handle) + else: + #close immediately + self.log.debug("Invalid curl handle -> closed") + c.close() + + chunksCreated = True + + while 1: + ret, num_handles = self.m.perform() + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + + t = time() + + # reduce these calls + # when num_q is 0, the loop is exited + while lastFinishCheck + 0.5 < t: + # list of failed curl handles + failed = [] + ex = None # save only last exception, we can only raise one anyway + + num_q, ok_list, err_list = self.m.info_read() + for c in ok_list: + chunk = self.findChunk(c) + try: # check if the header implies success, else add it to failed list + chunk.verifyHeader() + except ResponseException, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(c) + + for c in err_list: + curl, errno, msg = c + chunk = self.findChunk(curl) + #test if chunk was finished + if errno != 23 or "0 !=" not in msg: + failed.append(chunk) + ex = pycurl.error(errno, msg) + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex))) + continue + + try: # check if the header implies success, else add it to failed list + chunk.verifyHeader() + except ResponseException, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(curl) + if not num_q: # no more info to get + + # check if init is not finished so we reset download connections + # note that other chunks are closed and everything downloaded with initial connection + if failed and init not in failed and init.c not in chunksDone: + self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex)))) + + #list of chunks to clean and remove + to_clean = filter(lambda x: x is not init, self.chunks) + for chunk in to_clean: + self.closeChunk(chunk) + self.chunks.remove(chunk) + remove(fs_encode(self.info.getChunkName(chunk.id))) + + #let first chunk load the rest and update the info file + init.resetRange() + self.info.clear() + self.info.addChunk("%s.chunk0" % self.filename, (0, self.size)) + self.info.save() + elif failed: + raise ex + + lastFinishCheck = t + + if len(chunksDone) >= len(self.chunks): + if len(chunksDone) > len(self.chunks): + self.log.warning("Finished download chunks size incorrect, please report bug.") + done = True #all chunks loaded + + break + + if done: + break #all chunks loaded + + # calc speed once per second, averaging over 3 seconds + if lastTimeCheck + 1 < t: + diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in + enumerate(self.chunks)] + + self.lastSpeeds[1] = self.lastSpeeds[0] + self.lastSpeeds[0] = self.speeds + self.speeds = [float(a) / (t - lastTimeCheck) for a in diff] + self.lastArrived = [c.arrived for c in self.chunks] + lastTimeCheck = t + + if self.doAbort: + raise Abort() + + self.m.select(1) + + for chunk in self.chunks: + chunk.flushFile() #make sure downloads are written to disk + + self._copyChunks() + + def findChunk(self, handle): + """ linear search to find a chunk (should be ok since chunk size is usually low) """ + for chunk in self.chunks: + if chunk.c == handle: return chunk + + def closeChunk(self, chunk): + try: + self.m.remove_handle(chunk.c) + except pycurl.error, e: + self.log.debug("Error removing chunk: %s" % str(e)) + finally: + chunk.close() + + def close(self): + """ cleanup """ + for chunk in self.chunks: + self.closeChunk(chunk) + else: + #Workaround: pycurl segfaults when closing multi, that never had any curl handles + if hasattr(self, "m"): + c = pycurl.Curl() + self.m.add_handle(c) + self.m.remove_handle(c) + c.close() + + self.chunks = [] + if hasattr(self, "m"): + self.m.close() + del self.m + if hasattr(self, "info"): + del self.info
\ No newline at end of file diff --git a/pyload/plugins/network/CurlRequest.py b/pyload/plugins/network/CurlRequest.py new file mode 100644 index 000000000..717590ac5 --- /dev/null +++ b/pyload/plugins/network/CurlRequest.py @@ -0,0 +1,320 @@ +# -*- 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 +############################################################################### + +import pycurl + +from codecs import getincrementaldecoder, lookup, BOM_UTF8 +from urllib import quote, urlencode +from httplib import responses +from cStringIO import StringIO + +from pyload.plugins.Base import Abort +from pyload.network.CookieJar import CookieJar + +from ..Request import Request, ResponseException + + +def myquote(url): + return quote(url.encode('utf8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]") + + +def myurlencode(data): + data = dict(data) + 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, 418) + range(500, 506) + +pycurl.global_init(pycurl.GLOBAL_DEFAULT) + +class CurlRequest(Request): + """ Request class based on libcurl """ + + __version__ = "0.1" + + CONTEXT_CLASS = CookieJar + + def __init__(self, *args, **kwargs): + self.c = pycurl.Curl() + Request.__init__(self, *args, **kwargs) + + self.rep = StringIO() + self.lastURL = None + self.lastEffectiveURL = None + + # cookiejar defines the context + self.cj = self.context + + self.c.setopt(pycurl.WRITEFUNCTION, self.write) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + # TODO: addAuth, addHeader + + @property + def http(self): + print "Deprecated usage of req.http, just use req instead" + return self + + def initContext(self): + self.initHandle() + + if self.config: + self.setInterface(self.config) + self.initOptions(self.config) + + def initHandle(self): + """ sets common options to curl handle """ + + self.c.setopt(pycurl.FOLLOWLOCATION, 1) + self.c.setopt(pycurl.MAXREDIRS, 5) + self.c.setopt(pycurl.CONNECTTIMEOUT, 30) + self.c.setopt(pycurl.NOSIGNAL, 1) + self.c.setopt(pycurl.NOPROGRESS, 1) + if hasattr(pycurl, "AUTOREFERER"): + self.c.setopt(pycurl.AUTOREFERER, 1) + self.c.setopt(pycurl.SSL_VERIFYPEER, 0) + # 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) + + # don't save the cookies + self.c.setopt(pycurl.COOKIEFILE, "") + self.c.setopt(pycurl.COOKIEJAR, "") + + #self.c.setopt(pycurl.VERBOSE, 1) + + self.c.setopt(pycurl.USERAGENT, + "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0") + if pycurl.version_info()[7]: + self.c.setopt(pycurl.ENCODING, "gzip, deflate") + self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*", + "Accept-Language: en-US,en", + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Connection: keep-alive", + "Keep-Alive: 300", + "Expect:"]) + + def setInterface(self, options): + + interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"] + + if interface and interface.lower() != "none": + self.c.setopt(pycurl.INTERFACE, str(interface)) + + if proxy: + if proxy["type"] == "socks4": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4) + elif proxy["type"] == "socks5": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5) + else: + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP) + + self.c.setopt(pycurl.PROXY, str(proxy["address"])) + self.c.setopt(pycurl.PROXYPORT, proxy["port"]) + + if proxy["username"]: + self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"]))) + + if ipv6: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) + else: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) + + if "timeout" in options: + self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"]) + + def initOptions(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 setRequestContext(self, url, get, post, referer, cookies, multipart=False): + """ sets everything needed for the request """ + url = myquote(url) + + if get: + get = urlencode(get) + url = "%s?%s" % (url, get) + + self.c.setopt(pycurl.URL, url) + + if post: + self.c.setopt(pycurl.POST, 1) + if not multipart: + if type(post) == unicode: + post = str(post) #unicode not allowed + elif type(post) == str: + pass + else: + post = myurlencode(post) + + self.c.setopt(pycurl.POSTFIELDS, post) + else: + post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()] + self.c.setopt(pycurl.HTTPPOST, post) + else: + self.c.setopt(pycurl.POST, 0) + + if referer and self.lastURL: + self.c.setopt(pycurl.REFERER, str(self.lastURL)) + else: + self.c.setopt(pycurl.HTTPHEADER, ["Referer:"]) + + if cookies: + for c in self.cj.output().splitlines(): + self.c.setopt(pycurl.COOKIELIST, c) + else: + # Magic string that erases all cookies + self.c.setopt(pycurl.COOKIELIST, "ALL") + + # TODO: remove auth again + if "auth" in self.options: + self.c.setopt(pycurl.USERPWD, str(self.options["auth"])) + + def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False): + """ load and returns a given page """ + + self.setRequestContext(url, get, post, referer, cookies, multipart) + + # TODO: use http/rfc message instead + self.header = "" + + if "header" in self.options: + self.c.setopt(pycurl.HTTPHEADER, self.options["header"]) + + if just_header: + self.c.setopt(pycurl.FOLLOWLOCATION, 0) + self.c.setopt(pycurl.NOBODY, 1) #TODO: nobody= no post? + + # 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() + rep = self.getResponse() + + self.c.setopt(pycurl.POSTFIELDS, "") + self.lastURL = myquote(url) + self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL) + self.code = self.verifyHeader() + + if cookies: + self.parseCookies() + + if decode: + rep = self.decodeResponse(rep) + + return rep + + def parseCookies(self): + for c in self.c.getinfo(pycurl.INFO_COOKIELIST): + #http://xiix.wordpress.com/2006/03/23/mozillafirefox-cookie-format + domain, flag, path, secure, expires, name, value = c.split("\t") + # http only was added in py 2.6 + domain = domain.replace("#HttpOnly_", "") + self.cj.setCookie(domain, name, value, path, expires, secure) + + def verifyHeader(self): + """ raise an exceptions on bad headers """ + code = int(self.c.getinfo(pycurl.RESPONSE_CODE)) + if code in bad_headers: + raise ResponseException(code, responses.get(code, "Unknown statuscode")) + return code + + def getResponse(self): + """ retrieve response from string io """ + if self.rep is None: return "" + value = self.rep.getvalue() + self.rep.close() + self.rep = StringIO() + return value + + def decodeResponse(self, rep): + """ decode with correct encoding, relies on header """ + header = self.header.splitlines() + encoding = "utf8" # default encoding + + for line in header: + line = line.lower().replace(" ", "") + if not line.startswith("content-type:") or \ + ("text" not in line and "application" not in line): + continue + + none, delemiter, charset = line.rpartition("charset=") + if delemiter: + charset = charset.split(";") + if charset: + encoding = charset[0] + + try: + #self.log.debug("Decoded %s" % encoding ) + if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8): + encoding = 'utf-8-sig' + + decoder = getincrementaldecoder(encoding)("replace") + rep = decoder.decode(rep, True) + + #TODO: html_unescape as default + + except LookupError: + self.log.debug("No Decoder found for %s" % encoding) + except Exception: + self.log.debug("Error when decoding string from %s." % encoding) + + return rep + + def write(self, buf): + """ writes response """ + if self.rep.tell() > 1000000 or self.doAbort: + rep = self.getResponse() + if self.doAbort: raise Abort() + f = open("response.dump", "wb") + f.write(rep) + f.close() + raise Exception("Loaded Url exceeded limit") + + self.rep.write(buf) + + def writeHeader(self, buf): + """ writes header """ + self.header += buf + + def reset(self): + self.cj.clear() + self.options.clear() + + def close(self): + """ cleanup, unusable after this """ + self.rep.close() + if hasattr(self, "cj"): + del self.cj + if hasattr(self, "c"): + self.c.close() + del self.c
\ No newline at end of file diff --git a/pyload/plugins/network/DefaultRequest.py b/pyload/plugins/network/DefaultRequest.py new file mode 100644 index 000000000..dce486ea5 --- /dev/null +++ b/pyload/plugins/network/DefaultRequest.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from CurlRequest import CurlRequest +from CurlDownload import CurlDownload + +__version__ = "0.1" + +DefaultRequest = CurlRequest +DefaultDownload = CurlDownload
\ No newline at end of file diff --git a/pyload/plugins/network/XDCCRequest.py b/pyload/plugins/network/XDCCRequest.py new file mode 100644 index 000000000..6b692ab38 --- /dev/null +++ b/pyload/plugins/network/XDCCRequest.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: jeix +""" + +import socket +import re + +from os import remove +from os.path import exists + +from time import time + +import struct +from select import select + +from pyload.plugins.Plugin import Abort + +# TODO: This must be adapted to the new request interfaces +class XDCCRequest(): + def __init__(self, timeout=30, proxies={}): + + self.proxies = proxies + self.timeout = timeout + + self.filesize = 0 + self.recv = 0 + self.speed = 0 + + self.abort = False + + + def createSocket(self): + # proxytype = None + # proxy = None + # if self.proxies.has_key("socks5"): + # proxytype = socks.PROXY_TYPE_SOCKS5 + # proxy = self.proxies["socks5"] + # elif self.proxies.has_key("socks4"): + # proxytype = socks.PROXY_TYPE_SOCKS4 + # proxy = self.proxies["socks4"] + # if proxytype: + # sock = socks.socksocket() + # t = _parse_proxy(proxy) + # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2]) + # else: + # sock = socket.socket() + # return sock + + return socket.socket() + + def download(self, ip, port, filename, irc, progressNotify=None): + + ircbuffer = "" + lastUpdate = time() + cumRecvLen = 0 + + dccsock = self.createSocket() + + dccsock.settimeout(self.timeout) + dccsock.connect((ip, port)) + + if exists(filename): + i = 0 + nameParts = filename.rpartition(".") + while True: + newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2]) + i += 1 + + if not exists(newfilename): + filename = newfilename + break + + fh = open(filename, "wb") + + # recv loop for dcc socket + while True: + if self.abort: + dccsock.close() + fh.close() + remove(filename) + raise Abort() + + self._keepAlive(irc, ircbuffer) + + data = dccsock.recv(4096) + dataLen = len(data) + self.recv += dataLen + + cumRecvLen += dataLen + + now = time() + timespan = now - lastUpdate + if timespan > 1: + self.speed = cumRecvLen / timespan + cumRecvLen = 0 + lastUpdate = now + + if progressNotify: + progressNotify(self.percent) + + + if not data: + break + + fh.write(data) + + # acknowledge data by sending number of received bytes + dccsock.send(struct.pack('!I', self.recv)) + + dccsock.close() + fh.close() + + return filename + + def _keepAlive(self, sock, readbuffer): + fdset = select([sock], [], [], 0) + if sock not in fdset[0]: + return + + readbuffer += sock.recv(1024) + temp = readbuffer.split("\n") + readbuffer = temp.pop() + + for line in temp: + line = line.rstrip() + first = line.split() + if first[0] == "PING": + sock.send("PONG %s\r\n" % first[1]) + + def abortDownloads(self): + self.abort = True + + @property + def size(self): + return self.filesize + + @property + def arrived(self): + return self.recv + + @property + def percent(self): + if not self.filesize: return 0 + return (self.recv * 100) / self.filesize + + def close(self): + pass diff --git a/pyload/plugins/network/__init__.py b/pyload/plugins/network/__init__.py new file mode 100644 index 000000000..4b31e848b --- /dev/null +++ b/pyload/plugins/network/__init__.py @@ -0,0 +1 @@ +__author__ = 'christian' diff --git a/module/remote/ClickAndLoadBackend.py b/pyload/remote/ClickAndLoadBackend.py index ad8031587..ad8031587 100644 --- a/module/remote/ClickAndLoadBackend.py +++ b/pyload/remote/ClickAndLoadBackend.py diff --git a/pyload/remote/JSONClient.py b/pyload/remote/JSONClient.py new file mode 100644 index 000000000..a2c07a132 --- /dev/null +++ b/pyload/remote/JSONClient.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from urllib import urlopen, urlencode +from httplib import UNAUTHORIZED, FORBIDDEN + +from json_converter import loads, dumps +from apitypes import Unauthorized, Forbidden + +class JSONClient: + URL = "http://localhost:8001/api" + + def __init__(self, url=None): + self.url = url or self.URL + self.session = None + + def request(self, path, data): + ret = urlopen(self.url + path, urlencode(data)) + if ret.code == 400: + raise loads(ret.read()) + if ret.code == 404: + raise AttributeError("Unknown Method") + if ret.code == 500: + raise Exception("Remote Exception") + if ret.code == UNAUTHORIZED: + raise Unauthorized() + if ret.code == FORBIDDEN: + raise Forbidden() + return ret.read() + + def login(self, username, password): + self.session = loads(self.request("/login", {'username': username, 'password': password})) + return self.session + + def logout(self): + self.call("logout") + self.session = None + + def call(self, func, *args, **kwargs): + # Add the current session + kwargs["session"] = self.session + path = "/" + func + "/" + "/".join(dumps(x) for x in args) + data = dict((k, dumps(v)) for k, v in kwargs.iteritems()) + rep = self.request(path, data) + return loads(rep) + + def __getattr__(self, item): + def call(*args, **kwargs): + return self.call(item, *args, **kwargs) + + return call + +if __name__ == "__main__": + api = JSONClient() + api.login("User", "test") + print api.getServerVersion()
\ No newline at end of file diff --git a/pyload/remote/RemoteManager.py b/pyload/remote/RemoteManager.py new file mode 100644 index 000000000..7aeeb8a7a --- /dev/null +++ b/pyload/remote/RemoteManager.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from threading import Thread +from traceback import print_exc + +class BackendBase(Thread): + def __init__(self, manager): + Thread.__init__(self) + self.m = manager + self.core = manager.core + self.enabled = True + self.running = False + + def run(self): + self.running = True + try: + self.serve() + except Exception, e: + self.core.log.error(_("Remote backend error: %s") % e) + if self.core.debug: + print_exc() + finally: + self.running = False + + def setup(self, host, port): + pass + + def checkDeps(self): + return True + + def serve(self): + pass + + def shutdown(self): + pass + + def stop(self): + self.enabled = False# set flag and call shutdowm message, so thread can react + self.shutdown() + + +class RemoteManager(): + available = [] + + def __init__(self, core): + self.core = core + self.backends = [] + + if self.core.remote: + self.available.append("WebSocketBackend") + + + def startBackends(self): + host = self.core.config["remote"]["listenaddr"] + port = self.core.config["remote"]["port"] + + for b in self.available: + klass = getattr(__import__("pyload.remote.%s" % b, globals(), locals(), [b], -1), b) + backend = klass(self) + if not backend.checkDeps(): + continue + try: + backend.setup(host, port) + self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port}) + except Exception, e: + self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)}) + if self.core.debug: + print_exc() + else: + backend.start() + self.backends.append(backend) + + port += 1 diff --git a/pyload/remote/WSClient.py b/pyload/remote/WSClient.py new file mode 100644 index 000000000..0e58c6afa --- /dev/null +++ b/pyload/remote/WSClient.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from websocket import create_connection +from httplib import UNAUTHORIZED, FORBIDDEN + +from json_converter import loads, dumps +from apitypes import Unauthorized, Forbidden + +class WSClient: + URL = "ws://localhost:7227/api" + + def __init__(self, url=None): + self.url = url or self.URL + self.ws = None + + def connect(self): + self.ws = create_connection(self.url) + + def close(self): + self.ws.close() + + def login(self, username, password): + if not self.ws: self.connect() + return self.call("login", username, password) + + def call(self, func, *args, **kwargs): + if not self.ws: + raise Exception("Not Connected") + + if kwargs: + self.ws.send(dumps([func, args, kwargs])) + else: # omit kwargs + self.ws.send(dumps([func, args])) + + code, result = loads(self.ws.recv()) + if code == 400: + raise result + if code == 404: + raise AttributeError("Unknown Method") + elif code == 500: + raise Exception("Remote Exception: %s" % result) + elif code == UNAUTHORIZED: + raise Unauthorized() + elif code == FORBIDDEN: + raise Forbidden() + + return result + + def __getattr__(self, item): + def call(*args, **kwargs): + return self.call(item, *args, **kwargs) + + return call + +if __name__ == "__main__": + api = WSClient() + api.login("User", "test") + print api.getServerVersion()
\ No newline at end of file diff --git a/pyload/remote/WebSocketBackend.py b/pyload/remote/WebSocketBackend.py new file mode 100644 index 000000000..d29470067 --- /dev/null +++ b/pyload/remote/WebSocketBackend.py @@ -0,0 +1,49 @@ +#!/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 +############################################################################### + +import logging + +from RemoteManager import BackendBase + +from mod_pywebsocket import util +def get_class_logger(o=None): + return logging.getLogger('log') + +# Monkey patch for our logger +util.get_class_logger = get_class_logger + +class WebSocketBackend(BackendBase): + def setup(self, host, port): + + from wsbackend.Server import WebSocketServer, DefaultOptions + from wsbackend.Dispatcher import Dispatcher + from wsbackend.ApiHandler import ApiHandler + from wsbackend.AsyncHandler import AsyncHandler + + options = DefaultOptions() + options.server_host = host + options.port = port + options.dispatcher = Dispatcher() + options.dispatcher.addHandler(ApiHandler.PATH, ApiHandler(self.core.api)) + options.dispatcher.addHandler(AsyncHandler.PATH, AsyncHandler(self.core.api)) + + self.server = WebSocketServer(options) + + + def serve(self): + self.server.serve_forever() diff --git a/module/plugins/internal/__init__.py b/pyload/remote/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/plugins/internal/__init__.py +++ b/pyload/remote/__init__.py diff --git a/pyload/remote/apitypes.py b/pyload/remote/apitypes.py new file mode 100644 index 000000000..287a5f096 --- /dev/null +++ b/pyload/remote/apitypes.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + +class BaseObject(object): + __slots__ = [] + + def __str__(self): + return "<%s %s>" % (self.__class__.__name__, ", ".join("%s=%s" % (k,getattr(self,k)) for k in self.__slots__)) + +class ExceptionObject(Exception): + __slots__ = [] + +class DownloadState: + All = 0 + Finished = 1 + Unfinished = 2 + Failed = 3 + Unmanaged = 4 + +class DownloadStatus: + NA = 0 + Offline = 1 + Online = 2 + Queued = 3 + Paused = 4 + Finished = 5 + Skipped = 6 + Failed = 7 + Starting = 8 + Waiting = 9 + Downloading = 10 + TempOffline = 11 + Aborted = 12 + NotPossible = 13 + Decrypting = 14 + Processing = 15 + Custom = 16 + Unknown = 17 + +class FileStatus: + Ok = 0 + Missing = 1 + Remote = 2 + +class InputType: + NA = 0 + Text = 1 + Int = 2 + File = 3 + Folder = 4 + Textbox = 5 + Password = 6 + Time = 7 + Bool = 8 + Click = 9 + Select = 10 + Multiple = 11 + List = 12 + PluginList = 13 + Table = 14 + +class Interaction: + All = 0 + Notification = 1 + Captcha = 2 + Query = 4 + +class MediaType: + All = 0 + Other = 1 + Audio = 2 + Image = 4 + Video = 8 + Document = 16 + Archive = 32 + Executable = 64 + +class PackageStatus: + Ok = 0 + Paused = 1 + Folder = 2 + Remote = 3 + +class Permission: + All = 0 + Add = 1 + Delete = 2 + Modify = 4 + Download = 8 + Accounts = 16 + Interaction = 32 + Plugins = 64 + +class Role: + Admin = 0 + User = 1 + +class AccountInfo(BaseObject): + __slots__ = ['plugin', 'loginname', 'owner', 'valid', 'validuntil', 'trafficleft', 'maxtraffic', 'premium', 'activated', 'shared', 'config'] + + def __init__(self, plugin=None, loginname=None, owner=None, valid=None, validuntil=None, trafficleft=None, maxtraffic=None, premium=None, activated=None, shared=None, config=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.activated = activated + self.shared = shared + self.config = config + +class AddonInfo(BaseObject): + __slots__ = ['func_name', 'description', 'value'] + + def __init__(self, func_name=None, description=None, value=None): + self.func_name = func_name + self.description = description + self.value = value + +class AddonService(BaseObject): + __slots__ = ['func_name', 'description', 'arguments', 'media'] + + 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 ConfigHolder(BaseObject): + __slots__ = ['name', 'label', 'description', 'explanation', 'items', 'info'] + + def __init__(self, name=None, label=None, description=None, explanation=None, items=None, info=None): + self.name = name + self.label = label + self.description = description + self.explanation = explanation + self.items = items + self.info = info + +class ConfigInfo(BaseObject): + __slots__ = ['name', 'label', 'description', 'category', 'user_context', 'activated'] + + def __init__(self, name=None, label=None, description=None, category=None, user_context=None, activated=None): + self.name = name + self.label = label + self.description = description + self.category = category + self.user_context = user_context + self.activated = activated + +class ConfigItem(BaseObject): + __slots__ = ['name', 'label', 'description', 'input', 'value'] + + def __init__(self, name=None, label=None, description=None, input=None, value=None): + self.name = name + self.label = label + self.description = description + self.input = input + self.value = value + +class Conflict(ExceptionObject): + pass + +class DownloadInfo(BaseObject): + __slots__ = ['url', 'plugin', 'hash', 'status', 'statusmsg', 'error'] + + 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 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(ExceptionObject): + __slots__ = ['fid'] + + 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 Forbidden(ExceptionObject): + pass + +class Input(BaseObject): + __slots__ = ['type', 'default_value', 'data'] + + def __init__(self, type=None, default_value=None, data=None): + self.type = type + self.default_value = default_value + self.data = data + +class InteractionTask(BaseObject): + __slots__ = ['iid', 'type', 'input', 'title', 'description', 'plugin'] + + def __init__(self, iid=None, type=None, input=None, title=None, description=None, plugin=None): + self.iid = iid + self.type = type + self.input = input + self.title = title + self.description = description + self.plugin = plugin + +class InvalidConfigSection(ExceptionObject): + __slots__ = ['section'] + + def __init__(self, section=None): + self.section = section + +class LinkStatus(BaseObject): + __slots__ = ['url', 'name', 'size', 'status', 'plugin', 'hash'] + + def __init__(self, url=None, name=None, size=None, status=None, plugin=None, hash=None): + self.url = url + self.name = name + self.size = size + self.status = status + self.plugin = plugin + self.hash = hash + +class OnlineCheck(BaseObject): + __slots__ = ['rid', 'data'] + + def __init__(self, rid=None, data=None): + self.rid = rid + self.data = data + +class PackageDoesNotExists(ExceptionObject): + __slots__ = ['pid'] + + def __init__(self, pid=None): + self.pid = pid + +class PackageInfo(BaseObject): + __slots__ = ['pid', 'name', 'folder', 'root', 'owner', 'site', 'comment', 'password', 'added', 'tags', 'status', 'shared', 'packageorder', 'stats', 'fids', 'pids'] + + def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, tags=None, status=None, shared=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.added = added + self.tags = tags + self.status = status + self.shared = shared + 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.sizetotal = sizetotal + self.sizedone = sizedone + +class ProgressInfo(BaseObject): + __slots__ = ['plugin', 'name', 'statusmsg', 'eta', 'done', 'total', 'download'] + + def __init__(self, plugin=None, name=None, statusmsg=None, eta=None, done=None, total=None, download=None): + self.plugin = plugin + self.name = name + self.statusmsg = statusmsg + self.eta = eta + self.done = done + self.total = total + self.download = download + +class ServerStatus(BaseObject): + __slots__ = ['speed', 'linkstotal', 'linksqueue', 'sizetotal', 'sizequeue', 'notifications', 'paused', 'download', 'reconnect'] + + def __init__(self, speed=None, linkstotal=None, linksqueue=None, sizetotal=None, sizequeue=None, notifications=None, paused=None, download=None, reconnect=None): + self.speed = speed + self.linkstotal = linkstotal + self.linksqueue = linksqueue + self.sizetotal = sizetotal + self.sizequeue = sizequeue + self.notifications = notifications + self.paused = paused + self.download = download + self.reconnect = reconnect + +class ServiceDoesNotExists(ExceptionObject): + __slots__ = ['plugin', 'func'] + + def __init__(self, plugin=None, func=None): + self.plugin = plugin + self.func = func + +class ServiceException(ExceptionObject): + __slots__ = ['msg'] + + 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 Unauthorized(ExceptionObject): + pass + +class UserData(BaseObject): + __slots__ = ['uid', 'name', 'email', 'role', 'permission', 'folder', 'traffic', 'dllimit', 'dlquota', 'hddquota', 'user', 'templateName'] + + 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 UserDoesNotExists(ExceptionObject): + __slots__ = ['user'] + + def __init__(self, user=None): + self.user = user + +class Iface(object): + def addLinks(self, pid, links): + pass + def addLocalFile(self, pid, name, path): + pass + def addPackage(self, name, links, password): + pass + def addPackageChild(self, name, links, password, root, paused): + pass + def addPackageP(self, name, links, password, paused): + pass + def addUser(self, username, password): + pass + def callAddon(self, plugin, func, arguments): + pass + def callAddonHandler(self, plugin, func, pid_or_fid): + pass + def checkContainer(self, filename, data): + pass + def checkHTML(self, html, url): + pass + def checkLinks(self, links): + pass + def createPackage(self, name, folder, root, password, site, comment, paused): + pass + def deleteConfig(self, plugin): + pass + def deleteFiles(self, fids): + pass + def deletePackages(self, pids): + pass + def findFiles(self, pattern): + pass + def findPackages(self, tags): + pass + def freeSpace(self): + pass + def generateDownloadLink(self, fid, timeout): + pass + def generatePackages(self, links): + pass + def getAccountInfo(self, plugin, loginname, refresh): + pass + def getAccountTypes(self): + pass + def getAccounts(self): + pass + def getAddonHandler(self): + pass + def getAllFiles(self): + pass + def getAllUserData(self): + pass + def getAvailablePlugins(self): + pass + def getConfig(self): + pass + def getConfigValue(self, section, option): + pass + def getCoreConfig(self): + pass + def getFileInfo(self, fid): + pass + def getFileTree(self, pid, full): + pass + def getFilteredFileTree(self, pid, full, state): + pass + def getFilteredFiles(self, state): + pass + def getInteractionTasks(self, mode): + pass + def getLog(self, offset): + pass + def getPackageContent(self, pid): + pass + def getPackageInfo(self, pid): + pass + def getPluginConfig(self): + pass + def getProgressInfo(self): + pass + def getServerStatus(self): + pass + def getServerVersion(self): + pass + def getUserData(self): + pass + def getWSAddress(self): + pass + def hasAddonHandler(self, plugin, func): + pass + def isInteractionWaiting(self, mode): + pass + def loadConfig(self, name): + pass + def login(self, username, password): + pass + def moveFiles(self, fids, pid): + pass + def movePackage(self, pid, root): + pass + def orderFiles(self, fids, pid, position): + pass + def orderPackage(self, pids, position): + pass + def parseLinks(self, links): + pass + def pauseServer(self): + pass + def pollResults(self, rid): + pass + def quit(self): + pass + def recheckPackage(self, pid): + pass + def removeAccount(self, account): + pass + def removeUser(self, uid): + pass + def restart(self): + pass + def restartFailed(self): + pass + def restartFile(self, fid): + pass + def restartPackage(self, pid): + pass + def saveConfig(self, config): + pass + def searchSuggestions(self, pattern): + pass + def setConfigValue(self, section, option, value): + pass + def setInteractionResult(self, iid, result): + pass + def setPackageFolder(self, pid, path): + pass + def setPassword(self, username, old_password, new_password): + pass + def stopAllDownloads(self): + pass + def stopDownloads(self, fids): + pass + def togglePause(self): + pass + def toggleReconnect(self): + pass + def unpauseServer(self): + pass + def updateAccount(self, plugin, loginname, password): + pass + def updateAccountInfo(self, account): + pass + def updatePackage(self, pack): + pass + def updateUserData(self, data): + pass + def uploadContainer(self, filename, data): + pass + diff --git a/pyload/remote/apitypes_debug.py b/pyload/remote/apitypes_debug.py new file mode 100644 index 000000000..74ea8a6a8 --- /dev/null +++ b/pyload/remote/apitypes_debug.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + +from apitypes import * + +enums = [ + "DownloadState", + "DownloadStatus", + "FileStatus", + "InputType", + "Interaction", + "MediaType", + "PackageStatus", + "Permission", + "Role", +] + +classes = { + 'AccountInfo' : [basestring, basestring, int, bool, int, int, int, bool, bool, bool, (list, ConfigItem)], + 'AddonInfo' : [basestring, basestring, basestring], + 'AddonService' : [basestring, basestring, (list, basestring), (None, int)], + 'ConfigHolder' : [basestring, basestring, basestring, basestring, (list, ConfigItem), (None, (list, AddonInfo))], + 'ConfigInfo' : [basestring, basestring, basestring, basestring, bool, (None, bool)], + 'ConfigItem' : [basestring, basestring, basestring, Input, basestring], + 'DownloadInfo' : [basestring, basestring, basestring, int, basestring, basestring], + 'DownloadProgress' : [int, int, int, int], + 'EventInfo' : [basestring, (list, basestring)], + 'FileDoesNotExists' : [int], + 'FileInfo' : [int, basestring, int, int, int, int, int, int, int, (None, DownloadInfo)], + 'Input' : [int, (None, basestring), (None, basestring)], + 'InteractionTask' : [int, int, Input, basestring, basestring, basestring], + 'InvalidConfigSection' : [basestring], + 'LinkStatus' : [basestring, basestring, int, int, (None, basestring), (None, basestring)], + 'OnlineCheck' : [int, (dict, basestring, LinkStatus)], + 'PackageDoesNotExists' : [int], + 'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, bool, int, PackageStats, (list, int), (list, int)], + 'PackageStats' : [int, int, int, int], + 'ProgressInfo' : [basestring, basestring, basestring, int, int, int, (None, DownloadProgress)], + 'ServerStatus' : [int, int, int, int, int, bool, bool, bool, bool], + 'ServiceDoesNotExists' : [basestring, basestring], + 'ServiceException' : [basestring], + 'TreeCollection' : [PackageInfo, (dict, int, FileInfo), (dict, int, PackageInfo)], + 'UserData' : [int, basestring, basestring, int, int, basestring, int, int, basestring, int, int, basestring], + 'UserDoesNotExists' : [basestring], +} + +methods = { + 'addLinks': None, + 'addLocalFile': None, + 'addPackage': int, + 'addPackageChild': int, + 'addPackageP': int, + 'addUser': UserData, + 'callAddon': None, + 'callAddonHandler': None, + 'checkContainer': OnlineCheck, + 'checkHTML': OnlineCheck, + 'checkLinks': OnlineCheck, + 'createPackage': int, + 'deleteConfig': None, + 'deleteFiles': None, + 'deletePackages': None, + 'findFiles': TreeCollection, + 'findPackages': TreeCollection, + 'freeSpace': int, + 'generateDownloadLink': basestring, + 'generatePackages': (dict, basestring, list), + 'getAccountInfo': AccountInfo, + 'getAccountTypes': (list, basestring), + 'getAccounts': (list, AccountInfo), + 'getAddonHandler': (dict, basestring, list), + 'getAllFiles': TreeCollection, + 'getAllUserData': (dict, int, UserData), + 'getAvailablePlugins': (list, ConfigInfo), + 'getConfig': (dict, basestring, ConfigHolder), + 'getConfigValue': basestring, + 'getCoreConfig': (list, ConfigInfo), + 'getFileInfo': FileInfo, + 'getFileTree': TreeCollection, + 'getFilteredFileTree': TreeCollection, + 'getFilteredFiles': TreeCollection, + 'getInteractionTasks': (list, InteractionTask), + 'getLog': (list, basestring), + 'getPackageContent': TreeCollection, + 'getPackageInfo': PackageInfo, + 'getPluginConfig': (list, ConfigInfo), + 'getProgressInfo': (list, ProgressInfo), + 'getServerStatus': ServerStatus, + 'getServerVersion': basestring, + 'getUserData': UserData, + 'getWSAddress': basestring, + 'hasAddonHandler': bool, + 'isInteractionWaiting': bool, + 'loadConfig': ConfigHolder, + 'login': bool, + 'moveFiles': bool, + 'movePackage': bool, + 'orderFiles': None, + 'orderPackage': None, + 'parseLinks': (dict, basestring, list), + 'pauseServer': None, + 'pollResults': OnlineCheck, + 'quit': None, + 'recheckPackage': None, + 'removeAccount': None, + 'removeUser': None, + 'restart': None, + 'restartFailed': None, + 'restartFile': None, + 'restartPackage': None, + 'saveConfig': None, + 'searchSuggestions': (list, basestring), + 'setConfigValue': None, + 'setInteractionResult': None, + 'setPackageFolder': bool, + 'setPassword': bool, + 'stopAllDownloads': None, + 'stopDownloads': None, + 'togglePause': bool, + 'toggleReconnect': bool, + 'unpauseServer': None, + 'updateAccount': AccountInfo, + 'updateAccountInfo': None, + 'updatePackage': None, + 'updateUserData': None, + 'uploadContainer': int, +} diff --git a/pyload/remote/create_apitypes.py b/pyload/remote/create_apitypes.py new file mode 100644 index 000000000..61063fa3b --- /dev/null +++ b/pyload/remote/create_apitypes.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +import inspect +from os.path import abspath, dirname, join + +path = dirname(abspath(__file__)) +root = abspath(join(path, "..", "..")) + +from thrift.Thrift import TType +from thriftgen.pyload import ttypes +from thriftgen.pyload import Pyload + +# TODO: import and add version +# from pyload import CURRENT_VERSION + +type_map = { + TType.BOOL: 'bool', + TType.DOUBLE: 'float', + TType.I16: 'int', + TType.I32: 'int', + TType.I64: 'int', + TType.STRING: 'basestring', + TType.MAP: 'dict', + TType.LIST: 'list', + TType.SET: 'set', + TType.VOID: 'None', + TType.STRUCT: 'BaseObject', + TType.UTF8: 'unicode', +} + +def get_spec(spec, optional=False): + """ analyze the generated spec file and writes information into file """ + if spec[1] == TType.STRUCT: + return spec[3][0].__name__ + elif spec[1] == TType.LIST: + if spec[3][0] == TType.STRUCT: + ttype = spec[3][1][0].__name__ + else: + ttype = type_map[spec[3][0]] + return "(list, %s)" % ttype + elif spec[1] == TType.MAP: + if spec[3][2] == TType.STRUCT: + ttype = spec[3][3][0].__name__ + else: + ttype = type_map[spec[3][2]] + + return "(dict, %s, %s)" % (type_map[spec[3][0]], ttype) + else: + return type_map[spec[1]] + +optional_re = "%d: +optional +[a-z0-9<>_-]+ +%s" + +def main(): + + enums = [] + classes = [] + tf = open(join(path, "pyload.thrift"), "rb").read() + + print "generating apitypes.py" + + for name in dir(ttypes): + klass = getattr(ttypes, name) + + if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)): + continue + + if hasattr(klass, "thrift_spec"): + classes.append(klass) + else: + enums.append(klass) + + + f = open(join(path, "apitypes.py"), "wb") + f.write( + """#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + +class BaseObject(object): +\t__slots__ = [] + +\tdef __str__(self): +\t\treturn "<%s %s>" % (self.__class__.__name__, ", ".join("%s=%s" % (k,getattr(self,k)) for k in self.__slots__)) + +class ExceptionObject(Exception): +\t__slots__ = [] + +""") + + dev = open(join(path, "apitypes_debug.py"), "wb") + dev.write("""#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n +from apitypes import *\n +""") + + dev.write("enums = [\n") + + ## generate enums + for enum in enums: + name = enum.__name__ + f.write("class %s:\n" % name) + + for attr in sorted(dir(enum), key=lambda x: getattr(enum, x)): + if attr.startswith("_") or attr in ("read", "write"): continue + f.write("\t%s = %s\n" % (attr, getattr(enum, attr))) + + dev.write('\t"%s",\n' % name) + f.write("\n") + + dev.write("]\n\n") + + dev.write("classes = {\n") + + for klass in classes: + name = klass.__name__ + base = "ExceptionObject" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject" + f.write("class %s(%s):\n" % (name, base)) + + # No attributes, don't write further info + if not klass.__slots__: + f.write("\tpass\n\n") + continue + + f.write("\t__slots__ = %s\n\n" % klass.__slots__) + dev.write("\t'%s' : [" % name) + + #create init + args = ["self"] + ["%s=None" % x for x in klass.__slots__] + specs = [] + + f.write("\tdef __init__(%s):\n" % ", ".join(args)) + for i, attr in enumerate(klass.__slots__): + f.write("\t\tself.%s = %s\n" % (attr, attr)) + + spec = klass.thrift_spec[i+1] + # assert correct order, so the list of types is enough for check + assert spec[2] == attr + # dirty way to check optional attribute, since it is not in the generated code + # can produce false positives, but these are not critical + optional = re.search(optional_re % (i+1, attr), tf, re.I) + if optional: + specs.append("(None, %s)" % get_spec(spec)) + else: + specs.append(get_spec(spec)) + + f.write("\n") + dev.write(", ".join(specs) + "],\n") + + dev.write("}\n\n") + + f.write("class Iface(object):\n") + dev.write("methods = {\n") + + for name in dir(Pyload.Iface): + if name.startswith("_"): continue + + func = inspect.getargspec(getattr(Pyload.Iface, name)) + + f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args))) + + spec = getattr(Pyload, "%s_result" % name).thrift_spec + if not spec or not spec[0]: + dev.write("\t'%s': None,\n" % name) + else: + spec = spec[0] + dev.write("\t'%s': %s,\n" % (name, get_spec(spec))) + + f.write("\n") + dev.write("}\n") + + f.close() + dev.close() + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/pyload/remote/create_jstypes.py b/pyload/remote/create_jstypes.py new file mode 100644 index 000000000..90afa4c96 --- /dev/null +++ b/pyload/remote/create_jstypes.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from os.path import abspath, dirname, join + +path = dirname(abspath(__file__)) +module = join(path, "..") + +import apitypes +from apitypes_debug import enums + +# generate js enums +def main(): + + print "generating apitypes.js" + + f = open(join(module, 'web', 'app', 'scripts', 'utils', 'apitypes.js'), 'wb') + f.write("""// Autogenerated, do not edit! +/*jslint -W070: false*/ +define([], function() { +\t'use strict'; +\treturn { +""") + + for name in enums: + enum = getattr(apitypes, name) + values = dict([(attr, getattr(enum, attr)) for attr in dir(enum) if not attr.startswith("_")]) + + f.write("\t\t%s: %s,\n" % (name, str(values))) + + f.write("\t};\n});") + f.close() + + +if __name__ == "__main__": + main() diff --git a/pyload/remote/json_converter.py b/pyload/remote/json_converter.py new file mode 100644 index 000000000..b4e57c4a0 --- /dev/null +++ b/pyload/remote/json_converter.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +try: + from pyload.utils import json +except ImportError: + import json + +import apitypes +from apitypes import BaseObject +from apitypes import ExceptionObject + +# compact json separator +separators = (',', ':') + +# json encoder that accepts api objects +class BaseEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, BaseObject) or isinstance(o, ExceptionObject): + ret = {"@class": o.__class__.__name__} + for att in o.__slots__: + ret[att] = getattr(o, att) + return ret + + return json.JSONEncoder.default(self, o) + +# more compact representation, only clients with information of the classes can handle it +class BaseEncoderCompact(json.JSONEncoder): + def default(self, o): + if isinstance(o, BaseObject) or isinstance(o, ExceptionObject): + ret = {"@compact": [o.__class__.__name__]} + ret["@compact"].extend(getattr(o, attr) for attr in o.__slots__) + return ret + + return json.JSONEncoder.default(self, o) + + +def convert_obj(dct): + if '@class' in dct: + cls = getattr(apitypes, dct['@class']) + del dct['@class'] + # convert keywords to str, <=2.6 does not accept unicode + return cls(**dict((str(x) if type(x) == unicode else x, y) for x, y in dct.iteritems())) + elif '@compact' in dct: + cls = getattr(apitypes, dct['@compact'][0]) + return cls(*dct['@compact'][1:]) + + return dct + + +def dumps(*args, **kwargs): + if 'compact' in kwargs and kwargs['compact']: + kwargs['cls'] = BaseEncoderCompact + del kwargs['compact'] + else: + kwargs['cls'] = BaseEncoder + + kwargs['separators'] = separators + return json.dumps(*args, **kwargs) + + +def dump(*args, **kwargs): + if 'compact' in kwargs and kwargs['compact']: + kwargs['cls'] = BaseEncoderCompact + del kwargs['compact'] + else: + kwargs['cls'] = BaseEncoder + + kwargs['separators'] = separators + return json.dump(*args, **kwargs) + + +def loads(*args, **kwargs): + kwargs['object_hook'] = convert_obj + return json.loads(*args, **kwargs)
\ No newline at end of file diff --git a/pyload/remote/pyload.thrift b/pyload/remote/pyload.thrift new file mode 100644 index 000000000..9bcc2ce89 --- /dev/null +++ b/pyload/remote/pyload.thrift @@ -0,0 +1,532 @@ +namespace java org.pyload.thrift + +typedef i32 FileID +typedef i32 PackageID +typedef i32 ResultID +typedef i32 InteractionID +typedef i32 UserID +typedef i64 UTCDate +typedef i64 ByteCount +typedef list<string> LinkList +typedef string PluginName +typedef string JSONString + +enum DownloadStatus { + NA, // No downloads status set + Offline, + Online, + Queued, + Paused, + Finished, + Skipped, + Failed, + Starting, + Waiting, + Downloading, + TempOffline, + Aborted, + NotPossible, + Decrypting, + Processing, + Custom, + Unknown +} + +// Download states, combination of several downloadstatuses +// defined in Api +enum DownloadState { + All, + Finished, + Unfinished, + Failed, + Unmanaged // internal state +} + +enum MediaType { + All = 0 + Other = 1, + Audio = 2, + Image = 4, + Video = 8, + Document = 16, + Archive = 32, + Executable = 64 +} + +enum FileStatus { + Ok, + Missing, + Remote, // file is available at remote location +} + +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, ip, s.o. +enum InputType { + NA, + Text, + Int, + File, + Folder, + Textbox, + Password, + Time, + 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 + PluginList, // a list plugins from pyload + 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 Interaction { + All = 0, + Notification = 1, + Captcha = 2, + Query = 4, +} + +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 Input { + 1: InputType type, + 2: optional JSONString default_value, + 3: optional JSONString data, +} + +struct DownloadProgress { + 1: FileID fid, + 2: PackageID pid, + 3: ByteCount speed, // per second + 4: DownloadStatus status, +} + +struct ProgressInfo { + 1: PluginName plugin, + 2: string name, + 3: string statusmsg, + 4: i32 eta, // in seconds + 5: ByteCount done, + 6: ByteCount total, // arbitary number, size in case of files + 7: optional DownloadProgress download +} + +// 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: UserID owner, + 5: ByteCount size, + 6: FileStatus status, + 7: MediaType media, + 8: UTCDate added, + 9: i16 fileorder, + 10: 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: UserID owner, + 6: string site, + 7: string comment, + 8: string password, + 9: UTCDate added, + 10: list<string> tags, + 11: PackageStatus status, + 12: bool shared, + 13: i16 packageorder, + 14: PackageStats stats, + 15: list<FileID> fids, + 16: 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 online results +struct LinkStatus { + 1: string url, + 2: string name, + 3: ByteCount size, // size <= 0 : unknown + 4: DownloadStatus status, + 5: optional PluginName plugin, + 6: optional string hash +} + +struct ServerStatus { + 1: ByteCount speed, + 2: i16 linkstotal, + 3: i16 linksqueue, + 4: ByteCount sizetotal, + 5: ByteCount sizequeue, + 6: bool notifications, + 7: bool paused, + 8: bool download, + 9: bool reconnect, +} + +struct InteractionTask { + 1: InteractionID iid, + 2: Interaction type, + 3: Input input, + 4: string title, + 5: string description, + 6: PluginName plugin, +} + +struct AddonService { + 1: string func_name, + 2: string description, + 3: list<string> arguments, + 4: optional i16 media, +} + +struct AddonInfo { + 1: string func_name, + 2: string description, + 3: JSONString value, +} + +struct ConfigItem { + 1: string name, + 2: string label, + 3: string description, + 4: Input input, + 5: JSONString value, +} + +struct ConfigHolder { + 1: string name, // for plugin this is the PluginName + 2: string label, + 3: string description, + 4: string explanation, + 5: list<ConfigItem> items, + 6: optional list<AddonInfo> info, +} + +struct ConfigInfo { + 1: string name + 2: string label, + 3: string description, + 4: string category, + 5: bool user_context, + 6: optional bool activated, +} + +struct EventInfo { + 1: string eventname, + 2: list<JSONString> event_args, //will contain json objects +} + +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: list <ConfigItem> config, +} + +struct OnlineCheck { + 1: ResultID rid, // -1 -> nothing more to get + 2: map<string, LinkStatus> data, // package name 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 +} + +exception InvalidConfigSection { + 1: string section +} + +exception Unauthorized { +} + +exception Forbidden { +} + +exception Conflict { +} + + +service Pyload { + + /////////////////////// + // Core Status + /////////////////////// + + string getServerVersion(), + string getWSAddress(), + ServerStatus getServerStatus(), + list<ProgressInfo> getProgressInfo(), + + list<string> getLog(1: i32 offset), + ByteCount freeSpace(), + + void pauseServer(), + void unpauseServer(), + bool togglePause(), + bool toggleReconnect(), + + void quit(), + void restart(), + + /////////////////////// + // Configuration + /////////////////////// + + map<string, ConfigHolder> getConfig(), + string getConfigValue(1: string section, 2: string option), + + // two methods with ambigous classification, could be configuration or addon/plugin related + list<ConfigInfo> getCoreConfig(), + list<ConfigInfo> getPluginConfig(), + list<ConfigInfo> getAvailablePlugins(), + + ConfigHolder loadConfig(1: string name), + + void setConfigValue(1: string section, 2: string option, 3: string value), + void saveConfig(1: ConfigHolder config), + void deleteConfig(1: PluginName plugin), + + /////////////////////// + // Download Preparing + /////////////////////// + + map<PluginName, LinkList> parseLinks(1: LinkList links), + + // parses results and generates packages + OnlineCheck checkLinks(1: LinkList links), + OnlineCheck checkContainer(1: string filename, 2: binary data), + OnlineCheck checkHTML(1: string html, 2: string url), + + // poll results from previously started online check + OnlineCheck pollResults(1: ResultID rid), + + // packagename -> urls + map<string, LinkList> generatePackages(1: LinkList links), + + /////////////////////// + // Download + /////////////////////// + + 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), // delete the whole folder recursive + + // Modify Downloads + + void restartPackage(1: PackageID pid), + void restartFile(1: FileID fid), + void recheckPackage(1: PackageID pid), + void restartFailed() + void stopDownloads(1: list<FileID> fids), + void stopAllDownloads(), + + //////////////////////////// + // File Information retrieval + //////////////////////////// + + TreeCollection getAllFiles(), + TreeCollection getFilteredFiles(1: DownloadState state), + + // pid -1 for root, full=False only delivers first level in tree + TreeCollection getFileTree(1: PackageID pid, 2: bool full), + TreeCollection getFilteredFileTree(1: PackageID pid, 2: bool full, 3: DownloadState state), + + // 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), + TreeCollection findPackages(1: list<string> tags), + list<string> searchSuggestions(1: string pattern), + + // Modify Files/Packages + + // moving package while downloading is not possible, so they will return bool to indicate success + void updatePackage(1: PackageInfo pack) throws (1: PackageDoesNotExists e), + bool setPackageFolder(1: PackageID pid, 2: string path) 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 = interaction types binary ORed + bool isInteractionWaiting(1: i16 mode), + list<InteractionTask> getInteractionTasks(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), + + /////////////////////// + // Account Methods + /////////////////////// + + list<string> getAccountTypes(), + + list<AccountInfo> getAccounts(), + AccountInfo getAccountInfo(1: PluginName plugin, 2: string loginname, 3: bool refresh), + + AccountInfo updateAccount(1: PluginName plugin, 2: string loginname, 3: string password), + void updateAccountInfo(1: AccountInfo account), + void removeAccount(1: AccountInfo account), + + ///////////////////////// + // Auth+User Information + ///////////////////////// + + bool login(1: string username, 2: string password), + // returns own user data + UserData getUserData(), + + // works contextual, admin can change every password + bool setPassword(1: string username, 2: string old_password, 3: string new_password), + + // all user, for admins only + map<UserID, UserData> getAllUserData(), + + 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), + + /////////////////////// + // Addon Methods + /////////////////////// + + //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), + + + //scheduler + + // TODO + +} diff --git a/pyload/remote/wsbackend/AbstractHandler.py b/pyload/remote/wsbackend/AbstractHandler.py new file mode 100644 index 000000000..842d87473 --- /dev/null +++ b/pyload/remote/wsbackend/AbstractHandler.py @@ -0,0 +1,145 @@ +#!/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 mod_pywebsocket.msgutil import send_message +from mod_pywebsocket.util import get_class_logger + +from pyload.Api import UserData +from pyload.remote.json_converter import loads, dumps + + +class AbstractHandler: + """ + Abstract Handler providing common methods shared across WebSocket handlers + """ + PATH = "/" + + OK = 200 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + ERROR = 500 + + def __init__(self, api): + self.log = get_class_logger() + self.api = api + self.core = api.core + + def do_extra_handshake(self, req): + self.log.debug("WS Connected: %s" % req) + req.api = None #when api is set client is logged in + + # allow login via session when webinterface is active + if self.core.config['webinterface']['activated']: + cookie = req.headers_in.getheader('Cookie') + s = self.load_session(cookie) + if s: + uid = s.get('uid', None) + req.api = self.api.withUserContext(uid) + self.log.debug("WS authenticated user with cookie: %d" % uid) + + self.on_open(req) + + def on_open(self, req): + pass + + def load_session(self, cookies): + from Cookie import SimpleCookie + from beaker.session import Session + from pyload.web.webinterface import session + + cookies = SimpleCookie(cookies) + sid = cookies.get(session.options['key']) + if not sid: + return None + + s = Session({}, use_cookies=False, id=sid.value, **session.options) + if s.is_new: + return None + + return s + + def passive_closing_handshake(self, req): + self.log.debug("WS Closed: %s" % req) + self.on_close(req) + + def on_close(self, req): + pass + + def transfer_data(self, req): + raise NotImplemented + + def handle_call(self, msg, req): + """ Parses the msg for an argument call. If func is null an response was already sent. + + :return: func, args, kwargs + """ + try: + o = loads(msg) + except ValueError, e: #invalid json object + self.log.debug("Invalid Request: %s" % e) + self.send_result(req, self.ERROR, "No JSON request") + return None, None, None + + if not isinstance(o, basestring) and type(o) != list and len(o) not in range(1, 4): + self.log.debug("Invalid Api call: %s" % o) + self.send_result(req, self.ERROR, "Invalid Api call") + return None, None, None + + # called only with name, no args + if isinstance(o, basestring): + return o, [], {} + elif len(o) == 1: # arguments omitted + return o[0], [], {} + elif len(o) == 2: + func, args = o + if type(args) == list: + return func, args, {} + else: + return func, [], args + else: + return tuple(o) + + def do_login(self, req, args, kwargs): + user = None + # Cookies login when one argument is given + if len(args) == 1: + s = self.load_session(args) + if s: + user = UserData(uid=s.get('uid', None)) + else: + s = self.api.checkAuth(*args, **kwargs) + if s: + user = UserData(uid=s.uid) + + if user: + req.api = self.api.withUserContext(user.uid) + return self.send_result(req, self.OK, True) + else: + return self.send_result(req, self.FORBIDDEN, "Forbidden") + + def do_logout(self, req): + req.api = None + return self.send_result(req, self.OK, True) + + def send_result(self, req, code, result): + return send_message(req, dumps([code, result])) + + def send(self, req, obj): + return send_message(req, dumps(obj))
\ No newline at end of file diff --git a/pyload/remote/wsbackend/ApiHandler.py b/pyload/remote/wsbackend/ApiHandler.py new file mode 100644 index 000000000..4685121d4 --- /dev/null +++ b/pyload/remote/wsbackend/ApiHandler.py @@ -0,0 +1,81 @@ +#!/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 mod_pywebsocket.msgutil import receive_message + +from pyload.Api import ExceptionObject + +from AbstractHandler import AbstractHandler + +class ApiHandler(AbstractHandler): + """Provides access to the API. + + Send your request as json encoded string in the following manner: + ["function", [*args]] or ["function", {**kwargs}] + + the result will be: + + [code, result] + + Don't forget to login first. + Non json request will be ignored. + """ + + PATH = "/api" + + def transfer_data(self, req): + while True: + try: + line = receive_message(req) + except TypeError, e: # connection closed + self.log.debug("WS Error: %s" % e) + return self.passive_closing_handshake(req) + + self.handle_message(line, req) + + def handle_message(self, msg, req): + + func, args, kwargs = self.handle_call(msg, req) + if not func: + return # handle_call already sent the result + + if func == 'login': + return self.do_login(req, args, kwargs) + elif func == 'logout': + return self.do_logout(req) + else: + if not req.api: + return self.send_result(req, self.FORBIDDEN, "Forbidden") + + if not self.api.isAuthorized(func, req.api.user): + return self.send_result(req, self.UNAUTHORIZED, "Unauthorized") + + try: + result = getattr(req.api, func)(*args, **kwargs) + except ExceptionObject, e: + return self.send_result(req, self.BAD_REQUEST, e) + except AttributeError: + return self.send_result(req, self.NOT_FOUND, "Not Found") + except Exception, e: + self.core.print_exc() + return self.send_result(req, self.ERROR, str(e)) + + # None is invalid json type + if result is None: result = True + + return self.send_result(req, self.OK, result)
\ No newline at end of file diff --git a/pyload/remote/wsbackend/AsyncHandler.py b/pyload/remote/wsbackend/AsyncHandler.py new file mode 100644 index 000000000..c7a26cd6b --- /dev/null +++ b/pyload/remote/wsbackend/AsyncHandler.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 Queue import Queue, Empty +from threading import Lock +from time import time + +from mod_pywebsocket.msgutil import receive_message + +from pyload.Api import EventInfo, Interaction +from pyload.utils import lock +from AbstractHandler import AbstractHandler + +class Mode: + STANDBY = 1 + RUNNING = 2 + +class AsyncHandler(AbstractHandler): + """ + Handler that provides asynchronous information about server status, running downloads, occurred events. + + Progress information are continuous and will be pushed in a fixed interval when available. + After connect you have to login and can set the interval by sending the json command ["setInterval", xy]. + To start receiving updates call "start", afterwards no more incoming messages will be accepted! + """ + + PATH = "/async" + COMMAND = "start" + + PROGRESS_INTERVAL = 1.5 + EVENT_PATTERN = re.compile(r"^(package|file|interaction|linkcheck)", re.I) + INTERACTION = Interaction.All + + def __init__(self, api): + AbstractHandler.__init__(self, api) + self.clients = [] + self.lock = Lock() + + self.core.evm.listenTo("event", self.add_event) + + @lock + def on_open(self, req): + req.queue = Queue() + req.interval = self.PROGRESS_INTERVAL + req.events = self.EVENT_PATTERN + req.interaction = self.INTERACTION + req.mode = Mode.STANDBY + req.t = time() # time when update should be pushed + self.clients.append(req) + + @lock + def on_close(self, req): + try: + del req.queue + except AttributeError: # connection could be uninitialized + pass + + try: + self.clients.remove(req) + except ValueError: # ignore when not in list + pass + + @lock + def add_event(self, event, *args, **kwargs): + # Convert arguments to json suited instance + event = EventInfo(event, [x.toInfoData() if hasattr(x, 'toInfoData') else x for x in args]) + + # use the user kwarg argument to determine access + user = None + if 'user' in kwargs: + user = kwargs['user'] + del kwargs['user'] + if hasattr(user, 'uid'): + user = user.uid + + for req in self.clients: + # Not logged in yet + if not req.api: continue + + # filter events that these user is no owner of + # TODO: events are security critical, this should be revised later + # TODO: permissions? interaction etc + if not req.api.user.isAdmin(): + if user is not None and req.api.primaryUID != user: + break + + skip = False + for arg in args: + if hasattr(arg, 'owner') and arg.owner != req.api.primaryUID: + skip = True + break + + # user should not get this event + if skip: break + + if req.events.search(event.eventname): + self.log.debug("Pushing event %s" % event) + req.queue.put(event) + + def transfer_data(self, req): + while True: + + if req.mode == Mode.STANDBY: + try: + line = receive_message(req) + except TypeError, e: # connection closed + self.log.debug("WS Error: %s" % e) + return self.passive_closing_handshake(req) + + self.mode_standby(line, req) + else: + if self.mode_running(req): + return self.passive_closing_handshake(req) + + def mode_standby(self, msg, req): + """ accepts calls before pushing updates """ + func, args, kwargs = self.handle_call(msg, req) + if not func: + return # Result was already sent + + if func == 'login': + return self.do_login(req, args, kwargs) + + elif func == 'logout': + return self.do_logout(req) + + else: + if not req.api: + return self.send_result(req, self.FORBIDDEN, "Forbidden") + + if func == "setInterval": + req.interval = args[0] + elif func == "setEvents": + req.events = re.compile(args[0], re.I) + elif func == "setInteraction": + req.interaction = args[0] + elif func == self.COMMAND: + req.mode = Mode.RUNNING + + + def mode_running(self, req): + """ Listen for events, closes socket when returning True """ + try: + # block length of update interval if necessary + ev = req.queue.get(True, req.interval) + try: + self.send(req, ev) + except TypeError: + self.log.debug("Event %s not converted" % ev) + ev.event_args = [] + # Resend the event without arguments + self.send(req, ev) + + except Empty: + pass + + if req.t <= time(): + # TODO: server status is not enough + # modify core api to include progress? think of other needed information to show + # eta is quite wrong currently + # notifications + self.send(req, self.api.getServerStatus()) + self.send(req, self.api.getProgressInfo()) + + # update time for next update + req.t = time() + req.interval
\ No newline at end of file diff --git a/pyload/remote/wsbackend/Dispatcher.py b/pyload/remote/wsbackend/Dispatcher.py new file mode 100644 index 000000000..44cc7555e --- /dev/null +++ b/pyload/remote/wsbackend/Dispatcher.py @@ -0,0 +1,31 @@ +#!/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 mod_pywebsocket import util +from mod_pywebsocket.dispatch import Dispatcher as BaseDispatcher + +class Dispatcher(BaseDispatcher): + + def __init__(self): + self._logger = util.get_class_logger(self) + + self._handler_suite_map = {} + self._source_warnings = [] + + def addHandler(self, path, handler): + self._handler_suite_map[path] = handler
\ No newline at end of file diff --git a/pyload/remote/wsbackend/Server.py b/pyload/remote/wsbackend/Server.py new file mode 100644 index 000000000..02da44f04 --- /dev/null +++ b/pyload/remote/wsbackend/Server.py @@ -0,0 +1,737 @@ +#!/usr/bin/env python +# +# Copyright 2012, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# A copy of standalone.py with uneeded stuff removed +# some logging methods removed +# Added api attribute to request + +import BaseHTTPServer +import CGIHTTPServer +import SocketServer +import httplib +import logging +import os +import re +import select +import socket +import sys +import threading + +_HAS_SSL = False +_HAS_OPEN_SSL = False + +from mod_pywebsocket import common +from mod_pywebsocket import dispatch +from mod_pywebsocket import handshake +from mod_pywebsocket import http_header_util +from mod_pywebsocket import memorizingfile +from mod_pywebsocket import util + + +_DEFAULT_LOG_MAX_BYTES = 1024 * 256 +_DEFAULT_LOG_BACKUP_COUNT = 5 + +_DEFAULT_REQUEST_QUEUE_SIZE = 128 + +# 1024 is practically large enough to contain WebSocket handshake lines. +_MAX_MEMORIZED_LINES = 1024 + +def import_ssl(): + global _HAS_SSL, _HAS_OPEN_SSL + try: + import ssl + _HAS_SSL = True + except ImportError: + try: + import OpenSSL.SSL + _HAS_OPEN_SSL = True + except ImportError: + pass + + +class _StandaloneConnection(object): + """Mimic mod_python mp_conn.""" + + def __init__(self, request_handler): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._request_handler = request_handler + + def get_local_addr(self): + """Getter to mimic mp_conn.local_addr.""" + + return (self._request_handler.server.server_name, + self._request_handler.server.server_port) + local_addr = property(get_local_addr) + + def get_remote_addr(self): + """Getter to mimic mp_conn.remote_addr. + + Setting the property in __init__ won't work because the request + handler is not initialized yet there.""" + + return self._request_handler.client_address + remote_addr = property(get_remote_addr) + + def write(self, data): + """Mimic mp_conn.write().""" + + return self._request_handler.wfile.write(data) + + def read(self, length): + """Mimic mp_conn.read().""" + + return self._request_handler.rfile.read(length) + + def get_memorized_lines(self): + """Get memorized lines.""" + + return self._request_handler.rfile.get_memorized_lines() + + +class _StandaloneRequest(object): + """Mimic mod_python request.""" + + def __init__(self, request_handler, use_tls): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._logger = util.get_class_logger(self) + + self._request_handler = request_handler + self.connection = _StandaloneConnection(request_handler) + self._use_tls = use_tls + self.headers_in = request_handler.headers + + def get_uri(self): + """Getter to mimic request.uri.""" + + return self._request_handler.path + uri = property(get_uri) + + def get_method(self): + """Getter to mimic request.method.""" + + return self._request_handler.command + method = property(get_method) + + def get_protocol(self): + """Getter to mimic request.protocol.""" + + return self._request_handler.request_version + protocol = property(get_protocol) + + def is_https(self): + """Mimic request.is_https().""" + + return self._use_tls + + def _drain_received_data(self): + """Don't use this method from WebSocket handler. Drains unread data + in the receive buffer. + """ + + raw_socket = self._request_handler.connection + drained_data = util.drain_received_data(raw_socket) + + if drained_data: + self._logger.debug( + 'Drained data following close frame: %r', drained_data) + + +class _StandaloneSSLConnection(object): + """A wrapper class for OpenSSL.SSL.Connection to provide makefile method + which is not supported by the class. + """ + + def __init__(self, connection): + self._connection = connection + + def __getattribute__(self, name): + if name in ('_connection', 'makefile'): + return object.__getattribute__(self, name) + return self._connection.__getattribute__(name) + + def __setattr__(self, name, value): + if name in ('_connection', 'makefile'): + return object.__setattr__(self, name, value) + return self._connection.__setattr__(name, value) + + def makefile(self, mode='r', bufsize=-1): + return socket._fileobject(self._connection, mode, bufsize) + + +def _alias_handlers(dispatcher, websock_handlers_map_file): + """Set aliases specified in websock_handler_map_file in dispatcher. + + Args: + dispatcher: dispatch.Dispatcher instance + websock_handler_map_file: alias map file + """ + + fp = open(websock_handlers_map_file) + try: + for line in fp: + if line[0] == '#' or line.isspace(): + continue + m = re.match('(\S+)\s+(\S+)', line) + if not m: + logging.warning('Wrong format in map file:' + line) + continue + try: + dispatcher.add_resource_path_alias( + m.group(1), m.group(2)) + except dispatch.DispatchException, e: + logging.error(str(e)) + finally: + fp.close() + + +class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + """HTTPServer specialized for WebSocket.""" + + # Overrides SocketServer.ThreadingMixIn.daemon_threads + daemon_threads = True + # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address + allow_reuse_address = True + + def __init__(self, options): + """Override SocketServer.TCPServer.__init__ to set SSL enabled + socket object to self.socket before server_bind and server_activate, + if necessary. + """ + # Removed dispatcher init here + self._logger = logging.getLogger("log") + + self.request_queue_size = options.request_queue_size + self.__ws_is_shut_down = threading.Event() + self.__ws_serving = False + + SocketServer.BaseServer.__init__( + self, (options.server_host, options.port), WebSocketRequestHandler) + + # Expose the options object to allow handler objects access it. We name + # it with websocket_ prefix to avoid conflict. + self.websocket_server_options = options + + self._create_sockets() + self.server_bind() + self.server_activate() + + def _create_sockets(self): + self.server_name, self.server_port = self.server_address + self._sockets = [] + if not self.server_name: + # On platforms that doesn't support IPv6, the first bind fails. + # On platforms that supports IPv6 + # - If it binds both IPv4 and IPv6 on call with AF_INET6, the + # first bind succeeds and the second fails (we'll see 'Address + # already in use' error). + # - If it binds only IPv6 on call with AF_INET6, both call are + # expected to succeed to listen both protocol. + addrinfo_array = [ + (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''), + (socket.AF_INET, socket.SOCK_STREAM, '', '', '')] + else: + addrinfo_array = socket.getaddrinfo(self.server_name, + self.server_port, + socket.AF_UNSPEC, + socket.SOCK_STREAM, + socket.IPPROTO_TCP) + for addrinfo in addrinfo_array: + family, socktype, proto, canonname, sockaddr = addrinfo + try: + socket_ = socket.socket(family, socktype) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + continue + if self.websocket_server_options.use_tls: + if _HAS_SSL: + if self.websocket_server_options.tls_client_auth: + client_cert_ = ssl.CERT_REQUIRED + else: + client_cert_ = ssl.CERT_NONE + socket_ = ssl.wrap_socket(socket_, + keyfile=self.websocket_server_options.private_key, + certfile=self.websocket_server_options.certificate, + ssl_version=ssl.PROTOCOL_SSLv23, + ca_certs=self.websocket_server_options.tls_client_ca, + cert_reqs=client_cert_) + if _HAS_OPEN_SSL: + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + ctx.use_privatekey_file( + self.websocket_server_options.private_key) + ctx.use_certificate_file( + self.websocket_server_options.certificate) + socket_ = OpenSSL.SSL.Connection(ctx, socket_) + self._sockets.append((socket_, addrinfo)) + + def server_bind(self): + """Override SocketServer.TCPServer.server_bind to enable multiple + sockets bind. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + if self.allow_reuse_address: + socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + socket_.bind(self.server_address) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + if self.server_address[1] == 0: + # The operating system assigns the actual port number for port + # number 0. This case, the second and later sockets should use + # the same port number. Also self.server_port is rewritten + # because it is exported, and will be used by external code. + self.server_address = ( + self.server_name, socket_.getsockname()[1]) + self.server_port = self.server_address[1] + self._logger.info('Port %r is assigned', self.server_port) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + def server_activate(self): + """Override SocketServer.TCPServer.server_activate to enable multiple + sockets listen. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.debug('Listen on: %r', addrinfo) + try: + socket_.listen(self.request_queue_size) + except Exception, e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + if len(self._sockets) == 0: + self._logger.critical( + 'No sockets activated. Use info log level to see the reason.') + + def server_close(self): + """Override SocketServer.TCPServer.server_close to enable multiple + sockets close. + """ + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Close on: %r', addrinfo) + socket_.close() + + def fileno(self): + """Override SocketServer.TCPServer.fileno.""" + + self._logger.critical('Not supported: fileno') + return self._sockets[0][0].fileno() + + def handle_error(self, rquest, client_address): + """Override SocketServer.handle_error.""" + + self._logger.error( + 'Exception in processing request from: %r\n%s', + client_address, + util.get_stack_trace()) + # Note: client_address is a tuple. + + def get_request(self): + """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection + object with _StandaloneSSLConnection to provide makefile method. We + cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly + attribute. + """ + + accepted_socket, client_address = self.socket.accept() + if self.websocket_server_options.use_tls and _HAS_OPEN_SSL: + accepted_socket = _StandaloneSSLConnection(accepted_socket) + return accepted_socket, client_address + + def serve_forever(self, poll_interval=0.5): + """Override SocketServer.BaseServer.serve_forever.""" + + self.__ws_serving = True + self.__ws_is_shut_down.clear() + handle_request = self.handle_request + if hasattr(self, '_handle_request_noblock'): + handle_request = self._handle_request_noblock + else: + self._logger.warning('Fallback to blocking request handler') + try: + while self.__ws_serving: + r, w, e = select.select( + [socket_[0] for socket_ in self._sockets], + [], [], poll_interval) + for socket_ in r: + self.socket = socket_ + handle_request() + self.socket = None + finally: + self.__ws_is_shut_down.set() + + def shutdown(self): + """Override SocketServer.BaseServer.shutdown.""" + + self.__ws_serving = False + self.__ws_is_shut_down.wait() + + +class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): + """CGIHTTPRequestHandler specialized for WebSocket.""" + + # Use httplib.HTTPMessage instead of mimetools.Message. + MessageClass = httplib.HTTPMessage + + def setup(self): + """Override SocketServer.StreamRequestHandler.setup to wrap rfile + with MemorizingFile. + + This method will be called by BaseRequestHandler's constructor + before calling BaseHTTPRequestHandler.handle. + BaseHTTPRequestHandler.handle will call + BaseHTTPRequestHandler.handle_one_request and it will call + WebSocketRequestHandler.parse_request. + """ + + # Call superclass's setup to prepare rfile, wfile, etc. See setup + # definition on the root class SocketServer.StreamRequestHandler to + # understand what this does. + CGIHTTPServer.CGIHTTPRequestHandler.setup(self) + + self.rfile = memorizingfile.MemorizingFile( + self.rfile, + max_memorized_lines=_MAX_MEMORIZED_LINES) + + def __init__(self, request, client_address, server): + self._logger = util.get_class_logger(self) + + self._options = server.websocket_server_options + + # Overrides CGIHTTPServerRequestHandler.cgi_directories. + self.cgi_directories = self._options.cgi_directories + # Replace CGIHTTPRequestHandler.is_executable method. + if self._options.is_executable_method is not None: + self.is_executable = self._options.is_executable_method + + # OWN MODIFICATION + # This actually calls BaseRequestHandler.__init__. + try: + CGIHTTPServer.CGIHTTPRequestHandler.__init__( + self, request, client_address, server) + except socket.error, e: + # Broken pipe, let it pass + errno = e.args[0] # errno on py < 2.6 + if errno != 32: + raise + self._logger.debug("WS: Broken pipe") + + + + def parse_request(self): + """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. + + Return True to continue processing for HTTP(S), False otherwise. + + See BaseHTTPRequestHandler.handle_one_request method which calls + this method to understand how the return value will be handled. + """ + + # We hook parse_request method, but also call the original + # CGIHTTPRequestHandler.parse_request since when we return False, + # CGIHTTPRequestHandler.handle_one_request continues processing and + # it needs variables set by CGIHTTPRequestHandler.parse_request. + # + # Variables set by this method will be also used by WebSocket request + # handling (self.path, self.command, self.requestline, etc. See also + # how _StandaloneRequest's members are implemented using these + # attributes). + + ### Modified + # Most True values converted into False + if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self): + return False + + if self._options.use_basic_auth: + auth = self.headers.getheader('Authorization') + if auth != self._options.basic_auth_credential: + self.send_response(401) + self.send_header('WWW-Authenticate', + 'Basic realm="Pywebsocket"') + self.end_headers() + self._logger.info('Request basic authentication') + return True + + host, port, resource = http_header_util.parse_uri(self.path) + if resource is None: + self._logger.info('Invalid URI: %r', self.path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return False + server_options = self.server.websocket_server_options + if host is not None: + validation_host = server_options.validation_host + if validation_host is not None and host != validation_host: + self._logger.info('Invalid host: %r (expected: %r)', + host, + validation_host) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return False + if port is not None: + validation_port = server_options.validation_port + if validation_port is not None and port != validation_port: + self._logger.info('Invalid port: %r (expected: %r)', + port, + validation_port) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return False + self.path = resource + + request = _StandaloneRequest(self, self._options.use_tls) + + try: + # Fallback to default http handler for request paths for which + # we don't have request handlers. + if not self._options.dispatcher.get_handler_suite(self.path): + self._logger.info('No handler for resource: %r', + self.path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return False + except dispatch.DispatchException, e: + self._logger.info('%s', e) + self.send_error(e.status) + return False + + # If any Exceptions without except clause setup (including + # DispatchException) is raised below this point, it will be caught + # and logged by WebSocketServer. + + try: + try: + handshake.do_handshake( + request, + self._options.dispatcher, + allowDraft75=self._options.allow_draft75, + strict=self._options.strict) + except handshake.VersionException, e: + self._logger.info('%s', e) + self.send_response(common.HTTP_STATUS_BAD_REQUEST) + self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER, + e.supported_versions) + self.end_headers() + return False + except handshake.HandshakeException, e: + # Handshake for ws(s) failed. + self._logger.info('%s', e) + self.send_error(e.status) + return False + + request._dispatcher = self._options.dispatcher + self._options.dispatcher.transfer_data(request) + except handshake.AbortedByUserException, e: + self._logger.info('%s', e) + return False + + def log_request(self, code='-', size='-'): + """Override BaseHTTPServer.log_request.""" + + self._logger.info('"%s" %s %s', + self.requestline, str(code), str(size)) + + def log_error(self, *args): + """Override BaseHTTPServer.log_error.""" + + # Despite the name, this method is for warnings than for errors. + # For example, HTTP status code is logged by this method. + self._logger.warning('%s - %s', + self.address_string(), + args[0] % args[1:]) + + def is_cgi(self): + """Test whether self.path corresponds to a CGI script. + + Add extra check that self.path doesn't contains .. + Also check if the file is a executable file or not. + If the file is not executable, it is handled as static file or dir + rather than a CGI script. + """ + + if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): + if '..' in self.path: + return False + # strip query parameter from request path + resource_name = self.path.split('?', 2)[0] + # convert resource_name into real path name in filesystem. + scriptfile = self.translate_path(resource_name) + if not os.path.isfile(scriptfile): + return False + if not self.is_executable(scriptfile): + return False + return True + return False + + +def _get_logger_from_class(c): + return logging.getLogger('%s.%s' % (c.__module__, c.__name__)) + + +def _configure_logging(options): + logging.addLevelName(common.LOGLEVEL_FINE, 'FINE') + + logger = logging.getLogger() + logger.setLevel(logging.getLevelName(options.log_level.upper())) + if options.log_file: + handler = logging.handlers.RotatingFileHandler( + options.log_file, 'a', options.log_max, options.log_count) + else: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + deflate_log_level_name = logging.getLevelName( + options.deflate_log_level.upper()) + _get_logger_from_class(util._Deflater).setLevel( + deflate_log_level_name) + _get_logger_from_class(util._Inflater).setLevel( + deflate_log_level_name) + +class DefaultOptions: + server_host = '' + port = common.DEFAULT_WEB_SOCKET_PORT + use_tls = False + private_key = '' + certificate = '' + ca_certificate = '' + dispatcher = None + request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE + use_basic_auth = False + + allow_draft75 = False + strict = False + validation_host = None + validation_port = None + cgi_directories = '' + is_executable_method = False + +def _main(args=None): + """You can call this function from your own program, but please note that + this function has some side-effects that might affect your program. For + example, util.wrap_popen3_for_win use in this method replaces implementation + of os.popen3. + """ + + options, args = _parse_args_and_config(args=args) + + os.chdir(options.document_root) + + _configure_logging(options) + + # TODO(tyoshino): Clean up initialization of CGI related values. Move some + # of code here to WebSocketRequestHandler class if it's better. + options.cgi_directories = [] + options.is_executable_method = None + if options.cgi_paths: + options.cgi_directories = options.cgi_paths.split(',') + if sys.platform in ('cygwin', 'win32'): + cygwin_path = None + # For Win32 Python, it is expected that CYGWIN_PATH + # is set to a directory of cygwin binaries. + # For example, websocket_server.py in Chromium sets CYGWIN_PATH to + # full path of third_party/cygwin/bin. + if 'CYGWIN_PATH' in os.environ: + cygwin_path = os.environ['CYGWIN_PATH'] + util.wrap_popen3_for_win(cygwin_path) + + def __check_script(scriptpath): + return util.get_script_interp(scriptpath, cygwin_path) + + options.is_executable_method = __check_script + + if options.use_tls: + if not (_HAS_SSL or _HAS_OPEN_SSL): + logging.critical('TLS support requires ssl or pyOpenSSL module.') + sys.exit(1) + if not options.private_key or not options.certificate: + logging.critical( + 'To use TLS, specify private_key and certificate.') + sys.exit(1) + + if options.tls_client_auth: + if not options.use_tls: + logging.critical('TLS must be enabled for client authentication.') + sys.exit(1) + if not _HAS_SSL: + logging.critical('Client authentication requires ssl module.') + + if not options.scan_dir: + options.scan_dir = options.websock_handlers + + if options.use_basic_auth: + options.basic_auth_credential = 'Basic ' + base64.b64encode( + options.basic_auth_credential) + + try: + if options.thread_monitor_interval_in_sec > 0: + # Run a thread monitor to show the status of server threads for + # debugging. + ThreadMonitor(options.thread_monitor_interval_in_sec).start() + + server = WebSocketServer(options) + server.serve_forever() + except Exception, e: + logging.critical('mod_pywebsocket: %s' % e) + logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) + sys.exit(1) + + +if __name__ == '__main__': + _main(sys.argv[1:]) + + +# vi:sts=4 sw=4 et diff --git a/module/remote/thriftbackend/__init__.py b/pyload/remote/wsbackend/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/remote/thriftbackend/__init__.py +++ b/pyload/remote/wsbackend/__init__.py diff --git a/pyload/setup/Setup.py b/pyload/setup/Setup.py new file mode 100644 index 000000000..c61a389e2 --- /dev/null +++ b/pyload/setup/Setup.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 os +import sys +import socket +import webbrowser + +from getpass import getpass +from time import time +from sys import exit + +from pyload.utils import pylgettext as gettext +from pyload.utils.fs import abspath, dirname, exists, join, makedirs +from pyload.utils import get_console_encoding +from pyload.web.ServerThread import WebServer + +from system import get_system_info +from dependencies import deps + +class Setup(): + """ + pyLoads initial setup configuration assistant + """ + + @staticmethod + def check_system(): + return get_system_info() + + + @staticmethod + def check_deps(): + result = { + "core": [], + "opt": [] + } + + for d in deps: + avail, v = d.check() + check = { + "name": d.name, + "avail": avail, + "v": v + } + if d.optional: + result["opt"].append(check) + else: + result["core"].append(check) + + return result + + + def __init__(self, path, config): + self.path = path + self.config = config + self.stdin_encoding = get_console_encoding(sys.stdin.encoding) + self.lang = None + self.db = None + + # We will create a timestamp so that the setup will be completed in a specific interval + self.timestamp = time() + + # TODO: probably unneeded + self.yes = "yes" + self.no = "no" + + def start(self): + import __builtin__ + # set the gettext translation + __builtin__._ = lambda x: x + + web = WebServer(pysetup=self) + web.start() + + error = web.check_error() + + # TODO: start cli in this case + if error: #todo errno 44 port already in use + print error + + url = "http://%s:%d/" % (socket.gethostbyname(socket.gethostname()), web.port) + + print "Setup is running at %s" % url + + opened = webbrowser.open_new_tab(url) + if not opened: + print "Please point your browser to the url above." + + cli = self.ask("Use commandline for configuration instead?", self.no, bool=True) + if cli: + print "Not implemented yet!" + print "Use web configuration or config files" + + raw_input() + + return True + + + def start_cli(self): + + self.ask_lang() + + print _("Welcome to the pyLoad Configuration Assistent.") + print _("It will check your system and make a basic setup in order to run pyLoad.") + print "" + print _("The value in brackets [] always is the default value,") + print _("in case you don't want to change it or you are unsure what to choose, just hit enter.") + print _( + "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore.") + print _("If you have any problems with this assistent hit CTRL+C,") + print _("to abort and don't let him start with pyLoadCore automatically anymore.") + print "" + print _("When you are ready for system check, hit enter.") + raw_input() + + + # TODO: new system check + deps + + con = self.ask(_("Continue with setup?"), self.yes, bool=True) + + if not con: + return False + + print "" + print _("Do you want to change the config path? Current is %s") % abspath("") + print _( + "If you use pyLoad on a server or the home partition lives on an 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() + #calls exit when changed + + print "" + print _("Do you want to configure login data and basic settings?") + print _("This is recommend for first run.") + con = self.ask(_("Make basic setup?"), self.yes, bool=True) + + if con: + self.conf_basic() + + if ssl: + print "" + print _("Do you want to configure ssl?") + ssl = self.ask(_("Configure ssl?"), self.no, bool=True) + if ssl: + self.conf_ssl() + + print "" + print _("Do you want to configure webinterface?") + web = self.ask(_("Configure webinterface?"), self.yes, bool=True) + if web: + self.conf_web() + + print "" + print _("Setup finished successfully.") + print _("Hit enter to exit and restart pyLoad") + raw_input() + return True + + + def conf_basic(self): + print "" + print _("## Basic Setup ##") + + print "" + print _("The following logindata is valid for CLI, GUI and webinterface.") + + from pyload.database import DatabaseBackend + + db = DatabaseBackend(None) + db.setup() + username = self.ask(_("Username"), "User") + password = self.ask("", "", password=True) + db.addUser(username, password) + db.shutdown() + + print "" + langs = self.config.getMetaData("general", "language") + self.config["general"]["language"] = self.ask(_("Language"), "en", langs.type.split(";")) + + self.config["general"]["download_folder"] = self.ask(_("Download folder"), "Downloads") + self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3") + + reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True) + self.config["reconnect"]["activated"] = reconnect + if reconnect: + self.config["reconnect"]["method"] = self.ask(_("Reconnect script location"), "./reconnect.sh") + + + def conf_web(self): + print "" + print _("## Webinterface Setup ##") + + print "" + self.config["webinterface"]["activated"] = self.ask(_("Activate webinterface?"), self.yes, bool=True) + print "" + print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.") + self.config["webinterface"]["host"] = self.ask(_("Address"), "0.0.0.0") + self.config["webinterface"]["port"] = self.ask(_("Port"), "8000") + print "" + print _("pyLoad offers several server backends, now following a short explanation.") + print "threaded:", _("Default server, 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 knowledge.") + print "\t", _("Get it from here: https://github.com/jonashaag/bjoern, compile it") + print "\t", _("and copy bjoern.so to pyload/lib") + + print + print _( + "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface") + print _("come back here and change the builtin server to the threaded one here.") + + self.config["webinterface"]["server"] = self.ask(_("Server"), "threaded", + ["builtin", "threaded", "fastcgi", "lightweight"]) + + def conf_ssl(self): + print "" + print _("## SSL Setup ##") + print "" + print _("Execute these commands from pyLoad config folder to make ssl certificates:") + print "" + print "openssl genrsa -out ssl.key 1024" + print "openssl req -new -key ssl.key -out ssl.csr" + print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt " + print "" + print _("If you're done and everything went fine, you can activate ssl now.") + self.config["ssl"]["activated"] = self.ask(_("Activate SSL?"), self.yes, bool=True) + + def set_user(self): + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("setup", join(self.path, "locale"), + languages=[self.config["general"]["language"], "en"], fallback=True) + translation.install(True) + + self.openDB() + + try: + while True: + print _("Select action") + print _("1 - Create/Edit user") + print _("2 - List users") + print _("3 - Remove user") + print _("4 - Quit") + action = raw_input("[1]/2/3/4: ") + if not action in ("1", "2", "3", "4"): + continue + elif action == "1": + print "" + username = self.ask(_("Username"), "User") + password = self.ask("", "", password=True) + self.db.addUser(username, password) + elif action == "2": + print "" + print _("Users") + print "-----" + users = self.db.getAllUserData() + for user in users.itervalues(): + print user.name + print "-----" + print "" + elif action == "3": + print "" + username = self.ask(_("Username"), "") + if username: + self.db.removeUserByName(username) + elif action == "4": + self.db.syncSave() + break + finally: + self.closeDB() + + def addUser(self, username, password): + self.openDB() + try: + self.db.addUser(username, password) + finally: + self.closeDB() + + def openDB(self): + from pyload.database import DatabaseBackend + + if self.db is None: + self.db = DatabaseBackend(None) + self.db.setup() + + def closeDB(self): + if self.db is not None: + self.db.syncSave() + self.db.shutdown() + + def save(self): + self.config.save() + self.closeDB() + + def conf_path(self, trans=False): + if trans: + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("setup", join(self.path, "locale"), + languages=[self.config["general"]["language"], "en"], fallback=True) + translation.install(True) + + print _("Setting new configpath, current configuration will not be transferred!") + path = self.ask(_("Config path"), abspath("")) + try: + path = join(pypath, path) + if not exists(path): + makedirs(path) + f = open(join(pypath, "pyload", "config", "configdir"), "wb") + f.write(path) + f.close() + print _("Config path changed, setup will now close, please restart to go on.") + print _("Press Enter to exit.") + raw_input() + exit() + except Exception, e: + print _("Setting config path failed: %s") % str(e) + + + def ask_lang(self): + langs = self.config.getMetaData("general", "language").type.split(";") + self.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=[self.lang, "en"], fallback=True) + translation.install(True) + + #l10n Input shorthand for yes + self.yes = _("y") + #l10n Input shorthand for no + self.no = _("n") + + def ask(self, qst, default, answers=[], bool=False, password=False): + """ Generate dialog on command line """ + + if answers: + info = "(" + for i, answer in enumerate(answers): + info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer) + + info += ")" + elif bool: + if default == self.yes: + info = "([%s]/%s)" % (self.yes, self.no) + else: + info = "(%s/[%s])" % (self.yes, self.no) + else: + info = "[%s]" % default + + if password: + p1 = True + p2 = False + while p1 != p2: + # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS) + sys.stdout.write(_("Password: ")) + p1 = getpass("") + + if len(p1) < 4: + print _("Password too short. Use at least 4 symbols.") + continue + + sys.stdout.write(_("Password (again): ")) + p2 = getpass("") + + if p1 == p2: + return p1 + else: + print _("Passwords did not match.") + + while True: + input = raw_input(qst + " %s: " % info) + input = input.decode(self.stdin_encoding) + + if input.strip() == "": + input = default + + if bool: + #l10n yes, true,t are inputs for booleans with value true + if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]: + return True + #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: + print _("Invalid Input") + continue + + if not answers: + return input + + else: + if input in answers: + return input + else: + print _("Invalid Input") + + +if __name__ == "__main__": + test = Setup(join(abspath(dirname(__file__)), ".."), None) + test.start() diff --git a/module/remote/thriftbackend/thriftgen/__init__.py b/pyload/setup/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/remote/thriftbackend/thriftgen/__init__.py +++ b/pyload/setup/__init__.py diff --git a/pyload/setup/dependencies.py b/pyload/setup/dependencies.py new file mode 100644 index 000000000..f7a0e4ae7 --- /dev/null +++ b/pyload/setup/dependencies.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +import inspect + +# Provide gettext marker +_ = lambda x: x + + +def find_module(name): + from imp import find_module + + try: + f, pathname, desc = find_module(name) + if f is not None: + f.close() + return True + except: + return False + + +class Dependency(object): + name = None + optional = True + desc = None + + @classmethod + def check(cls): + """ Returns (availability, version) as tuple """ + inst = cls() + avail = inst.isStatisfied() + v = None + if avail: + v = inst.getVersion() + + return avail, v + + def isStatisfied(self): + raise NotImplementedError + + def getVersion(self): + return None + + +class Python(Dependency): + name = "Python" + optional = False + + def isStatisfied(self): + # obviously + return True + + def getVersion(self): + import sys + + return ".".join(str(v) for v in sys.version_info[:3]) + + +class JSON(Dependency): + name = "json" + optional = False + + def isStatisfied(self): + return find_module("json") or find_module("simplejson") + + +class PyCurl(Dependency): + name = "pycurl" + optional = False + + def isStatisfied(self): + return find_module("pycurl") + + +class Sqlite(Dependency): + name = "sqlite" + optional = False + + def isStatisfied(self): + return find_module("sqlite3") or find_module("pysqlite2") + +# TODO: ssl, crypto, image, tesseract, js + +deps = [Python, Sqlite, PyCurl, JSON]
\ No newline at end of file diff --git a/pyload/setup/system.py b/pyload/setup/system.py new file mode 100644 index 000000000..dab6d1d17 --- /dev/null +++ b/pyload/setup/system.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +import sys +import os + +from new_collections import OrderedDict + +# gettext decorator, translated only when needed +_ = lambda x: x + +# platform usually don't change at runtime +info = None + + +def get_system_info(): + """ Returns system information as dict """ + global info + + if info is None: + import platform + + info = OrderedDict([ + (_("Platform"), platform.platform()), + (_("Version"), sys.version), + (_("Path"), os.path.abspath("")), + (_("Encoding"), sys.getdefaultencoding()), + (_("FS-Encoding"), sys.getfilesystemencoding()) + ]) + + return info
\ No newline at end of file diff --git a/pyload/threads/AddonThread.py b/pyload/threads/AddonThread.py new file mode 100644 index 000000000..afb56f66b --- /dev/null +++ b/pyload/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 occurred"), 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/pyload/threads/BaseThread.py b/pyload/threads/BaseThread.py new file mode 100644 index 000000000..3655480dd --- /dev/null +++ b/pyload/threads/BaseThread.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +from threading import Thread +from time import strftime, gmtime +from types import MethodType +from pprint import pformat +from traceback import format_exc + +from pyload.utils import primary_uid +from pyload.utils.fs import listdir, join, save_join, stat, exists +from pyload.setup.system import get_system_info + + +class BaseThread(Thread): + """abstract base class for thread types""" + + def __init__(self, manager, ower=None): + Thread.__init__(self) + self.setDaemon(True) + self.m = manager #thread manager + self.core = manager.core + self.log = manager.core.log + + #: Owner of the thread, every type should set it or overwrite user + self.owner = None + + @property + def user(self): + return primary_uid(self.owner) + + def getProgress(self): + """ retrieves progress information about the current running task + + :return: :class:`ProgressInfo` + """ + + # Debug Stuff + 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.getPluginDump(pyfile.plugin) + "\n" + 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 getPluginDump(self, plugin): + dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % ( + self.m.core.api.getServerVersion(), plugin.__name__, plugin.__version__, format_exc()) + + tb = sys.exc_info()[2] + stack = [] + while tb: + stack.append(tb.tb_frame) + tb = tb.tb_next + + for frame in stack[1:]: + dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name, + frame.f_code.co_filename, + frame.f_lineno) + + for key, value in frame.f_locals.items(): + dump += "\t%20s = " % key + try: + dump += 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(plugin): + attr = getattr(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" + + return dump + + def getFileDump(self, pyfile): + dump = "PYFILE 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" + + return dump + + def getSystemDump(self): + dump = "SYSTEM:\n\n" + for k,v in get_system_info().iteritems(): + dump += "%s: %s\n" % (k, v) + + return dump diff --git a/pyload/threads/DecrypterThread.py b/pyload/threads/DecrypterThread.py new file mode 100644 index 000000000..22a2d0037 --- /dev/null +++ b/pyload/threads/DecrypterThread.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from time import sleep + +from pyload.Api import LinkStatus, DownloadStatus as DS +from pyload.utils import uniqify, accumulate +from pyload.plugins.Base import Abort, Retry +from pyload.plugins.Crypter import Package + +from BaseThread import BaseThread + +class DecrypterThread(BaseThread): + """thread for decrypting""" + + def __init__(self, manager, data, pid): + # TODO: owner + BaseThread.__init__(self, manager) + # [... (plugin, url) ...] + self.data = data + self.pid = pid + + self.start() + + def run(self): + pack = self.m.core.files.getPackage(self.pid) + links, packages = self.decrypt(accumulate(self.data), pack.password) + + if links: + self.log.info(_("Decrypted %(count)d links into package %(name)s") % {"count": len(links), "name": pack.name}) + self.m.core.api.addFiles(self.pid, [l.url for l in links]) + + # TODO: add single package into this one and rename it? + # TODO: nested packages + for p in packages: + self.m.core.api.addPackage(p.name, p.getURLs(), pack.password) + + def decrypt(self, plugin_map, password=None, err=None): + result = [] + + # TODO QUEUE_DECRYPT + + for name, urls in plugin_map.iteritems(): + klass = self.m.core.pluginManager.loadClass("crypter", name) + plugin = klass(self.m.core, password) + plugin_result = [] + + try: + try: + plugin_result = plugin._decrypt(urls) + except Retry: + sleep(1) + plugin_result = plugin._decrypt(urls) + except Abort: + plugin.logInfo(_("Decrypting aborted")) + except Exception, e: + plugin.logError(_("Decrypting failed"), e) + + # generate error linkStatus + if err: + plugin_result.extend(LinkStatus(url, url, -1, DS.Failed, name) for url in urls) + + if self.core.debug: + self.core.print_exc() + self.writeDebugReport(plugin.__name__, plugin=plugin) + finally: + plugin.clean() + + plugin.logDebug("Decrypted", plugin_result) + result.extend(plugin_result) + + # generated packages + pack_names = {} + # urls without package + urls = [] + + # merge urls and packages + for p in result: + if isinstance(p, Package): + if p.name in pack_names: + pack_names[p.name].urls.extend(p.urls) + else: + if not p.name: + urls.extend(p.links) + else: + pack_names[p.name] = p + else: + urls.append(p) + + urls = uniqify(urls) + + return urls, pack_names.values()
\ No newline at end of file diff --git a/pyload/threads/DownloadThread.py b/pyload/threads/DownloadThread.py new file mode 100644 index 000000000..b5a45185f --- /dev/null +++ b/pyload/threads/DownloadThread.py @@ -0,0 +1,231 @@ +#!/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 pyload.plugins.Base import Fail, Retry, Abort +from pyload.plugins.Hoster import Reconnect, SkipDownload +from pyload.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 = None + + 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 = None + 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 getProgress(self): + if self.active: + return self.active.getProgressInfo() + + + def put(self, job): + """assign a job to the thread""" + self.queue.put(job) + + def clean(self, pyfile): + """ set thread inactive and release pyfile """ + self.active = False + pyfile.release() + + def stop(self): + """stops the thread""" + self.put("quit") diff --git a/pyload/threads/InfoThread.py b/pyload/threads/InfoThread.py new file mode 100644 index 000000000..f39ac41f2 --- /dev/null +++ b/pyload/threads/InfoThread.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from time import time +from traceback import print_exc + +from pyload.Api import LinkStatus, DownloadStatus +from pyload.utils.packagetools import parseNames +from pyload.utils import has_method, accumulate + +from BaseThread import BaseThread +from DecrypterThread import DecrypterThread + + +class InfoThread(DecrypterThread): + def __init__(self, manager, owner, data, pid=-1, oc=None): + BaseThread.__init__(self, manager, owner) + + # [... (plugin, url) ...] + self.data = data + self.pid = pid + self.oc = oc # online check + # urls that already have a package name + self.names = {} + + self.start() + + def run(self): + 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] + + if crypter: + # decrypt them + links, packages = self.decrypt(crypter, err=True) + # push these as initial result and save package names + self.updateResult(links) + for pack in packages: + for url in pack.getURLs(): + self.names[url] = pack.name + + links.extend(pack.links) + self.updateResult(pack.links) + + # TODO: no plugin information pushed to GUI + # parse links and merge + hoster, crypter = self.m.core.pluginManager.parseUrls([l.url for l in links]) + accumulate(hoster + crypter, plugins) + + # db or info result + cb = self.updateDB if self.pid > 1 else self.updateResult + + for pluginname, urls in plugins.iteritems(): + plugin = self.m.core.pluginManager.loadModule("hoster", pluginname) + klass = self.m.core.pluginManager.getPluginClass(pluginname) + if has_method(klass, "getInfo"): + self.fetchForPlugin(klass, urls, cb) + # TODO: this branch can be removed in the future + elif has_method(plugin, "getInfo"): + self.log.debug("Deprecated .getInfo() method on module level, use staticmethod instead") + self.fetchForPlugin(plugin, urls, cb) + + if self.oc: + self.oc.done = True + + self.names.clear() + self.m.timestamp = time() + 5 * 60 + + def updateDB(self, result): + # writes results to db + # convert link info to tuples + info = [(l.name, l.size, l.status, l.url) for l in result if not l.hash] + info_hash = [(l.name, l.size, l.status, l.hash, l.url) for l in result if l.hash] + if info: + self.m.core.files.updateFileInfo(info, self.pid) + if info_hash: + self.m.core.files.updateFileInfo(info_hash, self.pid) + + def updateResult(self, result): + tmp = {} + parse = [] + # separate these with name and without + for link in result: + if link.url in self.names: + tmp[link] = self.names[link.url] + else: + parse.append(link) + + data = parseNames([(link.name, link) for link in parse]) + # merge in packages that already have a name + data = accumulate(tmp.iteritems(), data) + + self.m.setInfoResults(self.oc, data) + + def fetchForPlugin(self, plugin, urls, cb): + """executes info fetching for given plugin and urls""" + # also works on module names + pluginname = plugin.__name__.split(".")[-1] + try: + cached = [] #results loaded from cache + process = [] #urls to process + for url in urls: + if url in self.m.infoCache: + cached.append(self.m.infoCache[url]) + else: + process.append(url) + + if cached: + self.m.log.debug("Fetched %d links from cache for %s" % (len(cached), pluginname)) + cb(cached) + + 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] + + links = [] + # Convert results to link statuses + for res in result: + if isinstance(res, LinkStatus): + links.append(res) + elif type(res) == tuple and len(res) == 4: + links.append(LinkStatus(res[3], res[0], int(res[1]), res[2], pluginname)) + elif type(res) == tuple and len(res) == 5: + links.append(LinkStatus(res[3], res[0], int(res[1]), res[2], pluginname, res[4])) + else: + self.m.log.debug("Invalid getInfo result: " + result) + + # put them on the cache + for link in links: + self.m.infoCache[link.url] = link + + cb(links) + + 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)}) + self.core.print_exc()
\ No newline at end of file diff --git a/pyload/threads/ThreadManager.py b/pyload/threads/ThreadManager.py new file mode 100644 index 000000000..55cfcbfd2 --- /dev/null +++ b/pyload/threads/ThreadManager.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 +from subprocess import Popen +from threading import Event, Lock +from time import sleep, time +from traceback import print_exc +from random import choice + +from pyload.datatypes.PyFile import PyFile +from pyload.datatypes.OnlineCheck import OnlineCheck +from pyload.network.RequestFactory import getURL +from pyload.utils import lock, uniqify +from pyload.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""" + + + def __init__(self, core): + """Constructor""" + self.core = core + self.log = core.log + + self.threads = [] # thread list + self.localThreads = [] #addon+decrypter threads + + self.pause = True + + self.reconnecting = Event() + self.reconnecting.clear() + self.downloaded = 0 #number of files downloaded since last cleanup + + self.lock = Lock() + + # some operations require to fetch url info from hoster, so we caching them so it wont be done twice + # contains a timestamp and will be purged after timeout + self.infoCache = {} + + # pool of ids for online check + self.resultIDs = 0 + + # saved online checks + self.infoResults = {} + + # timeout for cache purge + self.timestamp = 0 + + for i in range(self.core.config.get("download", "max_downloads")): + self.createThread() + + + def createThread(self): + """create a download thread""" + + thread = DownloadThread(self) + self.threads.append(thread) + + def createInfoThread(self, data, pid): + """ start a thread which fetches online status and other info's """ + self.timestamp = time() + 5 * 60 + if data: InfoThread(self, None, data, pid) + + @lock + def createResultThread(self, user, data): + """ creates a thread to fetch online status, returns result id """ + self.timestamp = time() + 5 * 60 + + rid = self.resultIDs + self.resultIDs += 1 + + oc = OnlineCheck(rid, user) + self.infoResults[rid] = oc + + InfoThread(self, user, data, oc=oc) + + 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): + return self.infoResults.get(rid) + + def setInfoResults(self, oc, result): + self.core.evm.dispatchEvent("linkcheck:updated", oc.rid, result, owner=oc.owner) + oc.update(result) + + def getActiveDownloads(self, user=None): + # TODO: user context + return [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)] + + def getProgressList(self, user=None): + info = [] + + # TODO: local threads can create multiple progresses + for thread in self.threads + self.localThreads: + # skip if not belong to current user + if user and thread.user != user: continue + + progress = thread.getProgress() + if progress: info.append(progress) + + return info + + def getActiveFiles(self): + active = self.getActiveDownloads() + + for t in self.localThreads: + active.extend(t.getActiveFiles()) + + return active + + def processingIds(self): + """get a id list of all pyfiles processed""" + return [x.id for x in self.getActiveFiles()] + + def work(self): + """run all task which have to be done (this is for repetetive call by core)""" + try: + self.tryReconnect() + except Exception, e: + self.log.error(_("Reconnect Failed: %s") % str(e)) + self.reconnecting.clear() + self.core.print_exc() + + self.checkThreadCount() + + try: + self.assignJob() + except Exception, e: + self.log.warning("Assign job error", e) + self.core.print_exc() + + sleep(0.5) + self.assignJob() + #it may be failed non critical so we try it again + + if self.infoCache and self.timestamp < time(): + self.infoCache.clear() + self.log.debug("Cleared Result cache") + + for rid in self.infoResults.keys(): + if self.infoResults[rid].isStale(): + del self.infoResults[rid] + + def tryReconnect(self): + """checks if reconnect needed""" + + if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()): + return False + + active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active] + + if not (0 < active.count(True) == len(active)): + return False + + if not exists(self.core.config['reconnect']['method']): + if exists(join(pypath, self.core.config['reconnect']['method'])): + self.core.config['reconnect']['method'] = join(pypath, self.core.config['reconnect']['method']) + else: + self.core.config["reconnect"]["activated"] = False + self.log.warning(_("Reconnect script not found!")) + return + + self.reconnecting.set() + + #Do reconnect + self.log.info(_("Starting reconnect")) + + while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0: + sleep(0.25) + + ip = self.getIP() + + self.core.evm.dispatchEvent("reconnect:before", ip) + + self.log.debug("Old IP: %s" % ip) + + try: + reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE) + except: + self.log.warning(_("Failed executing reconnect script!")) + self.core.config["reconnect"]["activated"] = False + self.reconnecting.clear() + self.core.print_exc() + return + + reconn.wait() + sleep(1) + ip = self.getIP() + self.core.evm.dispatchEvent("reconnect:after", ip) + + self.log.info(_("Reconnected, new IP: %s") % ip) + + self.reconnecting.clear() + + def getIP(self): + """retrieve current ip""" + services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"), + ("http://checkip.dyndns.org/", ".*Current IP Address: (\S+)</body>.*")] + + ip = "" + for i in range(10): + try: + sv = choice(services) + ip = getURL(sv[0]) + ip = re.match(sv[1], ip).group(1) + break + except: + ip = "" + sleep(1) + + return ip + + def checkThreadCount(self): + """checks if there is a need for increasing or reducing thread count""" + + if len(self.threads) == self.core.config.get("download", "max_downloads"): + return True + elif len(self.threads) < self.core.config.get("download", "max_downloads"): + self.createThread() + else: + free = [x for x in self.threads if not x.active] + if free: + free[0].put("quit") + + + def cleanPycurl(self): + """ make a global curl cleanup (currently unused) """ + if self.processingIds(): + return False + import pycurl + + pycurl.global_cleanup() + pycurl.global_init(pycurl.GLOBAL_DEFAULT) + self.downloaded = 0 + self.log.debug("Cleaned up pycurl") + return True + + + def assignJob(self): + """assign a job to a thread if possible""" + + if self.pause or not self.core.api.isTimeDownload(): return + + #if self.downloaded > 20: + # if not self.cleanPyCurl(): return + + free = [x for x in self.threads if not x.active] + + inuse = [(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]]))) + + job = self.core.files.getJob(occ) + if job: + try: + job.initPlugin() + except Exception, e: + self.log.critical(str(e)) + print_exc() + job.setStatus("failed") + job.error = str(e) + job.release() + return + + 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: + #put job back + if occ not in self.core.files.jobCache: + self.core.files.jobCache[occ] = [] + self.core.files.jobCache[occ].append(job.id) diff --git a/module/web/__init__.py b/pyload/threads/__init__.py index e69de29bb..e69de29bb 100644 --- a/module/web/__init__.py +++ b/pyload/threads/__init__.py diff --git a/module/common/ImportDebugger.py b/pyload/utils/ImportDebugger.py index a997f7b0c..a997f7b0c 100644 --- a/module/common/ImportDebugger.py +++ b/pyload/utils/ImportDebugger.py diff --git a/pyload/utils/JsEngine.py b/pyload/utils/JsEngine.py new file mode 100644 index 000000000..3318ffb2a --- /dev/null +++ b/pyload/utils/JsEngine.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN +""" + +from imp import find_module +from os.path import join, exists +from urllib import quote + + +ENGINE = "" + +DEBUG = False +JS = False +PYV8 = False +NODE = False +RHINO = False + +# TODO: Refactor + clean up this class + +if not ENGINE: + try: + import subprocess + + subprocess.Popen(["js", "-v"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + p = subprocess.Popen(["js", "-e", "print(23+19)"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + #integrity check + if out.strip() == "42": + ENGINE = "js" + JS = True + except: + pass + +if not ENGINE or DEBUG: + try: + import PyV8 + ENGINE = "pyv8" + PYV8 = True + except: + pass + +if not ENGINE or DEBUG: + try: + import subprocess + subprocess.Popen(["node", "-v"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + p = subprocess.Popen(["node", "-e", "console.log(23+19)"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + #integrity check + if out.strip() == "42": + ENGINE = "node" + NODE = True + except: + pass + +if not ENGINE or DEBUG: + try: + path = "" #path where to find rhino + + if exists("/usr/share/java/js.jar"): + path = "/usr/share/java/js.jar" + elif exists("js.jar"): + path = "js.jar" + elif exists(join(pypath, "js.jar")): #may raises an exception, but js.jar wasnt found anyway + path = join(pypath, "js.jar") + + if not path: + raise Exception + + import subprocess + + p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", "print(23+19)"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + #integrity check + if out.strip() == "42": + ENGINE = "rhino" + RHINO = True + except: + pass + +class JsEngine(): + def __init__(self): + self.engine = ENGINE + self.init = False + + def __nonzero__(self): + return False if not ENGINE else True + + def set_debug(self, value): + global DEBUG + DEBUG = value + + def eval(self, script): + if not self.init: + if ENGINE == "pyv8" or (DEBUG and PYV8): + import PyV8 + global PyV8 + + self.init = True + + if type(script) == unicode: + script = script.encode("utf8") + + if not ENGINE: + raise Exception("No JS Engine") + + if not DEBUG: + if ENGINE == "pyv8": + return self.eval_pyv8(script) + elif ENGINE == "js": + return self.eval_js(script) + elif ENGINE == "node": + return self.eval_node(script) + elif ENGINE == "rhino": + return self.eval_rhino(script) + else: + results = [] + if PYV8: + res = self.eval_pyv8(script) + print "PyV8:", res + results.append(res) + if JS: + res = self.eval_js(script) + print "JS:", res + results.append(res) + if NODE: + res = self.eval_node(script) + print "NODE:", res + results.append(res) + if RHINO: + res = self.eval_rhino(script) + print "Rhino:", res + results.append(res) + + warning = False + for x in results: + for y in results: + if x != y: + warning = True + + if warning: print "### WARNING ###: Different results" + + return results[0] + + def eval_pyv8(self, script): + rt = PyV8.JSContext() + rt.enter() + return rt.eval(script) + + def eval_js(self, script): + script = "print(eval(unescape('%s')))" % quote(script) + p = subprocess.Popen(["js", "-e", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1) + out, err = p.communicate() + res = out.strip() + return res + + def eval_node(self, script): + script = "console.log(eval(unescape('%s')))" % quote(script) + p = subprocess.Popen(["node", "-e", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1) + out, err = p.communicate() + res = out.strip() + return res + + def eval_rhino(self, script): + script = "print(eval(unescape('%s')))" % quote(script) + p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", script], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1) + out, err = p.communicate() + res = out.strip() + return res.decode("utf8").encode("ISO-8859-1") + + def error(self): + return _("No js engine detected, please install either Spidermonkey, ossp-js, pyv8, nodejs or rhino") + +if __name__ == "__main__": + js = JsEngine() + js.set_debug(True) + + test = u'"ü"+"ä"' + js.eval(test)
\ No newline at end of file diff --git a/pyload/utils/PluginLoader.py b/pyload/utils/PluginLoader.py new file mode 100644 index 000000000..038ac9b23 --- /dev/null +++ b/pyload/utils/PluginLoader.py @@ -0,0 +1,333 @@ +############################################################################### +# Copyright(c) 2008-2013 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 import listdir, makedirs +from os.path import isfile, join, exists, basename +from sys import version_info +from time import time +from collections import defaultdict +from logging import getLogger + +from pyload.lib.SafeEval import const_eval as literal_eval +from pyload.plugins.Base import Base + +from new_collections import namedtuple + +PluginTuple = namedtuple("PluginTuple", "version re deps category user path") + + +class BaseAttributes(defaultdict): + """ Dictionary that loads defaults values from Base object """ + + def __missing__(self, key): + attr = "__%s__" % key + if not hasattr(Base, attr): + return defaultdict.__missing__(self, key) + + return getattr(Base, attr) + +class LoaderFactory: + """ Container for multiple plugin loaders """ + + def __init__(self, *loader): + self.loader = list(loader) + + def __iter__(self): + return self.loader.__iter__() + + + def checkVersions(self): + """ Reduces every plugin loader to the globally newest version. + Afterwards every plugin is unique across all available loader """ + for plugin_type in self.loader[0].iterTypes(): + for loader in self.loader: + # iterate all plugins + for plugin, info in loader.getPlugins(plugin_type).iteritems(): + # now iterate all other loaders + for l2 in self.loader: + if l2 is not loader: + l2.removePlugin(plugin_type, plugin, info.version) + + def getPlugin(self, plugin, name): + """ retrieve a plugin from an available loader """ + for loader in self.loader: + if loader.hasPlugin(plugin, name): + return loader.getPlugin(plugin, name) + + +class PluginLoader: + """ + Class to provide and load plugins from the file-system + """ + TYPES = ("crypter", "hoster", "accounts", "addons", "network", "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) + # finds the beginning of a expression that could span multiple lines + MULTI = re.compile(r'__(?P<attr>[a-z0-9_]+)__\s*=\s*(\(|\{|\[|"{3})',re.I) + + # closing symbols + MULTI_MATCH = { + "{": "}", + "(": ")", + "[": "]", + '"""': '"""' + } + + NO_MATCH = re.compile(r'^no_match$') + + def __init__(self, path, package, config): + self.path = path + self.package = package + self.config = config + self.log = getLogger("log") + self.plugins = {} + + self.createIndex() + + def logDebug(self, plugin, name, msg): + self.log.debug("Plugin %s | %s: %s" % (plugin, name, msg)) + + def createIndex(self): + """create information for all plugins available""" + + if not exists(self.path): + makedirs(self.path) + if not exists(join(self.path, "__init__.py")): + f = open(join(self.path, "__init__.py"), "wb") + f.close() + + a = time() + for plugin in self.TYPES: + self.plugins[plugin] = self.parse(plugin) + + self.log.debug("Created index of plugins for %s in %.2f ms", self.path, (time() - a) * 1000) + + def parse(self, folder): + """ Analyze and parses all plugins in folder """ + plugins = {} + pfolder = join(self.path, folder) + if not exists(pfolder): + makedirs(pfolder) + if not exists(join(pfolder, "__init__.py")): + f = open(join(pfolder, "__init__.py"), "wb") + f.close() + + 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) + if plugin: + plugins[name] = plugin + + return plugins + + def parseAttributes(self, filename, name, folder=""): + """ Parse attribute dict from plugin""" + data = open(filename, "rb") + content = data.read() + data.close() + + attrs = BaseAttributes() + for m in self.BUILTIN.findall(content) + self.SINGLE.findall(content) + self.parseMultiLine(content): + #replace gettext function and eval result + try: + attrs[m[0]] = literal_eval(m[-1].replace("_(", "(")) + except Exception, e: + self.logDebug(folder, name, "Error when parsing: %s" % m[-1]) + self.log.debug(str(e)) + + 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 parseMultiLine(self, content): + # regexp is not enough to parse multi line statements + attrs = [] + for m in self.MULTI.finditer(content): + attr = m.group(1) + char = m.group(2) + # the end char to search for + endchar = self.MULTI_MATCH[char] + size = len(endchar) + # save number of of occurred + stack = 0 + endpos = m.start(2) - size + for i in xrange(m.end(2), len(content) - size + 1): + if content[i:i+size] == endchar: + # closing char seen and match now complete + if stack == 0: + endpos = i + break + else: + stack -= 1 + elif content[i:i+size] == char: + stack += 1 + + # in case the end was not found match will be empty + attrs.append((attr, content[m.start(2): endpos + size])) + + return attrs + + + def parsePlugin(self, filename, folder, name): + """ 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") + + if "pattern" in attrs and attrs["pattern"]: + try: + plugin_re = re.compile(attrs["pattern"], re.I) + except: + self.logDebug(folder, name, "Invalid regexp pattern '%s'" % attrs["pattern"]) + plugin_re = self.NO_MATCH + else: + plugin_re = self.NO_MATCH + + deps = attrs["dependencies"] + category = attrs["category"] if folder == "addons" else "" + + # create plugin tuple + # user_context=True is the default for non addons plugins + plugin = PluginTuple(version, plugin_re, deps, category, + bool(folder != "addons" or attrs["user_context"]), filename) + + # These have none or their own config + if folder in ("internal", "accounts", "network"): + return plugin + + if folder == "addons" and "config" not in attrs and not attrs["internal"]: + attrs["config"] = (["activated", "bool", "Activated", False],) + + if "config" in attrs and attrs["config"] is not None: + config = attrs["config"] + desc = attrs["description"] + expl = attrs["explanation"] + + # Convert tuples to list + config = [list(x) for x in config] + + if folder == "addons" and not attrs["internal"]: + for item in config: + if item[0] == "activated": break + else: # activated flag missing + config.insert(0, ("activated", "bool", "Activated", False)) + + try: + self.config.addConfigSection(name, name, desc, expl, config) + except: + self.logDebug(folder, name, "Invalid config %s" % config) + + return plugin + + def iterPlugins(self): + """ Iterates over all plugins returning (type, name, info) with info as PluginTuple """ + + for plugin, data in self.plugins.iteritems(): + for name, info in data.iteritems(): + yield plugin, name, info + + def iterTypes(self): + """ Iterate over the available plugin types """ + + for plugin in self.plugins.iterkeys(): + yield plugin + + def hasPlugin(self, plugin, name): + """ Check if certain plugin is available """ + return plugin in self.plugins and name in self.plugins[plugin] + + def getPlugin(self, plugin, name): + """ Return plugin info for a single entity """ + try: + return self.plugins[plugin][name] + except KeyError: + return None + + def getPlugins(self, plugin): + """ Return all plugins of given plugin type """ + return self.plugins[plugin] + + def removePlugin(self, plugin, name, available_version=None): + """ Removes a plugin from the index. + Optionally only when its version is below or equal the available one + """ + try: + if available_version is not None: + if self.plugins[plugin][name] <= available_version: + del self.plugins[plugin][name] + else: + del self.plugins[plugin][name] + + # no errors are thrown if the plugin didn't existed + except KeyError: + return + + def isUserPlugin(self, name): + """ Determine if given plugin name is enable for user_context in any plugin type """ + for plugins in self.plugins: + if name in plugins and name[plugins].user: + return True + + return False + + def savePlugin(self, content): + """ Saves a plugin to disk """ + + def loadModule(self, plugin, name): + """ Returns loaded module for plugin + + :param plugin: plugin type, subfolder of module.plugins + :raises Exception: Everything could go wrong, failures needs to be catched + """ + plugins = self.plugins[plugin] + # convert path to python recognizable import + path = basename(plugins[name].path).replace(".pyc", "").replace(".py", "") + module = __import__(self.package + ".%s.%s" % (plugin, path), globals(), locals(), path) + return module + + def loadAttributes(self, plugin, name): + """ Same as `parseAttributes` for already indexed plugins """ + return self.parseAttributes(self.plugins[plugin][name].path, name, plugin)
\ No newline at end of file diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py new file mode 100644 index 000000000..577213dd1 --- /dev/null +++ b/pyload/utils/__init__.py @@ -0,0 +1,244 @@ +# -*- 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 + +# abstraction layer for json operations +try: # since python 2.6 + import json +except ImportError: #use system simplejson if available + import simplejson as json + +json_loads = json.loads +json_dumps = json.dumps + +def decode(string): + """ decode string to unicode with utf8 """ + if type(string) == str: + return string.decode("utf8", "replace") + else: + return string + +def encode(string): + """ decode string to utf8 """ + if type(string) == unicode: + return string.encode("utf8", "replace") + else: + return string + + +def remove_chars(string, repl): + """ removes all chars in repl from string""" + if type(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 parse_time(timestamp, pattern): + """ Parse a string representing a time according to a pattern and + return a time in seconds suitable for an account plugin. """ + return int(time.mktime(time.strptime(timestamp, pattern))) + +def parseFileSize(string, unit=None): + print "Deprecated parseFileSize, use parse_size" + return parse_size(string, unit) + +def parse_size(string, unit=None): + """ Parses file size from a string. Tries to parse unit if not given. + + :return: size in bytes + """ + if not unit: + m = re.match(r"([\d.,]+) *([a-zA-Z]*)", string.strip().lower()) + if m: + traffic = float(m.group(1).replace(",", ".")) + unit = m.group(2) + else: + return 0 + else: + if isinstance(string, basestring): + traffic = float(string.replace(",", ".")) + else: + traffic = string + + #ignore case + unit = unit.lower().strip() + + if unit in ("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 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 lock(func): + def new(*args, **kwargs): + #print "Handler: %s args: %s" % (func,args[1:]) + args[0].lock.acquire() + try: + return func(*args, **kwargs) + finally: + args[0].lock.release() + + return new + +def read_lock(func): + def new(*args, **kwargs): + args[0].lock.acquire(shared=True) + try: + return func(*args, **kwargs) + 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 hasattr(obj, '__dict__') and name in obj.__dict__ + +def accumulate(it, inv_map=None): + """ accumulate (key, value) data to {value : [keylist]} dictionary """ + if inv_map is None: + 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_bool(value): + if not isinstance(value, basestring): return True if value else False + return True if value.lower() in ("1", "true", "on", "an", "yes") else False + +def to_int(string, default=0): + """ return int from string or default """ + try: + return int(string) + except ValueError: + return default + +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 primary_uid(user): + """ Gets primary user id for user instances or ints """ + if type(user) == int: return user + return user.primary if user else None + +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/pyload/utils/filetypes.py b/pyload/utils/filetypes.py new file mode 100644 index 000000000..ce5c8a0c5 --- /dev/null +++ b/pyload/utils/filetypes.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.Api import MediaType + +filetypes = { + MediaType.Audio: re.compile("\.(m3u|m4a|mp3|wav|wma|aac?|flac|midi|m4b)$", re.I), + MediaType.Image: re.compile("\.(jpe?g|bmp|png|gif|ico|tiff?|svg|psd)$", re.I), + MediaType.Video: re.compile("\.(3gp|flv|m4v|avi|mp4|mov|swf|vob|wmv|divx|mpe?g|rm|mkv)$", re.I), + MediaType.Document: re.compile("\.(epub|mobi|acsm|azw[0-9]|pdf|txt|md|abw|docx?|tex|odt|rtf||log)$", re.I), + MediaType.Archive: re.compile("\.(rar|r[0-9]+|7z|7z.[0-9]+|zip|gz|bzip2?|tar|lzma)$", re.I), + MediaType.Executable: re.compile("\.(jar|exe|dmg|sh|apk)$", re.I), +} + + +def guess_type(name): + for mt, regex in filetypes.iteritems(): + if regex.search(name) is not None: + return mt + + return MediaType.Other + + + diff --git a/pyload/utils/fs.py b/pyload/utils/fs.py new file mode 100644 index 000000000..05e098e2a --- /dev/null +++ b/pyload/utils/fs.py @@ -0,0 +1,90 @@ +# -*- 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') + else: + 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 dirname(path): + return fs_decode(os.path.dirname(fs_encode(path))) + +def abspath(path): + return fs_decode(os.path.abspath(fs_encode(path))) + +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) + +def listdir(path): + return [fs_decode(x) for x in 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: + s = os.statvfs(folder) + return s.f_frsize * s.f_bavail + +def get_bsize(path): + """ get optimal file system buffer size (in bytes) for i/o calls """ + path = fs_encode(path) + + if os.name == "nt": + import ctypes + + drive = "%s\\" % os.path.splitdrive(path)[0] + cluster_sectors, sector_size = ctypes.c_longlong(0) + ctypes.windll.kernel32.GetDiskFreeSpaceW(ctypes.c_wchar_p(drive), ctypes.pointer(cluster_sectors), ctypes.pointer(sector_size), None, None) + return cluster_sectors * sector_size + else: + return os.statvfs(path).f_bsize diff --git a/pyload/utils/json_layer.py b/pyload/utils/json_layer.py new file mode 100644 index 000000000..cf9743603 --- /dev/null +++ b/pyload/utils/json_layer.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# abstraction layer for json operations + +print ".json_layer is deprecated, use .json instead" + +try: # since python 2.6 + import json + from json import loads as json_loads + from json import dumps as json_dumps +except ImportError: #use system simplejson if available + import simplejson as json + from simplejson import loads as json_loads + from simplejson import dumps as json_dumps diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py new file mode 100644 index 000000000..02dfa4739 --- /dev/null +++ b/pyload/utils/packagetools.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python + +# JDownloader/src/jd/controlling/LinkGrabberPackager.java + +import re +from urlparse import urlparse + +endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)" + +rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I), + re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I), + re.compile("(.*)\\.rar$", re.I), + re.compile("(.*)\\.r\\d+$", re.I), + re.compile("(.*)(\\.|_|-)\\d+$", re.I)] + +zipPats = [re.compile("(.*)\\.zip$", re.I), + re.compile("(.*)\\.z\\d+$", re.I), + re.compile("(?is).*\\.7z\\.[\\d]+$", re.I), + re.compile("(.*)\\.a.$", re.I)] + +ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"), + re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)] + +iszPats = [re.compile("(.*)\\.isz$", re.I), + re.compile("(.*)\\.i\\d{2}$", re.I)] + +pat1 = re.compile("(\\.?CD\\d+)", re.I) +pat2 = re.compile("(\\.?part\\d+)", re.I) + +pat3 = re.compile("(.+)[\\.\\-_]+$") +pat4 = re.compile("(.+)\\.\\d+\\.xtm$") + +def matchFirst(string, *args): + """ matches against list of regexp and returns first match""" + for patternlist in args: + for pattern in patternlist: + r = pattern.search(string) + if r is not None: + name = r.group(1) + return name + + return string + + +def parseNames(files): + """ Generates packages names from name, data lists + + :param files: list of (name, data) + :return: packagenames mapped to data lists (eg. urls) + """ + packs = {} + + for file, url in files: + patternMatch = False + + if file is None: + continue + + # remove trailing / + name = file.rstrip('/') + + # extract last path part .. if there is a path + split = name.rsplit("/", 1) + if len(split) > 1: + name = split.pop(1) + + #check if an already existing package may be ok for this file + # found = False + # for pack in packs: + # if pack in file: + # packs[pack].append(url) + # found = True + # break + # + # if found: continue + + # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern + before = name + name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats) + if before != name: + patternMatch = True + + # xtremsplit pattern + r = pat4.search(name) + if r is not None: + name = r.group(1) + + # remove part and cd pattern + r = pat1.search(name) + if r is not None: + name = name.replace(r.group(0), "") + patternMatch = True + + r = pat2.search(name) + if r is not None: + name = name.replace(r.group(0), "") + patternMatch = True + + # additional checks if extension pattern matched + if patternMatch: + # remove extension + index = name.rfind(".") + if index <= 0: + index = name.rfind("_") + if index > 0: + length = len(name) - index + if length <= 4: + name = name[:-length] + + # remove endings like . _ - + r = pat3.search(name) + if r is not None: + name = r.group(1) + + # replace . and _ with space + name = name.replace(".", " ") + name = name.replace("_", " ") + + name = name.strip() + else: + name = "" + + # fallback: package by hoster + if not name: + name = urlparse(file).hostname + if name: name = name.replace("www.", "") + + # fallback : default name + if not name: + name = _("Unnamed package") + + # build mapping + if name in packs: + packs[name].append(url) + else: + packs[name] = [url] + + return packs + + +if __name__ == "__main__": + from os.path import join + from pprint import pprint + + f = open(join("..", "..", "testlinks2.txt"), "rb") + urls = [(x.strip(), x.strip()) for x in f.readlines() if x.strip()] + f.close() + + print "Having %d urls." % len(urls) + + packs = parseNames(urls) + + pprint(packs) + + print "Got %d urls." % sum([len(x) for x in packs.itervalues()]) diff --git a/module/common/pylgettext.py b/pyload/utils/pylgettext.py index fb36fecee..fb36fecee 100644 --- a/module/common/pylgettext.py +++ b/pyload/utils/pylgettext.py diff --git a/pyload/web/.bowerrc b/pyload/web/.bowerrc new file mode 100644 index 000000000..f594df7a7 --- /dev/null +++ b/pyload/web/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/components" +} diff --git a/pyload/web/Gruntfile.js b/pyload/web/Gruntfile.js new file mode 100644 index 000000000..0a97e7360 --- /dev/null +++ b/pyload/web/Gruntfile.js @@ -0,0 +1,438 @@ +'use strict'; +var LIVERELOAD_PORT = 35729; +var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT}); +var mountFolder = function(connect, dir) { + return connect.static(require('path').resolve(dir)); +}; +var fs = require('fs'); +var path = require('path'); + +// # Globbing +// for performance reasons we're only matching one level down: +// 'test/spec/{,*/}*.js' +// use this if you want to recursively match all subfolders: +// 'test/spec/**/*.js' + +module.exports = function(grunt) { + // load all grunt tasks + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); + + // configurable paths + var yeomanConfig = { + app: 'app', + dist: 'dist', + banner: '/* Copyright(c) 2008-2013 pyLoad Team */\n' + }; + + grunt.initConfig({ + yeoman: yeomanConfig, + watch: { + options: { + nospawn: true + }, + less: { + files: ['<%= yeoman.app %>/styles/**/*.less'], + tasks: ['less'] + }, + livereload: { + options: { + livereload: LIVERELOAD_PORT + }, + files: [ + '<%= yeoman.app %>/**/*.html', + '{<%= yeoman.app %>}/styles/**/*.css', + '{.tmp,<%= yeoman.app %>}/scripts/**/*.js', + '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + ] + } + }, + connect: { + options: { + port: 9000, + // change this to '0.0.0.0' to access the server from outside + hostname: 'localhost' + }, + livereload: { + options: { + middleware: function(connect) { + return [ + lrSnippet, + mountFolder(connect, '.tmp'), + mountFolder(connect, yeomanConfig.app) + ]; + } + } + }, + test: { + options: { + middleware: function(connect) { + return [ + mountFolder(connect, '.tmp'), + mountFolder(connect, 'test') + ]; + } + } + }, + dist: { + options: { + middleware: function(connect) { + return [ + mountFolder(connect, yeomanConfig.dist) + ]; + } + } + } + }, + open: { // Opens the webbrowser + server: { + path: 'http://localhost:<%= connect.options.port %>' + } + }, + clean: { + dist: { + files: [ + { + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/*', + '!<%= yeoman.dist %>/.git*' + ] + } + ] + }, + server: '.tmp' + }, + jshint: { + options: { + jshintrc: '<%= yeoman.app %>/components/pyload-common/.jshintrc' + }, + all: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/**/*.js', + '!<%= yeoman.app %>/scripts/vendor/*', + 'test/spec/{,*/}*.js' + ] + }, + mocha: { + all: { + options: { + run: true, + urls: ['http://localhost:<%= connect.options.port %>/index.html'] + } + } + }, + less: { + options: { + paths: [yeomanConfig.app + '/components', yeomanConfig.app + '/components/pyload-common/styles', + yeomanConfig.app + '/styles/default'] + //dumpLineNumbers: true + }, + dist: { + files: [ + { + expand: true, // Enable dynamic expansion. + cwd: '<%= yeoman.app %>/styles/', // Src matches are relative to this path. + src: ['**/main.less'], // Actual pattern(s) to match. + dest: '.tmp/styles', // Destination path prefix. + ext: '.css' // Dest filepaths will have this extension. + } + ] + } + }, + // not used since Uglify task does concat, + // but still available if needed + /*concat: { + dist: {} + },*/ + requirejs: { + dist: { + // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js + options: { + // `name` and `out` is set by grunt-usemin + baseUrl: yeomanConfig.app + '/scripts', + optimize: 'none', + // TODO: Figure out how to make sourcemaps work with grunt-usemin + // https://github.com/yeoman/grunt-usemin/issues/30 + //generateSourceMaps: true, + // required to support SourceMaps + // http://requirejs.org/docs/errors.html#sourcemapcomments + preserveLicenseComments: false, + useStrict: true, + wrap: true, + + // Delete already included files from dist + // TODO: For multiple modules it would delete to much files + done: function(done, output) { + var root = path.join(path.resolve('.'), yeomanConfig.app); + var parse = require('rjs-build-analysis').parse(output); + parse.bundles.forEach(function(bundle) { + var parent = path.relative(path.resolve('.'), bundle.parent); + bundle.children.forEach(function(f) { + // Skip templates + if (f.indexOf('hbs!') > -1) return; + + var rel = path.relative(root, f); + var target = path.join(yeomanConfig.dist, rel); + + if (target === parent) + return; + + if (fs.existsSync(target)) { + console.log('Removing', target); + fs.unlinkSync(target); + + // Remove the empty directories + var files = fs.readdirSync(path.dirname(target)); + if (files.length === 0) { + fs.rmdirSync(path.dirname(target)); + console.log('Removing dir', path.dirname(target)); + } + + } + }); + }); + done(); + } + //uglify2: {} // https://github.com/mishoo/UglifyJS2 + } + } + }, + rev: { + dist: { + files: { + src: [ + // TODO only main script needs a rev + '<%= yeoman.dist %>/scripts/default.js', + '<%= yeoman.dist %>/styles/{,*/}*.css' + ] + } + } + }, + useminPrepare: { + options: { + dest: '<%= yeoman.dist %>' + }, + html: '<%= yeoman.app %>/index.html' + }, + usemin: { + options: { + dirs: ['<%= yeoman.dist %>'] + }, + html: ['<%= yeoman.dist %>/*.html'], + css: ['<%= yeoman.dist %>/styles/**/*.css'] + }, + imagemin: { + dist: { + files: [ + { + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '**/*.{png,jpg,jpeg}', + dest: '<%= yeoman.dist %>/images' + } + ] + } + }, + svgmin: { + dist: { + files: [ + { + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '**/*.svg', + dest: '<%= yeoman.dist %>/images' + } + ] + } + }, + htmlmin: { + dist: { + options: { + /*removeCommentsFromCDATA: true, + // https://github.com/yeoman/grunt-usemin/issues/44 + //collapseWhitespace: true, + collapseBooleanAttributes: true, + removeAttributeQuotes: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeOptionalTags: true*/ + }, + files: [ + { + expand: true, + cwd: '<%= yeoman.app %>', + src: ['*.html'], + dest: '<%= yeoman.dist %>' + } + ] + } + }, + cssmin: { + options: { + banner: yeomanConfig.banner + }, + dist: { + expand: true, + cwd: '<%= yeoman.dist %>', + src: ['**/*.css', '!*.min.css'], + dest: '<%= yeoman.dist %>', + ext: '.css' + } + }, + uglify: { // JS min + options: { + mangle: true, + report: 'min', + preserveComments: false, + banner: yeomanConfig.banner + }, + dist: { + expand: true, + cwd: '<%= yeoman.dist %>', + dest: '<%= yeoman.dist %>', + src: ['**/*.js', '!*.min.js'] + } + }, + compress: { + main: { + options: { + mode: 'gzip' + }, + expand: true, + cwd: '<%= yeoman.dist %>', + dest: '<%= yeoman.dist %>', + src: ['**/*.{js,css,html}'] + } + }, + + // Put files not handled in other tasks here + copy: { + // Copy files from third party libraries + stageComponents: { + files: [ + { + expand: true, + flatten: true, + cwd: '<%= yeoman.app %>', + dest: '.tmp/fonts', + src: [ + '**/font-awesome/font/*' + ] + }, + { + expand: true, + flatten: true, + cwd: '<%= yeoman.app %>', + dest: '.tmp/vendor', + src: [ + '**/select2/select2.{png,css}', + '**/select2/select2-spinner.gif', + '**/select2/select2x2.png' + ] + }, + { + expand: true, + cwd: '<%= yeoman.app %>/components/pyload-common', + dest: '.tmp', + src: [ + 'favicon.ico', + 'images/*', + 'fonts/*' + ] + } + ] + }, + + dist: { + files: [ + { + expand: true, + dot: true, + cwd: '<%= yeoman.app %>', + dest: '<%= yeoman.dist %>', + src: [ + '*.{ico,txt}', + 'images/{,*/}*.{webp,gif}', + 'templates/**/*.html', + 'scripts/**/*.js', + 'styles/**/*.css', + 'fonts/*' + ] + } + ] + }, + + tmp: { + files: [ + { + expand: true, + cwd: '.tmp/', + dest: '<%= yeoman.dist %>', + src: [ + 'fonts/*', + 'images/*', + '**/*.{css,gif,png,js,html,ico}' + ] + } + ] + } + }, + concurrent: { + server: [ + 'copy:stageComponents', + 'less' + ], + test: [ + 'less' + ], + dist: [ + 'imagemin', + 'svgmin', + 'htmlmin', + 'cssmin' + ] + } + }); + + grunt.registerTask('server', function(target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'concurrent:server', + 'connect:livereload', + 'watch' + ]); + }); + + grunt.registerTask('test', [ + 'clean:server', + 'concurrent:test', + 'connect:test', + 'mocha' + ]); + + grunt.registerTask('build', [ + 'clean:dist', + 'useminPrepare', + 'less', + 'copy', // Copy .tmp, components, app to dist + 'requirejs', // build the main script and remove included scripts + 'concat', + 'concurrent:dist', // Run minimisation + 'uglify', // minify js + 'rev', + 'usemin', + 'compress' + ]); + + grunt.registerTask('default', [ + 'jshint', +// 'test', + 'build' + ]); +}; diff --git a/pyload/web/ServerThread.py b/pyload/web/ServerThread.py new file mode 100644 index 000000000..c55ddef0f --- /dev/null +++ b/pyload/web/ServerThread.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +from __future__ import with_statement +from time import time, sleep + +import threading +import logging + +from pyload.utils.fs import exists + +core = None +setup = None +log = logging.getLogger("log") + + +class WebServer(threading.Thread): + def __init__(self, pycore=None, pysetup=None): + global core, setup + threading.Thread.__init__(self) + + if pycore: + core = pycore + config = pycore.config + elif pysetup: + setup = pysetup + config = pysetup.config + else: + raise Exception("No config context provided") + + self.server = config['webinterface']['server'] + self.https = config['webinterface']['https'] + self.cert = config["ssl"]["cert"] + self.key = config["ssl"]["key"] + self.host = config['webinterface']['host'] + self.port = config['webinterface']['port'] + self.debug = config['general']['debug_mode'] + self.force_server = config['webinterface']['force_server'] + self.error = None + + self.setDaemon(True) + + def run(self): + self.running = True + import webinterface + + global webinterface + + if self.https: + if not exists(self.cert) or not exists(self.key): + log.warning(_("SSL certificates not found.")) + self.https = False + + if webinterface.UNAVAILALBE: + log.warning(_("WebUI built is not available")) + elif webinterface.APP_PATH == "app": + log.info(_("Running webUI in development mode")) + + prefer = None + + # These cases covers all settings + if self.server == "threaded": + prefer = "threaded" + elif self.server == "fastcgi": + prefer = "flup" + elif self.server == "fallback": + prefer = "wsgiref" + + server = self.select_server(prefer) + + try: + self.start_server(server) + + except Exception, e: + log.error(_("Failed starting webserver: " + e.message)) + self.error = e + if core: + core.print_exc() + + def select_server(self, prefer=None): + """ find a working server """ + from servers import all_server + + unavailable = [] + server = None + for server in all_server: + + if self.force_server and self.force_server == server.NAME: + break # Found server + # When force_server is set, no further checks have to be made + elif self.force_server: + continue + + if prefer and prefer == server.NAME: + break # found prefered server + elif prefer: # prefer is similar to force, but force has precedence + continue + + # Filter for server that offer ssl if needed + if self.https and not server.SSL: + continue + + try: + if server.find(): + break # Found a server + else: + unavailable.append(server.NAME) + except Exception, e: + log.error(_("Failed importing webserver: " + e.message)) + + if unavailable: # Just log whats not available to have some debug information + log.debug("Unavailable webserver: " + ",".join(unavailable)) + + if not server and self.force_server: + server = self.force_server # just return the name + + return server + + + def start_server(self, server): + + from servers import ServerAdapter + + if issubclass(server, ServerAdapter): + + if self.https and not server.SSL: + log.warning(_("This server offers no SSL, please consider using threaded instead")) + elif not self.https: + self.cert = self.key = None # This implicitly disables SSL + # there is no extra argument for the server adapter + # TODO: check for openSSL ? + + # Now instantiate the serverAdapter + server = server(self.host, self.port, self.key, self.cert, 6, self.debug) # todo, num_connections + name = server.NAME + + else: # server is just a string + name = server + + log.info( + _("Starting %(name)s webserver: %(host)s:%(port)d") % {"name": name, "host": self.host, "port": self.port}) + webinterface.run_server(host=self.host, port=self.port, server=server) + + + # check if an error was raised for n seconds + def check_error(self, n=1): + t = time() + n + while time() < t: + if self.error: + return self.error + sleep(0.1) + diff --git a/pyload/web/__init__.py b/pyload/web/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/web/__init__.py diff --git a/pyload/web/api_app.py b/pyload/web/api_app.py new file mode 100644 index 000000000..39747d5ea --- /dev/null +++ b/pyload/web/api_app.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from urllib import unquote +from traceback import format_exc, print_exc + +from bottle import route, request, response, HTTPError, parse_auth + +from utils import set_session, get_user_api, add_json_header +from webinterface import PYLOAD, session + +from pyload.Api import ExceptionObject +from pyload.remote.json_converter import loads, dumps, BaseEncoder +from pyload.utils import remove_chars + +# used for gzip compression +try: + import gzip + from cStringIO import StringIO +except ImportError: + gzip = None + StringIO = None + +# gzips response if supported +def json_response(obj): + accept = 'gzip' in request.headers.get('Accept-Encoding', '') + result = dumps(obj) + # don't compress small string + if gzip and accept and len(result) > 500: + response.headers['Vary'] = 'Accept-Encoding' + response.headers['Content-Encoding'] = 'gzip' + zbuf = StringIO() + zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf) + zfile.write(result) + zfile.close() + return zbuf.getvalue() + + return result + + +# returns http error +def error(code, msg): + return HTTPError(code, dumps(msg), **dict(response.headers)) + +# 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=""): + add_json_header(response) + + s = request.environ.get('beaker.session') + # Accepts standard http auth + auth = parse_auth(request.get_header('Authorization', '')) + if 'session' in request.POST or 'session' in request.GET: + # removes "' so it works on json strings + s = s.get_by_id(remove_chars(request.params.get('session'), "'\"")) + elif auth: + user = PYLOAD.checkAuth(auth[0], auth[1], request.environ.get('REMOTE_ADDR', None)) + # if auth is correct create a pseudo session + if user: s = {'uid': user.uid} + + api = get_user_api(s) + if not api: + return error(401, "Unauthorized") + + if not PYLOAD.isAuthorized(func, api.user): + return error(403, "Forbidden") + + if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"): + print "Invalid API call", func + return error(404, "Not Found") + + # TODO: possible encoding + # TODO Better error codes on invalid input + + args = [loads(unquote(arg)) for arg in args.split("/")[1:]] + kwargs = {} + + # accepts body as json dict + if request.json: + kwargs = request.json + + # file upload, reads whole file into memory + for name, f in request.files.iteritems(): + kwargs["filename"] = f.filename + kwargs[name] = f.value + + # convert arguments from json to obj separately + for x, y in request.params.iteritems(): + try: + if not x or not y or x == "session": continue + kwargs[x] = loads(unquote(y)) + except Exception, e: + # Unsupported input + msg = "Invalid Input %s, %s : %s" % (x, y, e.message) + print_exc() + print msg + return error(415, msg) + + try: + result = getattr(api, func)(*args, **kwargs) + # null is invalid json response + if result is None: result = True + return json_response(result) + + except ExceptionObject, e: + return error(400, e.message) + except Exception, e: + print_exc() + return error(500, {"error": e.message, "traceback": format_exc()}) + + +@route("/api/login") +@route("/api/login", method="POST") +def login(): + add_json_header(response) + + username = request.params.get("username") + password = request.params.get("password") + + user = PYLOAD.checkAuth(username, password, request.environ.get('REMOTE_ADDR', None)) + + if not user: + return json_response(False) + + 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] + # reuse old session id + except: + sid = request.get_header(session.options['key']) + + result = BaseEncoder().default(user) + result["session"] = sid + + # Return full user information if needed + if request.params.get('user', None): + return dumps(result) + + return json_response(sid) + + +@route("/api/logout") +@route("/api/logout", method="POST") +def logout(): + add_json_header(response) + + s = request.environ.get('beaker.session') + s.delete() + + return json_response(True) diff --git a/pyload/web/app/fonts/Abel-Regular.ttf b/pyload/web/app/fonts/Abel-Regular.ttf Binary files differnew file mode 100755 index 000000000..e37beb972 --- /dev/null +++ b/pyload/web/app/fonts/Abel-Regular.ttf diff --git a/pyload/web/app/fonts/Abel-Regular.woff b/pyload/web/app/fonts/Abel-Regular.woff Binary files differnew file mode 100644 index 000000000..ab8954389 --- /dev/null +++ b/pyload/web/app/fonts/Abel-Regular.woff diff --git a/pyload/web/app/images/default/checks_sheet.png b/pyload/web/app/images/default/checks_sheet.png Binary files differnew file mode 100644 index 000000000..9662b8070 --- /dev/null +++ b/pyload/web/app/images/default/checks_sheet.png diff --git a/pyload/web/app/images/icon.png b/pyload/web/app/images/icon.png Binary files differnew file mode 100644 index 000000000..1ab4ca081 --- /dev/null +++ b/pyload/web/app/images/icon.png diff --git a/pyload/web/app/index.html b/pyload/web/app/index.html new file mode 100644 index 000000000..98e1bf233 --- /dev/null +++ b/pyload/web/app/index.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + <!-- TODO: dynamic title --> + <title>pyLoad WebUI</title> + <meta name="description" content="pyLoad WebUI"> + <meta name="viewport" content="width=device-width"> + + <!-- TODO: basepath and templates --> + <link href="styles/font.css" rel="stylesheet" type="text/css"/> + <link href="styles/default/main.css" rel="stylesheet" type="text/css"> + <link href="vendor/select2.css" rel="stylesheet" type="text/css"/> + + + <!-- build:js scripts/config.js --> + <script data-main="scripts/config" src="components/requirejs/require.js"></script> + <!-- endbuild --> + + <script type="text/javascript"> + + // Use value set by templateEngine or default val + function configValue(string, defaultValue) { + if (string.indexOf('{{') > -1) + return defaultValue; + return string; + } + + window.dates = { + weeks: ['week', 'weeks'], + days: ['day', 'days'], + hours: ['hour', 'hours'], + minutes: ['minute', 'minutes'], + seconds: ['second', 'seconds'] + }; // TODO carefully when translating + + window.hostProtocol = window.location.protocol + '//'; + window.hostAddress = window.location.hostname; + window.hostPort = configValue('{{web}}', '8001'); + // TODO + window.pathPrefix = '/'; + window.wsAddress = configValue('{{ws}}', 'ws://%s:7227'); + window.setup = configValue('{{setup}}', 'false'); + + require(['config'], function(Config) { + require(['default'], function(App) { + }); + }) + </script> + +</head> +<body> +<div id="wrap"> + <header> + <div class="container-fluid"> + <div class="row-fluid" id="header"> + <div class="span3"> + <div class="logo"></div> + <span class="title visible-large-screen">pyLoad</span> + </div> + </div> + </div> + <div id="notification-area"></div> + <div id="selection-area"></div> + </header> + <div id="content-container" class="container-fluid"> + <div class="row-fluid" id="actionbar"> + </div> + <div class="row-fluid" id="content"> + </div> + </div> +</div> +<footer> + <div class="container-fluid"> + <div class="row-fluid"> + <div class="span2 offset1"> + <div class="copyright"> + © 2008-2013<br> + <a href="http://pyload.org/" target="_blank">The pyLoad Team</a><br> + </div> + </div> + <div class="span2 block"> + <h2 class="block-title"> + <a href="http://pyload.org" target="_blank"> + Community <i class="icon-comment"></i> + </a> + </h2> + <hr> + <a href="http://pyload.org" target="_blank">Homepage</a> · + <a href="http://board.pyload.org" target="_blank">Board</a> · + <a href="http://pyload.org/chat" target="_blank">Chat</a> + </div> + + <div class="span2 block"> + <h2 class="block-title"> + <a href="https://twitter.com/pyload" target="_blank"> + Follow us <i class="icon-twitter"></i> + </a> + </h2> + <hr> + <a href="https://twitter.com/pyload" target="_blank">Twitter</a> · + <a href="http://www.youtube.com/user/pyloadTeam" target="_blank">Youtube</a> + </div> + + <div class="span2 block"> + <h2 class="block-title"> + <a href="https://github.com/pyload" target="_blank"> + Development <i class="icon-github"></i> + </a> + </h2> + <hr> + <a href="https://github.com/pyload" target="_blank">Github</a> · + <a href="http://docs.pyload.org" target="_blank">Documentation</a> + </div> + + <div class="span2 block"> + <h2 class="block-title"> + <a href="http://pyload.org/donate" target="_blank"> + Donate <i class="icon-bitcoin"> </i> + </a> + </h2> + <hr> + <a href="http://pyload.org/donate" target="_blank">PayPal</a> · + <a href="http://blockchain.info/address/1JvcfSKuzk3VENJm9XtqGp2DCTesgokkG2" target="_blank">Bitcoin</a> · + <a href="https://flattr.com/profile/pyload" target="_blank">Flattr</a> + </div> + + </div> + </div> +</footer> +<div id="modal-overlay" class="hide"></div> +</body> +</html> diff --git a/pyload/web/app/scripts/app.js b/pyload/web/app/scripts/app.js new file mode 100644 index 000000000..af5c50b14 --- /dev/null +++ b/pyload/web/app/scripts/app.js @@ -0,0 +1,104 @@ +/* + * Global Application Object + * Contains all necessary logic shared across views + */ +define([ + + // Libraries. + 'jquery', + 'underscore', + 'backbone', + 'handlebars', + 'utils/animations', + 'utils/lazyRequire', + 'utils/dialogs', + 'marionette', + 'bootstrap', + 'animate' + +], function($, _, Backbone, Handlebars) { + 'use strict'; + + Backbone.Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) { + return Handlebars.compile(rawTemplate); + }; + + // TODO: configurable root + var App = new Backbone.Marionette.Application({ + root: '/' + }); + + App.addRegions({ + header: '#header', + notification: '#notification-area', + selection: '#selection-area', + content: '#content', + actionbar: '#actionbar' + }); + + App.navigate = function(url) { + return Backbone.history.navigate(url, true); + }; + + App.apiUrl = function(path) { + var url = window.hostProtocol + window.hostAddress + ':' + window.hostPort + window.pathPrefix + path; + return url; + }; + + // Add Global Helper functions + // Generates options dict that can be used for xhr requests + App.apiRequest = function(method, data, options) { + options || (options = {}); + options.url = App.apiUrl('api/' + method); + options.dataType = 'json'; + + if (data) { + options.type = 'POST'; + options.data = {}; + // Convert arguments to json + _.keys(data).map(function(key) { + options.data[key] = JSON.stringify(data[key]); + }); + } + + return options; + }; + + App.setTitle = function(name) { + var title = window.document.title; + var newTitle; + // page name separator + var index = title.indexOf('-'); + if (index >= 0) + newTitle = name + ' - ' + title.substr(index + 2, title.length); + else + newTitle = name + ' - ' + title; + + window.document.title = newTitle; + }; + + App.openWebSocket = function(path) { + return new WebSocket(window.wsAddress.replace('%s', window.hostAddress) + path); + }; + + App.on('initialize:after', function() { +// TODO pushState variable + Backbone.history.start({ + pushState: false, + root: App.root + }); + + // All links should be handled by backbone + $(document).on('click', 'a[data-nav]', function(evt) { + var href = { prop: $(this).prop('href'), attr: $(this).attr('href') }; + var root = location.protocol + '//' + location.host + App.root; + if (href.prop.slice(0, root.length) === root) { + evt.preventDefault(); + Backbone.history.navigate(href.attr, true); + } + }); + }); + + // Returns the app object to be available to other modules through require.js. + return App; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/AccountList.js b/pyload/web/app/scripts/collections/AccountList.js new file mode 100644 index 000000000..f6a8eda65 --- /dev/null +++ b/pyload/web/app/scripts/collections/AccountList.js @@ -0,0 +1,23 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Account'], function($, Backbone, _, App, Account) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: Account, + + comparator: function(account) { + return account.get('plugin'); + }, + + initialize: function() { + + }, + + fetch: function(options) { + options = App.apiRequest('getAccounts', null, options); + return Backbone.Collection.prototype.fetch.call(this, options); + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/FileList.js b/pyload/web/app/scripts/collections/FileList.js new file mode 100644 index 000000000..112dc5e51 --- /dev/null +++ b/pyload/web/app/scripts/collections/FileList.js @@ -0,0 +1,28 @@ +define(['jquery', 'backbone', 'underscore', 'models/File'], function($, Backbone, _, File) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: File, + + comparator: function(file) { + return file.get('fileorder'); + }, + + isEqual: function(fileList) { + if (this.length !== fileList.length) return false; + + // Assuming same order would be faster in false case + var diff = _.difference(this.models, fileList.models); + + // If there is a difference models are unequal + return diff.length > 0; + }, + + initialize: function() { + + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/InteractionList.js b/pyload/web/app/scripts/collections/InteractionList.js new file mode 100644 index 000000000..24f8b9248 --- /dev/null +++ b/pyload/web/app/scripts/collections/InteractionList.js @@ -0,0 +1,49 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/InteractionTask'], + function($, Backbone, _, App, InteractionTask) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: InteractionTask, + + comparator: function(task) { + return task.get('iid'); + }, + + fetch: function(options) { + options = App.apiRequest('getInteractionTasks/0', null, options); + var self = this; + options.success = function(data) { + self.set(data); + }; + + return $.ajax(options); + }, + + toJSON: function() { + var data = {queries: 0, notifications: 0}; + + this.map(function(task) { + if (task.isNotification()) + data.notifications++; + else + data.queries++; + }); + + return data; + }, + + // a task is waiting for attention (no notification) + hasTaskWaiting: function() { + var tasks = 0; + this.map(function(task) { + if (!task.isNotification()) + tasks++; + }); + + return tasks > 0; + } + + }); + + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/LinkList.js b/pyload/web/app/scripts/collections/LinkList.js new file mode 100644 index 000000000..170a2c039 --- /dev/null +++ b/pyload/web/app/scripts/collections/LinkList.js @@ -0,0 +1,14 @@ +define(['jquery', 'backbone', 'underscore', 'models/LinkStatus'], function($, Backbone, _, LinkStatus) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: LinkStatus, + + comparator: function(link) { + return link.get('name'); + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/PackageList.js b/pyload/web/app/scripts/collections/PackageList.js new file mode 100644 index 000000000..7bee861a4 --- /dev/null +++ b/pyload/web/app/scripts/collections/PackageList.js @@ -0,0 +1,16 @@ +define(['jquery', 'backbone', 'underscore', 'models/Package'], function($, Backbone, _, Package) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: Package, + + comparator: function(pack) { + return pack.get('packageorder'); + }, + + initialize: function() { + } + + }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/ProgressList.js b/pyload/web/app/scripts/collections/ProgressList.js new file mode 100644 index 000000000..51849d8de --- /dev/null +++ b/pyload/web/app/scripts/collections/ProgressList.js @@ -0,0 +1,18 @@ +define(['jquery', 'backbone', 'underscore', 'models/Progress'], function($, Backbone, _, Progress) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: Progress, + + comparator: function(progress) { + return progress.get('eta'); + }, + + initialize: function() { + + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/config.js b/pyload/web/app/scripts/config.js new file mode 100644 index 000000000..51ea63285 --- /dev/null +++ b/pyload/web/app/scripts/config.js @@ -0,0 +1,78 @@ +// Sets the require.js configuration for your application. +'use strict'; +require.config({ + + deps: ['default'], + + paths: { + + jquery: '../components/jquery/jquery', + flot: '../components/flot/jquery.flot', + transit: '../components/jquery.transit/jquery.transit', + animate: '../components/jquery.animate-enhanced/scripts/src/jquery.animate-enhanced', + cookie: '../components/jquery.cookie/jquery.cookie', + omniwindow: 'vendor/jquery.omniwindow', + select2: '../components/select2/select2', + bootstrap: '../components/bootstrap-assets/js/bootstrap', + underscore: '../components/underscore/underscore', + backbone: '../components/backbone/backbone', + marionette: '../components/backbone.marionette/lib/backbone.marionette', + handlebars: '../components/handlebars.js/dist/handlebars', + jed: '../components/jed/jed', + moment: '../components/momentjs/moment', + + // TODO: Two hbs dependencies could be replaced + i18nprecompile: '../components/require-handlebars-plugin/hbs/i18nprecompile', + json2: '../components/require-handlebars-plugin/hbs/json2', + + // Plugins +// text: '../components/requirejs-text/text', + hbs: '../components/require-handlebars-plugin/hbs', + + // Shortcut + tpl: '../templates/default' + }, + + hbs: { + disableI18n: true, + helperPathCallback: // Callback to determine the path to look for helpers + function(name) { + if (name === '_' || name === 'ngettext') + name = 'gettext'; + + // Some helpers are accumulated into one file + if (name.indexOf('file') === 0) + name = 'fileHelper'; + + return 'helpers/' + name; + }, + templateExtension: 'html' + }, + + // Sets the configuration for your third party scripts that are not AMD compatible + shim: { + underscore: { + exports: '_' + }, + + backbone: { + deps: ['underscore', 'jquery'], + exports: 'Backbone' + }, + marionette: { + deps: ['backbone'], + exports: 'Backbone' + }, + handlebars: { + exports: 'Handlebars' + }, + + flot: ['jquery'], + transit: ['jquery'], + cookie: ['jquery'], + omniwindow: ['jquery'], + select2: ['jquery'], + bootstrap: ['jquery'], + animate: ['jquery'] + } +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/controller.js b/pyload/web/app/scripts/controller.js new file mode 100644 index 000000000..9a892323f --- /dev/null +++ b/pyload/web/app/scripts/controller.js @@ -0,0 +1,99 @@ +define([ + 'app', + 'backbone', + 'jquery', + 'underscore', + + // Views + 'views/headerView', + 'hbs!tpl/header/blank', + 'views/notificationView', + 'views/dashboard/dashboardView', + 'views/dashboard/selectionView', + 'views/dashboard/filterView', + 'views/loginView', + 'views/settings/settingsView', + 'views/accounts/accountListView' +], function( + App, Backbone, $, _, HeaderView, blankHeader, NotificationView, DashboardView, SelectionView, FilterView, LoginView, SettingsView, AccountListView) { + 'use strict'; + return { + + // resets the main views + reset: function() { + if (App.header.currentView) { + App.header.currentView.close(); + App.header.$el.html(blankHeader()); + App.header.currentView = null; + } + if (App.content.currentView) { + App.content.currentView.close(); + } + + if (App.actionbar.currentView) { + App.actionbar.currentView.close(); + } + }, + + header: function() { + if (!App.header.currentView) { + App.header.show(new HeaderView()); + App.header.currentView.init(); + } + if (!App.notification.currentView) { + App.notification.attachView(new NotificationView()); + } + }, + + dashboard: function() { + this.header(); + + App.actionbar.show(new FilterView()); + + // now visible every time + if (_.isUndefined(App.selection.currentView) || _.isNull(App.selection.currentView)) + App.selection.attachView(new SelectionView()); + + App.content.show(new DashboardView()); + }, + + login: function() { + this.reset(); + + App.content.show(new LoginView()); + }, + + logout: function() { + this.reset(); + + $.ajax(App.apiRequest('logout', null, { + success: function() { + App.user.destroy(); + App.navigate('login'); + } + } + )); + }, + + settings: function() { + this.header(); + + var view = new SettingsView(); + App.actionbar.show(new view.actionbar()); + App.content.show(view); + }, + + accounts: function() { + this.header(); + + var view = new AccountListView(); + App.actionbar.show(new view.actionbar()); + App.content.show(view); + }, + + admin: function() { + alert('Not implemented'); + } + }; + +}); diff --git a/pyload/web/app/scripts/default.js b/pyload/web/app/scripts/default.js new file mode 100644 index 000000000..91b46715e --- /dev/null +++ b/pyload/web/app/scripts/default.js @@ -0,0 +1,38 @@ +define('default', ['require', 'backbone', 'jquery', 'app', 'router', 'models/UserSession'], + function(require, Backbone, $, App, Router, UserSession) { + 'use strict'; + + // Global ajax options + var options = { + statusCode: { + 401: function() { + console.log('Not logged in.'); + App.navigate('login'); + } + }, + xhrFields: {withCredentials: true} + }; + + $.ajaxSetup(options); + + Backbone.ajax = function() { + Backbone.$.ajaxSetup.call(Backbone.$, options); + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + $(function() { + // load setup async + if (window.setup === 'true') { + require(['setup'], function(SetupRouter) { + App.router = new SetupRouter(); + App.start(); + }); + } else { + App.user = new UserSession(); + App.router = new Router(); + App.start(); + } + }); + + return App; + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/fileHelper.js b/pyload/web/app/scripts/helpers/fileHelper.js new file mode 100644 index 000000000..2e14f939f --- /dev/null +++ b/pyload/web/app/scripts/helpers/fileHelper.js @@ -0,0 +1,69 @@ +// Helpers to render the file view +define('helpers/fileHelper', ['handlebars', 'utils/apitypes', 'helpers/formatTimeLeft'], + function(Handlebars, Api, formatTime) { + 'use strict'; + + function fileClass(file, options) { + if (file.finished) + return 'finished'; + else if (file.failed) + return 'failed'; + else if (file.offline) + return 'offline'; + else if (file.online) + return 'online'; + else if (file.waiting) + return 'waiting'; + else if (file.downloading) + return 'downloading'; + + return ''; + } + + function fileIcon(media, options) { + switch (media) { + case Api.MediaType.Audio: + return 'icon-music'; + case Api.MediaType.Image: + return 'icon-picture'; + case Api.MediaType.Video: + return 'icon-film'; + case Api.MediaType.Document: + return 'icon-file-text'; + case Api.MediaType.Archive: + return 'icon-archive'; + case Api.MediaType.Executable: + return 'icon-cog'; + default: + return 'icon-file-alt'; + } + } + + // TODO rest of the states + function fileStatus(file, options) { + var s; + var msg = file.download.statusmsg; + + if (file.failed) { + s = '<i class="icon-remove"></i> '; + if (file.download.error) + s += file.download.error; + else s += msg; + } else if (file.finished) + s = '<i class="icon-ok"></i> ' + msg; + else if (file.downloading) + s = '<div class="progress"><div class="bar" style="width: ' + file.progress + '%"> ' + + formatTime(file.eta) + '</div></div>'; + else if (file.waiting) + s = '<i class="icon-time"></i> ' + formatTime(file.eta); + else + s = msg; + + return new Handlebars.SafeString(s); + } + + Handlebars.registerHelper('fileClass', fileClass); + Handlebars.registerHelper('fileIcon', fileIcon); + Handlebars.registerHelper('fileStatus', fileStatus); + return fileClass; + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/formatSize.js b/pyload/web/app/scripts/helpers/formatSize.js new file mode 100644 index 000000000..f72d62158 --- /dev/null +++ b/pyload/web/app/scripts/helpers/formatSize.js @@ -0,0 +1,20 @@ +// Format bytes in human readable format +define('helpers/formatSize', ['handlebars', 'utils/i18n'], function(Handlebars, i18n) { + 'use strict'; + + var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; + function formatSize(bytes, options) { + if (!bytes || bytes === 0) return '0 B'; + if (bytes === -1) + return i18n.gettext('not available'); + if (bytes === -2) + return i18n.gettext('unlimited'); + + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); + // round to two digits + return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; + } + + Handlebars.registerHelper('formatSize', formatSize); + return formatSize; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/formatTime.js b/pyload/web/app/scripts/helpers/formatTime.js new file mode 100644 index 000000000..750ce58fe --- /dev/null +++ b/pyload/web/app/scripts/helpers/formatTime.js @@ -0,0 +1,20 @@ +// Formats a timestamp +define('helpers/formatTime', ['underscore','handlebars', 'moment', 'utils/i18n'], + function(_, Handlebars, moment, i18n) { + 'use strict'; + + function formatTime(time, format) { + if (time === -1) + return i18n.gettext('unknown'); + else if (time === -2) + return i18n.gettext('unlimited'); + + if (!_.isString(format)) + format = 'lll'; + + return moment(time).format(format); + } + + Handlebars.registerHelper('formatTime', formatTime); + return formatTime; +}); diff --git a/pyload/web/app/scripts/helpers/formatTimeLeft.js b/pyload/web/app/scripts/helpers/formatTimeLeft.js new file mode 100644 index 000000000..dafeda3e2 --- /dev/null +++ b/pyload/web/app/scripts/helpers/formatTimeLeft.js @@ -0,0 +1,17 @@ +// Format seconds in human readable format +define('helpers/formatTimeLeft', ['handlebars', 'vendor/remaining'], function(Handlebars, Remaining) { + 'use strict'; + + function formatTimeLeft(seconds, options) { + if (seconds === Infinity) + return '∞'; + else if (!seconds || seconds <= 0) + return '-'; + + // TODO: digital or written string + return Remaining.getStringDigital(seconds, window.dates); + } + + Handlebars.registerHelper('formatTimeLeft', formatTimeLeft); + return formatTimeLeft; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/gettext.js b/pyload/web/app/scripts/helpers/gettext.js new file mode 100644 index 000000000..d73b5e378 --- /dev/null +++ b/pyload/web/app/scripts/helpers/gettext.js @@ -0,0 +1,16 @@ +require(['underscore', 'handlebars', 'utils/i18n'], function(_, Handlebars, i18n) { + 'use strict'; + // These methods binds additional content directly to translated message + function ngettext(single, plural, n) { + return i18n.sprintf(i18n.ngettext(single, plural, n), n); + } + + function gettext(key, message) { + return i18n.sprintf(i18n.gettext(key), message); + } + + Handlebars.registerHelper('_', gettext); + Handlebars.registerHelper('gettext', gettext); + Handlebars.registerHelper('ngettext', ngettext); + return gettext; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/ifEq.js b/pyload/web/app/scripts/helpers/ifEq.js new file mode 100644 index 000000000..1c8a71b61 --- /dev/null +++ b/pyload/web/app/scripts/helpers/ifEq.js @@ -0,0 +1,14 @@ +define('helpers/ifEq', ['underscore', 'handlebars'], + function(_, Handlebars) { + /*jshint validthis:true */ + 'use strict'; + function ifEq(v1, v2, options) { + if (v1 === v2) { + return options.fn(this); + } + return options.inverse(this); + } + + Handlebars.registerHelper('ifEq', ifEq); + return ifEq; + }); diff --git a/pyload/web/app/scripts/helpers/linkStatus.js b/pyload/web/app/scripts/helpers/linkStatus.js new file mode 100644 index 000000000..448d63691 --- /dev/null +++ b/pyload/web/app/scripts/helpers/linkStatus.js @@ -0,0 +1,18 @@ +define('helpers/linkStatus', ['underscore', 'handlebars', 'utils/apitypes', 'utils/i18n'], + function(_, Handlebars, Api, i18n) { + 'use strict'; + function linkStatus(status) { + var s; + if (status === Api.DownloadStatus.Online) + s = '<span class="text-success">' + i18n.gettext('online') + '</span>'; + else if (status === Api.DownloadStatus.Offline) + s = '<span class="text-error">' + i18n.gettext('offline') + '</span>'; + else + s = '<span class="text-info">' + i18n.gettext('unknown') + '</span>'; + + return new Handlebars.SafeString(s); + } + + Handlebars.registerHelper('linkStatus', linkStatus); + return linkStatus; + }); diff --git a/pyload/web/app/scripts/helpers/pluginIcon.js b/pyload/web/app/scripts/helpers/pluginIcon.js new file mode 100644 index 000000000..1004c2487 --- /dev/null +++ b/pyload/web/app/scripts/helpers/pluginIcon.js @@ -0,0 +1,14 @@ +// Resolves name of plugin to icon path +define('helpers/pluginIcon', ['handlebars', 'app'], function(Handlebars, App) { + 'use strict'; + + function pluginIcon(name) { + if (name && typeof name === 'object' && typeof name.get === 'function') + name = name.get('plugin'); + + return App.apiUrl('icons/' + name); + } + + Handlebars.registerHelper('pluginIcon', pluginIcon); + return pluginIcon; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/truncate.js b/pyload/web/app/scripts/helpers/truncate.js new file mode 100644 index 000000000..fb351b776 --- /dev/null +++ b/pyload/web/app/scripts/helpers/truncate.js @@ -0,0 +1,25 @@ +require(['underscore','handlebars'], function(_, Handlebars) { + 'use strict'; + + function truncate(fullStr, options) { + var strLen = 30; + if (_.isNumber(options)) + strLen = options; + + if (fullStr.length <= strLen) return fullStr; + + var separator = options.separator || '…'; + + var sepLen = separator.length, + charsToShow = strLen - sepLen, + frontChars = Math.ceil(charsToShow / 2), + backChars = Math.floor(charsToShow / 2); + + return fullStr.substr(0, frontChars) + + separator + + fullStr.substr(fullStr.length - backChars); + } + + Handlebars.registerHelper('truncate', truncate); + return truncate; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Account.js b/pyload/web/app/scripts/models/Account.js new file mode 100644 index 000000000..26241d8e3 --- /dev/null +++ b/pyload/web/app/scripts/models/Account.js @@ -0,0 +1,101 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', './ConfigItem'], function($, Backbone, _, App, Api, ConfigItem) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'loginname', + + defaults: { + plugin: null, + loginname: null, + owner: -1, + valid: false, + validuntil: -1, + trafficleft: -1, + maxtraffic: -1, + premium: false, + activated: false, + shared: false, + config: null + }, + + // Model Constructor + initialize: function() { + }, + + // Any time a model attribute is set, this method is called + validate: function(attrs) { + + }, + + // representation handled by server + toServerJSON: function() { + var data = this.toJSON(); + delete data.config; + + return data; + }, + + parse: function(resp) { + // Convert config to models + resp.config = _.map(resp.config, function(item) { + return new ConfigItem(item); + }); + + // JS uses time based on ms + if (resp.validuntil > 0) + resp.validuntil *= 1000; + + return resp; + }, + + fetch: function(options) { + var refresh = _.has(options, 'refresh') && options.refresh; + options = App.apiRequest('getAccountInfo', + {plugin: this.get('plugin'), + loginname: this.get('loginname'), refresh: refresh}, options); + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + setPassword: function(password, options) { + options = App.apiRequest('updateAccount', + {plugin: this.get('plugin'), loginname: this.get('loginname'), password: password}, options); + + return $.ajax(options); + }, + + save: function() { + // use changed config items only + var data = this.toJSON(); + data.config = _.map(_.filter(data.config, function(c) { + return c.isChanged(); + }), function(c) { + return c.prepareSave(); + }); + + // On success wait 1sec and trigger event to reload info + var options = App.apiRequest('updateAccountInfo', {account: data}, { + success: function() { + _.delay(function() { + App.vent.trigger('account:updated'); + }, 1000); + } + }); + return $.ajax(options); + }, + + destroy: function(options) { + options = App.apiRequest('removeAccount', {account: this.toServerJSON()}, options); + var self = this; + options.success = function() { + self.trigger('destroy', self, self.collection, options); + }; + + // TODO request is not dispatched +// return Backbone.Model.prototype.destroy.call(this, options); + return $.ajax(options); + } + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/CollectorPackage.js b/pyload/web/app/scripts/models/CollectorPackage.js new file mode 100644 index 000000000..b608b8e18 --- /dev/null +++ b/pyload/web/app/scripts/models/CollectorPackage.js @@ -0,0 +1,94 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'collections/LinkList'], + function($, Backbone, _, App, Api, LinkList) { + 'use strict'; + return Backbone.Model.extend({ + + idAttribute: 'name', + defaults: { + name: 'Unnamed package', + new_name: null, + links: null + }, + + initialize: function() { + this.set('links', new LinkList()); + }, + + destroy: function() { + // Copied from backbones destroy method + var model = this; + model.trigger('destroy', model, model.collection); + }, + + // overwrites original name + setName: function(name) { + this.set('new_name', name); + }, + + // get the actual name + getName: function() { + var new_name = this.get('new_name'); + if (new_name) + return new_name; + + return this.get('name'); + + }, + // Add the package to pyload + add: function() { + var self = this; + var links = this.get('links').pluck('url'); + + $.ajax(App.apiRequest('addPackage', + {name: this.getName(), + links: links}, + {success: function() { + self.destroy(); + App.vent.trigger('package:added'); + }})); + + }, + + updateLinks: function(links) { + this.get('links').set(links, {remove: false}); + this.trigger('change'); + }, + + // Returns true if pack is empty now + removeLinks: function(links) { + this.get('links').remove(_.map(links, function(link) { + return link.url; + })); + return this.get('links').length === 0; + }, + + toJSON: function() { + var data = { + name: this.getName(), + links: this.get('links').toJSON() + }; + var links = this.get('links'); + data.length = links.length; + data.size = 0; + data.online = 0; + data.offline = 0; + data.unknown = 0; + + // Summary + links.each(function(link) { + if (link.get('status') === Api.DownloadStatus.Online) + data.online++; + else if (link.get('status') === Api.DownloadStatus.Offline) + data.offline++; + else + data.unknown++; + + if (link.get('size') > 0) + data.size += link.get('size'); + }); + + return data; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ConfigHolder.js b/pyload/web/app/scripts/models/ConfigHolder.js new file mode 100644 index 000000000..638b2d644 --- /dev/null +++ b/pyload/web/app/scripts/models/ConfigHolder.js @@ -0,0 +1,68 @@ +define(['jquery', 'backbone', 'underscore', 'app', './ConfigItem'], + function($, Backbone, _, App, ConfigItem) { + 'use strict'; + + return Backbone.Model.extend({ + + defaults: { + name: '', + label: '', + description: '', + explanation: null, + // simple list but no collection + items: null, + info: null + }, + + // Model Constructor + initialize: function() { + + }, + + // Loads it from server by name + fetch: function(options) { + options = App.apiRequest('loadConfig/"' + this.get('name') + '"', null, options); + return Backbone.Model.prototype.fetch.call(this, options); + }, + + save: function(options) { + var config = this.toJSON(); + var items = []; + // Convert changed items to json + _.each(config.items, function(item) { + if (item.isChanged()) { + items.push(item.prepareSave()); + } + }); + config.items = items; + // TODO: only set new values on success + + options = App.apiRequest('saveConfig', {config: config}, options); + + return $.ajax(options); + }, + + parse: function(resp) { + // Create item models + resp.items = _.map(resp.items, function(item) { + return new ConfigItem(item); + }); + + return Backbone.Model.prototype.parse.call(this, resp); + }, + + isLoaded: function() { + return this.has('items') || this.has('explanation'); + }, + + // check if any of the items has changes + hasChanges: function() { + var items = this.get('items'); + if (!items) return false; + return _.reduce(items, function(a, b) { + return a || b.isChanged(); + }, false); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ConfigItem.js b/pyload/web/app/scripts/models/ConfigItem.js new file mode 100644 index 000000000..8c75f45f6 --- /dev/null +++ b/pyload/web/app/scripts/models/ConfigItem.js @@ -0,0 +1,42 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'name', + + defaults: { + name: '', + label: '', + description: '', + input: null, + value: null, + // additional attributes + inputView: null + }, + + // Model Constructor + initialize: function() { + + }, + + isChanged: function() { + return this.get('inputView') && this.get('inputView').getVal() !== this.get('value'); + }, + + // set new value and return json + prepareSave: function() { + // set the new value + if (this.get('inputView')) + this.set('value', this.get('inputView').getVal()); + + // These values are enough to be handled correctly + return { + name: this.get('name'), + value: this.get('value'), + '@class': this.get('@class') + }; + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/File.js b/pyload/web/app/scripts/models/File.js new file mode 100644 index 000000000..562e6b0ae --- /dev/null +++ b/pyload/web/app/scripts/models/File.js @@ -0,0 +1,97 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], function($, Backbone, _, App, Api) { + 'use strict'; + + var Finished = [Api.DownloadStatus.Finished, Api.DownloadStatus.Skipped]; + var Failed = [Api.DownloadStatus.Failed, Api.DownloadStatus.Aborted, Api.DownloadStatus.TempOffline, Api.DownloadStatus.Offline]; + // Unfinished - Other + + 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, + + // UI attributes + selected: false, + visible: true, + progress: 0, + eta: 0 + }, + + // Model Constructor + initialize: function() { + + }, + + fetch: function(options) { + options = App.apiRequest( + 'getFileInfo', + {fid: this.get('fid')}, + options); + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + destroy: function(options) { + // also not working when using data + options = App.apiRequest( + 'deleteFiles/[' + this.get('fid') + ']', + null, options); + options.method = 'post'; + + return Backbone.Model.prototype.destroy.call(this, options); + }, + + // Does not send a request to the server + destroyLocal: function(options) { + this.trigger('destroy', this, this.collection, options); + }, + + restart: function(options) { + options = App.apiRequest( + 'restartFile', + {fid: this.get('fid')}, + options); + + return $.ajax(options); + }, + + // Any time a model attribute is set, this method is called + validate: function(attrs) { + + }, + + setDownloadStatus: function(status) { + if (this.isDownload()) + this.get('download').status = status; + }, + + isDownload: function() { + return this.has('download'); + }, + + isFinished: function() { + return _.indexOf(Finished, this.get('download').status) > -1; + }, + + isUnfinished: function() { + return _.indexOf(Finished, this.get('download').status) === -1 && _.indexOf(Failed, this.get('download').status) === -1; + }, + + isFailed: function() { + return _.indexOf(Failed, this.get('download').status) > -1; + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/InteractionTask.js b/pyload/web/app/scripts/models/InteractionTask.js new file mode 100644 index 000000000..54c739d4b --- /dev/null +++ b/pyload/web/app/scripts/models/InteractionTask.js @@ -0,0 +1,41 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'iid', + + defaults: { + iid: -1, + type: null, + input: null, + default_value: null, + title: '', + description: '', + plugin: '', + // additional attributes + result: '' + }, + + // Model Constructor + initialize: function() { + + }, + + save: function(options) { + options = App.apiRequest('setInteractionResult/' + this.get('iid'), + {result: this.get('result')}, options); + + return $.ajax(options); + }, + + isNotification: function() { + return this.get('type') === Api.Interaction.Notification; + }, + + isCaptcha: function() { + return this.get('type') === Api.Interaction.Captcha; + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/LinkStatus.js b/pyload/web/app/scripts/models/LinkStatus.js new file mode 100644 index 000000000..be6385c9c --- /dev/null +++ b/pyload/web/app/scripts/models/LinkStatus.js @@ -0,0 +1,23 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'url', + + defaults: { + name: '', + size: -1, + status: Api.DownloadStatus.Queued, + plugin: '', + hash: null + }, + + destroy: function() { + var model = this; + model.trigger('destroy', model, model.collection); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Package.js b/pyload/web/app/scripts/models/Package.js new file mode 100644 index 000000000..a34ec1c69 --- /dev/null +++ b/pyload/web/app/scripts/models/Package.js @@ -0,0 +1,119 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'collections/FileList', 'require'], + function($, Backbone, _, App, FileList, require) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'pid', + + defaults: { + pid: -1, + name: null, + folder: '', + root: -1, + owner: -1, + site: '', + comment: '', + password: '', + added: -1, + tags: null, + status: -1, + shared: false, + packageorder: -1, + stats: null, + fids: null, + pids: null, + files: null, // Collection + packs: null, // Collection + + selected: false // For Checkbox + }, + + // Model Constructor + initialize: function() { + }, + + toJSON: function(options) { + var obj = Backbone.Model.prototype.toJSON.call(this, options); + obj.percent = Math.round(obj.stats.linksdone * 100 / obj.stats.linkstotal); + + return obj; + }, + + // Changes url + method and delegates call to super class + fetch: function(options) { + options = App.apiRequest( + 'getFileTree/' + this.get('pid'), + {full: false}, + options); + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + // Create a pseudo package und use search to populate data + search: function(qry, options) { + options = App.apiRequest( + 'findFiles', + {pattern: qry}, + options); + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + save: function(options) { + // TODO + }, + + destroy: function(options) { + // TODO: Not working when using data?, array seems to break it + options = App.apiRequest( + 'deletePackages/[' + this.get('pid') + ']', + null, options); + options.method = 'post'; + + console.log(options); + + return Backbone.Model.prototype.destroy.call(this, options); + }, + + restart: function(options) { + options = App.apiRequest( + 'restartPackage', + {pid: this.get('pid')}, + options); + + var self = this; + options.success = function() { + self.fetch(); + }; + return $.ajax(options); + }, + + parse: function(resp) { + // Package is loaded from tree collection + if (_.has(resp, 'root')) { + if (!this.has('files')) + resp.root.files = new FileList(_.values(resp.files)); + else + this.get('files').set(_.values(resp.files)); + + // circular dependencies needs to be avoided + var PackageList = require('collections/PackageList'); + + if (!this.has('packs')) + resp.root.packs = new PackageList(_.values(resp.packages)); + else + this.get('packs').set(_.values(resp.packages)); + + return resp.root; + } + return Backbone.model.prototype.parse.call(this, resp); + }, + + // Any time a model attribute is set, this method is called + validate: function(attrs) { + + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Progress.js b/pyload/web/app/scripts/models/Progress.js new file mode 100644 index 000000000..b0bbb684d --- /dev/null +++ b/pyload/web/app/scripts/models/Progress.js @@ -0,0 +1,50 @@ +define(['jquery', 'backbone', 'underscore', 'utils/apitypes'], function($, Backbone, _, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + // generated, not submitted + idAttribute: 'pid', + + defaults: { + pid: -1, + plugin: null, + name: null, + statusmsg: -1, + eta: -1, + done: -1, + total: -1, + download: null + }, + + getPercent: function() { + if (this.get('total') > 0) + return Math.round(this.get('done') * 100 / this.get('total')); + return 0; + }, + + // Model Constructor + initialize: function() { + + }, + + // Any time a model attribute is set, this method is called + validate: function(attrs) { + + }, + + toJSON: function(options) { + var obj = Backbone.Model.prototype.toJSON.call(this, options); + obj.percent = this.getPercent(); + obj.downloading = this.isDownload() && this.get('download').status === Api.DownloadStatus.Downloading; + + return obj; + }, + + isDownload : function() { + return this.has('download'); + } + + }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ServerStatus.js b/pyload/web/app/scripts/models/ServerStatus.js new file mode 100644 index 000000000..59739b41e --- /dev/null +++ b/pyload/web/app/scripts/models/ServerStatus.js @@ -0,0 +1,47 @@ +define(['jquery', 'backbone', 'underscore'], + function($, Backbone, _) { + 'use strict'; + + return Backbone.Model.extend({ + + defaults: { + speed: 0, + linkstotal: 0, + linksqueue: 0, + sizetotal: 0, + sizequeue: 0, + notifications: -1, + paused: false, + download: false, + reconnect: false + }, + + // Model Constructor + initialize: function() { + + }, + + fetch: function(options) { + options || (options = {}); + options.url = 'api/getServerStatus'; + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + toJSON: function(options) { + var obj = Backbone.Model.prototype.toJSON.call(this, options); + + obj.linksdone = obj.linkstotal - obj.linksqueue; + obj.sizedone = obj.sizetotal - obj.sizequeue; + if (obj.speed && obj.speed > 0) + obj.eta = Math.round(obj.sizequeue / obj.speed); + else if (obj.sizequeue > 0) + obj.eta = Infinity; + else + obj.eta = 0; + + return obj; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Setup.js b/pyload/web/app/scripts/models/Setup.js new file mode 100644 index 000000000..424edf452 --- /dev/null +++ b/pyload/web/app/scripts/models/Setup.js @@ -0,0 +1,34 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + url: App.apiUrl('setup'), + defaults: { + lang: 'en', + system: null, + deps: null, + user: null, + password: null + }, + + fetch: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup'); + return Backbone.Model.prototype.fetch.call(this, options); + }, + + // will get a 409 on success + submit: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup_done'); + options.data = { + user: this.get('user'), + password: this.get('password') + }; + return Backbone.Model.prototype.fetch.call(this, options); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/TreeCollection.js b/pyload/web/app/scripts/models/TreeCollection.js new file mode 100644 index 000000000..2f761e6cc --- /dev/null +++ b/pyload/web/app/scripts/models/TreeCollection.js @@ -0,0 +1,50 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Package', 'collections/FileList', 'collections/PackageList'], + function($, Backbone, _, App, Package, FileList, PackageList) { + 'use strict'; + + // 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; + + options = App.apiRequest( + 'getFileTree/' + pid, + {full: false}, + options); + + console.log('Fetching package tree ' + pid); + return Backbone.Model.prototype.fetch.call(this, options); + }, + + // Parse the response and updates the collections + parse: function(resp) { + var ret = {}; + if (!this.has('packages')) + ret.packages = new PackageList(_.values(resp.packages)); + else + this.get('packages').set(_.values(resp.packages)); + + if (!this.has('files')) + ret.files = new FileList(_.values(resp.files)); + else + this.get('files').set(_.values(resp.files)); + + ret.root = new Package(resp.root); + return ret; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/UserSession.js b/pyload/web/app/scripts/models/UserSession.js new file mode 100644 index 000000000..7bf6abd8f --- /dev/null +++ b/pyload/web/app/scripts/models/UserSession.js @@ -0,0 +1,38 @@ +define(['jquery', 'backbone', 'underscore', 'utils/apitypes', 'app'], + function($, Backbone, _, Api, App) { + 'use strict'; + + // Used in app -> can not have a dependency on app + return Backbone.Model.extend({ + + idAttribute: 'name', + + defaults: { + uid: -1, + name: 'User', + permissions: null, + session: null + }, + + // Model Constructor + initialize: function() { + this.set(JSON.parse(localStorage.getItem('user'))); + }, + + save: function() { + localStorage.setItem('user', JSON.stringify(this.toJSON())); + }, + + destroy: function() { + localStorage.removeItem('user'); + }, + + // TODO + fetch: function(options) { + options = App.apiRequest('todo', null, options); + + return Backbone.Model.prototype.fetch.call(this, options); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/router.js b/pyload/web/app/scripts/router.js new file mode 100644 index 000000000..68ea5575d --- /dev/null +++ b/pyload/web/app/scripts/router.js @@ -0,0 +1,29 @@ +/** + * Router defines routes that are handled by registered controller + */ +define([ + // Libraries + 'backbone', + 'marionette', + + // Modules + 'controller' +], + function(Backbone, Marionette, Controller) { + 'use strict'; + + return Backbone.Marionette.AppRouter.extend({ + + appRoutes: { + '': 'dashboard', + 'login': 'login', + 'logout': 'logout', + 'settings': 'settings', + 'accounts': 'accounts', + 'admin': 'admin' + }, + + // Our controller to handle the routes + controller: Controller + }); + }); diff --git a/pyload/web/app/scripts/routers/defaultRouter.js b/pyload/web/app/scripts/routers/defaultRouter.js new file mode 100644 index 000000000..4b00d160c --- /dev/null +++ b/pyload/web/app/scripts/routers/defaultRouter.js @@ -0,0 +1,30 @@ +define(['jquery', 'backbone', 'views/headerView'], function($, Backbone, HeaderView) { + 'use strict'; + + 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/pyload/web/app/scripts/routers/mobileRouter.js b/pyload/web/app/scripts/routers/mobileRouter.js new file mode 100644 index 000000000..e24cb7a34 --- /dev/null +++ b/pyload/web/app/scripts/routers/mobileRouter.js @@ -0,0 +1,56 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { + 'use strict'; + + 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/pyload/web/app/scripts/setup.js b/pyload/web/app/scripts/setup.js new file mode 100644 index 000000000..94d370078 --- /dev/null +++ b/pyload/web/app/scripts/setup.js @@ -0,0 +1,33 @@ +/** + * Router and controller used in setup mode + */ +define([ + // Libraries + 'backbone', + 'marionette', + 'app', + + // Views + 'views/setup/setupView' +], + function(Backbone, Marionette, App, SetupView) { + 'use strict'; + + return Backbone.Marionette.AppRouter.extend({ + + appRoutes: { + '': 'setup' + }, + + controller: { + + setup: function() { + + var view = new SetupView(); + App.actionbar.show(new view.actionbar()); + App.content.show(view); + } + + } + }); + }); diff --git a/pyload/web/app/scripts/utils/animations.js b/pyload/web/app/scripts/utils/animations.js new file mode 100644 index 000000000..7f89afef1 --- /dev/null +++ b/pyload/web/app/scripts/utils/animations.js @@ -0,0 +1,129 @@ +define(['jquery', 'underscore', 'transit'], function(jQuery, _) { + 'use strict'; + + // Adds an element and computes its height, which is saved as data attribute + // Important function to have slide animations + jQuery.fn.appendWithHeight = function(element, hide) { + var o = jQuery(this[0]); + element = jQuery(element); + + // TODO: additionally it could be placed out of viewport first + // The real height can only be retrieved when element is on DOM and display:true + element.css('visibility', 'hidden'); + o.append(element); + + var height = element.height(); + + // Hide the element + if (hide === true) { + element.hide(); + element.height(0); + } + + element.css('visibility', ''); + element.data('height', height); + + return this; + }; + + // Shortcut to have a animation when element is added + jQuery.fn.appendWithAnimation = function(element, animation) { + var o = jQuery(this[0]); + element = jQuery(element); + + if (animation === true) + element.hide(); + + o.append(element); + + if (animation === true) + element.fadeIn(); + +// element.calculateHeight(); + + return this; + }; + + // calculate the height and write it to data, should be used on invisible elements + jQuery.fn.calculateHeight = function(setHeight) { + var o = jQuery(this[0]); + var height = o.height(); + if (!height) { + var display = o.css('display'); + o.css('visibility', 'hidden'); + o.show(); + height = o.height(); + + o.css('display', display); + o.css('visibility', ''); + } + + if (setHeight) + o.css('height', height); + + o.data('height', height); + return this; + }; + + // TODO: carry arguments, optional height argument + + // reset arguments, sets overflow hidden + jQuery.fn.slideOut = function(reset) { + var o = jQuery(this[0]); + o.animate({height: o.data('height'), opacity: 'show'}, function() { + // reset css attributes; + if (reset) { + this.css('overflow', ''); + this.css('height', ''); + } + }); + return this; + }; + + jQuery.fn.slideIn = function(reset) { + var o = jQuery(this[0]); + if (reset) { + o.css('overflow', 'hidden'); + } + o.animate({height: 0, opacity: 'hide'}); + return this; + }; + + jQuery.fn.initTooltips = function(placement) { + placement || (placement = 'top'); + + var o = jQuery(this[0]); + o.find('[data-toggle="tooltip"]').tooltip( + { + delay: {show: 800, hide: 100}, + placement: placement + }); + + return this; + }; + + jQuery.fn._transit = jQuery.fn.transit; + + // Overriding transit plugin to support hide and show + jQuery.fn.transit = jQuery.fn.transition = function(props, duration, easing, callback) { + var self = this; + var cb = callback; + var newprops = _.extend({}, props); + + if (newprops && (newprops.opacity === 'hide')) { + newprops.opacity = 0; + + callback = function() { + self.css({display: 'none'}); + if (typeof cb === 'function') { + cb.apply(self); + } + }; + } else if (newprops && (newprops.opacity === 'show')) { + newprops.opacity = 1; + this.css({display: 'block'}); + } + + return this._transit(newprops, duration, easing, callback); + }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/apitypes.js b/pyload/web/app/scripts/utils/apitypes.js new file mode 100644 index 000000000..cb094a05b --- /dev/null +++ b/pyload/web/app/scripts/utils/apitypes.js @@ -0,0 +1,16 @@ +// Autogenerated, do not edit! +/*jslint -W070: false*/ +define([], function() { + 'use strict'; + return { + DownloadState: {'Failed': 3, 'All': 0, 'Unmanaged': 4, 'Finished': 1, 'Unfinished': 2}, + DownloadStatus: {'NotPossible': 13, 'Downloading': 10, 'NA': 0, 'Processing': 15, 'Waiting': 9, 'Decrypting': 14, 'Paused': 4, 'Failed': 7, 'Finished': 5, 'Skipped': 6, 'Unknown': 17, 'Aborted': 12, 'Online': 2, 'TempOffline': 11, 'Offline': 1, 'Custom': 16, 'Starting': 8, 'Queued': 3}, + FileStatus: {'Remote': 2, 'Ok': 0, 'Missing': 1}, + InputType: {'PluginList': 13, 'Multiple': 11, 'Int': 2, 'NA': 0, 'Time': 7, 'List': 12, 'Bool': 8, 'File': 3, 'Text': 1, 'Table': 14, 'Folder': 4, 'Password': 6, 'Click': 9, 'Select': 10, 'Textbox': 5}, + Interaction: {'Captcha': 2, 'All': 0, 'Query': 4, 'Notification': 1}, + MediaType: {'All': 0, 'Audio': 2, 'Image': 4, 'Executable': 64, 'Other': 1, 'Video': 8, 'Document': 16, 'Archive': 32}, + PackageStatus: {'Paused': 1, 'Remote': 3, 'Folder': 2, 'Ok': 0}, + Permission: {'All': 0, 'Interaction': 32, 'Modify': 4, 'Add': 1, 'Accounts': 16, 'Plugins': 64, 'Download': 8, 'Delete': 2}, + Role: {'Admin': 0, 'User': 1}, + }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/dialogs.js b/pyload/web/app/scripts/utils/dialogs.js new file mode 100644 index 000000000..3ceffc9c3 --- /dev/null +++ b/pyload/web/app/scripts/utils/dialogs.js @@ -0,0 +1,15 @@ +// Loads all helper and set own handlebars rules +define(['jquery', 'underscore', 'views/abstract/modalView'], function($, _, Modal) { + 'use strict'; + + // Shows the confirm dialog for given context + // on success executes func with context + _.confirm = function(template, func, context) { + template = 'hbs!tpl/' + template; + _.requireOnce([template], function(html) { + var dialog = new Modal(html, _.bind(func, context)); + dialog.show(); + }); + + }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/i18n.js b/pyload/web/app/scripts/utils/i18n.js new file mode 100644 index 000000000..a8d948b4a --- /dev/null +++ b/pyload/web/app/scripts/utils/i18n.js @@ -0,0 +1,5 @@ +define(['jed'], function(Jed) { + 'use strict'; + // TODO load i18n data + return new Jed({}); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/lazyRequire.js b/pyload/web/app/scripts/utils/lazyRequire.js new file mode 100644 index 000000000..96c07aa24 --- /dev/null +++ b/pyload/web/app/scripts/utils/lazyRequire.js @@ -0,0 +1,97 @@ +// Define the module. +define( + [ + 'require', 'underscore' + ], + function( require, _ ){ + 'use strict'; + + + // 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 ); + + }; + + + // -------------------------------------------------- // + // -------------------------------------------------- // + + // Set up holder for underscore + var instances = {}; + _.requireOnce = function(dependencies, loadCallback) { + if (!_.has(instances, dependencies)) + instances[dependencies] = lazyRequire.once(); + + return instances[dependencies](dependencies, loadCallback); + }; + + + // Return the module definition. + return( lazyRequire ); + } +);
\ No newline at end of file diff --git a/pyload/web/app/scripts/vendor/jquery.omniwindow.js b/pyload/web/app/scripts/vendor/jquery.omniwindow.js new file mode 100644 index 000000000..e1f0b8f77 --- /dev/null +++ b/pyload/web/app/scripts/vendor/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/pyload/web/app/scripts/vendor/remaining.js b/pyload/web/app/scripts/vendor/remaining.js new file mode 100644 index 000000000..d66a2931a --- /dev/null +++ b/pyload/web/app/scripts/vendor/remaining.js @@ -0,0 +1,149 @@ +/** + * Javascript Countdown + * Copyright (c) 2009 Markus Hedlund + * Version 1.1 + * Licensed under MIT license + * http://www.opensource.org/licenses/mit-license.php + * http://labs.mimmin.com/countdown + */ +define([], function() { + var remaining = { + /** + * Get the difference of the passed date, and now. The different formats of the taget parameter are: + * January 12, 2009 15:14:00 (Month dd, yyyy hh:mm:ss) + * January 12, 2009 (Month dd, yyyy) + * 09,00,12,15,14,00 (yy,mm,dd,hh,mm,ss) Months range from 0-11, not 1-12. + * 09,00,12 (yy,mm,dd) Months range from 0-11, not 1-12. + * 500 (milliseconds) + * 2009-01-12 15:14:00 (yyyy-mm-dd hh-mm-ss) + * 2009-01-12 15:14 (yyyy-mm-dd hh-mm) + * @param target Target date. Can be either a date object or a string (formated like '24 December, 2010 15:00:00') + * @return Difference in seconds + */ + getSeconds: function(target) { + var today = new Date(); + + if (typeof(target) == 'object') { + var targetDate = target; + } else { + var matches = target.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})(:(\d{2}))?/); // YYYY-MM-DD HH-MM-SS + if (matches != null) { + matches[7] = typeof(matches[7]) == 'undefined' ? '00' : matches[7]; + var targetDate = new Date(matches[1], matches[2] - 1, matches[3], matches[4], matches[5], matches[7]); + } else { + var targetDate = new Date(target); + } + } + + return Math.floor((targetDate.getTime() - today.getTime()) / 1000); + }, + + /** + * @param seconds Difference in seconds + * @param i18n A language object (see code) + * @param onlyLargestUnit Return only the largest unit (see documentation) + * @param hideEmpty Hide empty units (see documentation) + * @return String formated something like '1 week, 1 hours, 1 second' + */ + getString: function(seconds, i18n, onlyLargestUnit, hideEmpty) { + if (seconds < 1) { + return ''; + } + + if (typeof(hideEmpty) == 'undefined' || hideEmpty == null) { + hideEmpty = true; + } + if (typeof(onlyLargestUnit) == 'undefined' || onlyLargestUnit == null) { + onlyLargestUnit = false; + } + if (typeof(i18n) == 'undefined' || i18n == null) { + i18n = { + weeks: ['week', 'weeks'], + days: ['day', 'days'], + hours: ['hour', 'hours'], + minutes: ['minute', 'minutes'], + seconds: ['second', 'seconds'] + }; + } + + var units = { + weeks: 7 * 24 * 60 * 60, + days: 24 * 60 * 60, + hours: 60 * 60, + minutes: 60, + seconds: 1 + }; + + var returnArray = []; + var value; + for (unit in units) { + value = units[unit]; + if (seconds / value >= 1 || unit == 'seconds' || !hideEmpty) { + secondsConverted = Math.floor(seconds / value); + var i18nUnit = i18n[unit][secondsConverted == 1 ? 0 : 1]; + returnArray.push(secondsConverted + ' ' + i18nUnit); + seconds -= secondsConverted * value; + + if (onlyLargestUnit) { + break; + } + } + } + ; + + return returnArray.join(', '); + }, + + /** + * @param seconds Difference in seconds + * @return String formated something like '169:00:01' + */ + getStringDigital: function(seconds) { + if (seconds < 1) { + return ''; + } + + remainingTime = remaining.getArray(seconds); + + for (index in remainingTime) { + remainingTime[index] = remaining.padNumber(remainingTime[index]); + } + ; + + return remainingTime.join(':'); + }, + + /** + * @param seconds Difference in seconds + * @return Array with hours, minutes and seconds + */ + getArray: function(seconds) { + if (seconds < 1) { + return []; + } + + var units = [60 * 60, 60, 1]; + + var returnArray = []; + var value; + for (index in units) { + value = units[index]; + secondsConverted = Math.floor(seconds / value); + returnArray.push(secondsConverted); + seconds -= secondsConverted * value; + } + ; + + return returnArray; + }, + + /** + * @param number An integer + * @return Integer padded with a 0 if necessary + */ + padNumber: function(number) { + return (number >= 0 && number < 10) ? '0' + number : number; + } + }; + return remaining; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/abstract/itemView.js b/pyload/web/app/scripts/views/abstract/itemView.js new file mode 100644 index 000000000..c37118a4c --- /dev/null +++ b/pyload/web/app/scripts/views/abstract/itemView.js @@ -0,0 +1,47 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { + 'use strict'; + + // A view that is meant for temporary displaying + // All events must be unbound in onDestroy + return Backbone.View.extend({ + + tagName: 'li', + destroy: function() { + this.undelegateEvents(); + this.unbind(); + if (this.onDestroy) { + this.onDestroy(); + } + this.$el.removeData().unbind(); + this.remove(); + }, + + hide: function() { + this.$el.slideUp(); + }, + + show: function() { + this.$el.slideDown(); + }, + + unrender: function() { + var self = this; + this.$el.slideUp(function() { + self.destroy(); + }); + }, + + deleteItem: function(e) { + if (e) + e.stopPropagation(); + this.model.destroy(); + }, + + restart: function(e) { + if(e) + e.stopPropagation(); + this.model.restart(); + } + + }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/abstract/modalView.js b/pyload/web/app/scripts/views/abstract/modalView.js new file mode 100644 index 000000000..61016a9fb --- /dev/null +++ b/pyload/web/app/scripts/views/abstract/modalView.js @@ -0,0 +1,130 @@ +define(['jquery', 'backbone', 'underscore', 'omniwindow'], function($, Backbone, _) { + 'use strict'; + + return Backbone.View.extend({ + + events: { + 'click .btn-confirm': 'confirm', + 'click .btn-close': 'hide', + 'click .close': 'hide' + }, + + template: null, + dialog: null, + + onHideDestroy: false, + confirmCallback: null, + + initialize: function(template, confirm) { + this.confirmCallback = confirm; + var self = this; + if (this.template === null) { + if (template) { + this.template = template; + // When template was provided this is a temporary dialog + this.onHideDestroy = true; + } + else + require(['hbs!tpl/dialogs/modal'], function(template) { + self.template = template; + }); + } + }, + + // TODO: whole modal stuff is not very elegant + render: function() { + this.$el.html(this.template(this.renderContent())); + this.onRender(); + + if (this.dialog === null) { + this.$el.addClass('modal hide'); + this.$el.css({opacity: 0, scale: 0.7}); + + var self = this; + $('body').append(this.el); + this.dialog = this.$el.omniWindow({ + overlay: { + selector: '#modal-overlay', + hideClass: 'hide', + animations: { + hide: function(subjects, internalCallback) { + subjects.overlay.transition({opacity: 'hide', delay: 100}, 300, function() { + internalCallback(subjects); + self.onHide(); + if (self.onHideDestroy) + self.destroy(); + }); + }, + show: function(subjects, internalCallback) { + subjects.overlay.fadeIn(300); + internalCallback(subjects); + }}}, + modal: { + hideClass: 'hide', + animations: { + hide: function(subjects, internalCallback) { + subjects.modal.transition({opacity: 'hide', scale: 0.7}, 300); + internalCallback(subjects); + }, + + show: function(subjects, internalCallback) { + subjects.modal.transition({opacity: 'show', scale: 1, delay: 100}, 300, function() { + internalCallback(subjects); + }); + }} + }}); + } + + return this; + }, + + onRender: function() { + + }, + + renderContent: function() { + if (this.model) + return this.model.toJSON(); + return {}; + }, + + show: function() { + if (this.dialog === null) + this.render(); + + this.dialog.trigger('show'); + + this.onShow(); + }, + + onShow: function() { + + }, + + hide: function() { + this.dialog.trigger('hide'); + }, + + onHide: function() { + + }, + + confirm: function() { + if (this.confirmCallback) + this.confirmCallback.apply(); + + this.hide(); + }, + + destroy: function() { + this.onDestroy(); + this.$el.remove(); + this.dialog = null; + this.remove(); + }, + + onDestroy: function() { + + } + }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountEdit.js b/pyload/web/app/scripts/views/accounts/accountEdit.js new file mode 100644 index 000000000..503860a5e --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountEdit.js @@ -0,0 +1,41 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'views/input/inputRenderer', 'hbs!tpl/accounts/editAccount', 'hbs!tpl/settings/configItem'], + function($, _, App, modalView, renderForm, template, templateItem) { + 'use strict'; + return modalView.extend({ + + events: { + 'click .btn-save': 'save', + 'submit form': 'save' + }, + + template: template, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + }, + + onRender: function() { + renderForm(this.$('.account-config'), + this.model.get('config'), + templateItem + ); + }, + + save: function() { + var password = this.$('#password').val(); + if (password !== '') { + this.model.setPassword(password); + } + this.model.save(); + this.hide(); + return false; + }, + + onShow: function() { + }, + + onHide: function() { + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountListView.js b/pyload/web/app/scripts/views/accounts/accountListView.js new file mode 100644 index 000000000..37bfba964 --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountListView.js @@ -0,0 +1,52 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'collections/AccountList', './accountView', + 'hbs!tpl/accounts/layout', 'hbs!tpl/accounts/actionbar'], + function($, _, Backbone, App, AccountList, accountView, template, templateBar) { + 'use strict'; + + // Renders settings over view page + return Backbone.Marionette.CollectionView.extend({ + + itemView: accountView, + template: template, + + collection: null, + modal: null, + + initialize: function() { + this.actionbar = Backbone.Marionette.ItemView.extend({ + template: templateBar, + events: { + 'click .btn': 'addAccount' + }, + addAccount: _.bind(this.addAccount, this) + }); + + this.collection = new AccountList(); + this.update(); + + this.listenTo(App.vent, 'account:updated', this.update); + }, + + update: function() { + this.collection.fetch(); + }, + + onBeforeRender: function() { + this.$el.html(template()); + }, + + appendHtml: function(collectionView, itemView, index) { + this.$('.account-list').append(itemView.el); + }, + + addAccount: function() { + var self = this; + _.requireOnce(['views/accounts/accountModal'], function(Modal) { + if (self.modal === null) + self.modal = new Modal(); + + self.modal.show(); + }); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountModal.js b/pyload/web/app/scripts/views/accounts/accountModal.js new file mode 100644 index 000000000..31e05dff6 --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountModal.js @@ -0,0 +1,72 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/addAccount', 'helpers/pluginIcon', 'select2'], + function($, _, App, modalView, template, pluginIcon) { + 'use strict'; + return modalView.extend({ + + events: { + 'submit form': 'add', + 'click .btn-add': 'add' + }, + template: template, + plugins: null, + select: null, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + var self = this; + $.ajax(App.apiRequest('getAccountTypes', null, {success: function(data) { + self.plugins = _.sortBy(data, function(item) { + return item; + }); + self.render(); + }})); + }, + + onRender: function() { + // TODO: could be a separate input type if needed on multiple pages + if (this.plugins) + this.select = this.$('#pluginSelect').select2({ + escapeMarkup: function(m) { + return m; + }, + formatResult: this.format, + formatSelection: this.format, + data: {results: this.plugins, text: function(item) { + return item; + }}, + id: function(item) { + return item; + } + }); + }, + + onShow: function() { + }, + + onHide: function() { + }, + + format: function(data) { + return '<img class="logo-select" src="' + pluginIcon(data) + '"> ' + data; + }, + + add: function(e) { + e.stopPropagation(); + if (this.select) { + var plugin = this.select.val(), + login = this.$('#login').val(), + password = this.$('#password').val(), + self = this; + + $.ajax(App.apiRequest('updateAccount', { + plugin: plugin, loginname: login, password: password + }, { success: function(data) { + App.vent.trigger('account:updated', data); + self.hide(); + }})); + } + return false; + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountView.js b/pyload/web/app/scripts/views/accounts/accountView.js new file mode 100644 index 000000000..f49deb0a6 --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountView.js @@ -0,0 +1,48 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'hbs!tpl/accounts/account'], + function($, _, Backbone, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + tagName: 'div', + className: 'row-fluid', + template: template, + + events: { + 'click .btn-success': 'toggle', + 'click .btn-blue': 'edit', + 'click .btn-yellow': 'refresh', + 'click .btn-danger': 'deleteAccount' + }, + + modelEvents: { + 'change': 'render' + }, + + modal: null, + + toggle: function() { + this.model.set('activated', !this.model.get('activated')); + this.model.save(); + }, + + edit: function() { + // TODO: clean the modal on view close + var self = this; + _.requireOnce(['views/accounts/accountEdit'], function(Modal) { + if (self.modal === null) + self.modal = new Modal({model: self.model}); + + self.modal.show(); + }); + }, + + refresh: function() { + this.model.fetch({refresh: true}); + }, + + deleteAccount: function() { + this.model.destroy(); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/dashboardView.js b/pyload/web/app/scripts/views/dashboard/dashboardView.js new file mode 100644 index 000000000..d98e28fe3 --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/dashboardView.js @@ -0,0 +1,172 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/TreeCollection', 'collections/FileList', + './packageView', './fileView', 'hbs!tpl/dashboard/layout', 'select2'], + function($, Backbone, _, App, TreeCollection, FileList, PackageView, FileView, template) { + 'use strict'; + // Renders whole dashboard + return Backbone.Marionette.ItemView.extend({ + + template: template, + + events: { + }, + + ui: { + 'packages': '.package-list', + 'files': '.file-list' + }, + + // Package tree + tree: null, + // Current open files + files: null, + // True when loading animation is running + isLoading: false, + + initialize: function() { + App.dashboard = this; + this.tree = new TreeCollection(); + + var self = this; + // When package is added we reload the data + this.listenTo(App.vent, 'package:added', function() { + console.log('Package tree caught, package:added event'); + self.tree.fetch(); + }); + + this.listenTo(App.vent, 'file:updated', _.bind(this.fileUpdated, this)); + + // TODO: merge? + this.init(); + // TODO: file:added + // TODO: package:deleted + // TODO: package:updated + }, + + init: function() { + var self = this; + // TODO: put in separated function + // TODO: order of elements? + // Init the tree and callback for package added + this.tree.fetch({success: function() { + self.update(); + self.tree.get('packages').on('add', function(pack) { + console.log('Package ' + pack.get('pid') + ' added to tree'); + self.appendPackage(pack, 0, true); + self.openPackage(pack); + }); + }}); + + this.$('.input').select2({tags: ['a', 'b', 'sdf']}); + }, + + update: function() { + console.log('Update package list'); + + var packs = this.tree.get('packages'); + this.files = this.tree.get('files'); + + if (packs) + packs.each(_.bind(this.appendPackage, this)); + + if (!this.files || this.files.length === 0) { + // no files are displayed + this.files = null; + // Open the first package + if (packs && packs.length >= 1) + this.openPackage(packs.at(0)); + } + else + this.files.each(_.bind(this.appendFile, this)); + + return this; + }, + + // TODO sorting ?! + // Append a package to the list, index, animate it + appendPackage: function(pack, i, animation) { + var el = new PackageView({model: pack}).render().el; + $(this.ui.packages).appendWithAnimation(el, animation); + }, + + appendFile: function(file, i, animation) { + var el = new FileView({model: file}).render().el; + $(this.ui.files).appendWithAnimation(el, animation); + }, + + // Show content of the packages on main view + openPackage: function(pack) { + var self = this; + + // load animation only when something is shown and its different from current package + if (this.files && this.files !== pack.get('files')) + self.loading(); + + pack.fetch({silent: true, success: function() { + console.log('Package ' + pack.get('pid') + ' loaded'); + self.contentReady(pack.get('files')); + }, failure: function() { + self.failure(); + }}); + + }, + + contentReady: function(files) { + var old_files = this.files; + this.files = files; + App.vent.trigger('dashboard:contentReady'); + + // show the files when no loading animation is running and not already open + if (!this.isLoading && old_files !== files) + this.show(); + }, + + // Do load animation, remove the old stuff + loading: function() { + this.isLoading = true; + this.files = null; + var self = this; + $(this.ui.files).fadeOut({complete: function() { + // All file views should vanish + App.vent.trigger('dashboard:destroyContent'); + + // Loading was faster than animation + if (self.files) + self.show(); + + self.isLoading = false; + }}); + }, + + failure: function() { + // TODO + }, + + show: function() { + // fileUL has to be resetted before + this.files.each(_.bind(this.appendFile, this)); + //TODO: show placeholder when nothing is displayed (filtered content empty) + $(this.ui.files).fadeIn(); + App.vent.trigger('dashboard:updated'); + }, + + // Refresh the file if it is currently shown + fileUpdated: function(data) { + var fid; + if (_.isObject(data)) + fid = data.fid; + else + fid = data; + // this works with ids and object TODO: not anymore + var file = this.files.get(fid); + if (file) + if (_.isObject(data)) { // update directly + file.set(data); + App.vent.trigger('dashboard:updated'); + } else { // fetch from server + file.fetch({success: function() { + App.vent.trigger('dashboard:updated'); + }}); + } + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/fileView.js b/pyload/web/app/scripts/views/dashboard/fileView.js new file mode 100644 index 000000000..ed2d2ea40 --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/fileView.js @@ -0,0 +1,103 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'views/abstract/itemView', 'helpers/formatTimeLeft', 'hbs!tpl/dashboard/file'], + function($, Backbone, _, App, Api, ItemView, formatTime, template) { + 'use strict'; + + // Renders single file item + return ItemView.extend({ + + tagName: 'li', + className: 'file-view row-fluid', + template: template, + events: { + 'click .checkbox': 'select', + 'click .btn-delete': 'deleteItem', + 'click .btn-restart': 'restart' + }, + + initialize: function() { + this.listenTo(this.model, 'change', this.render); + // This will be triggered manually and changed before with silent=true + this.listenTo(this.model, 'change:visible', this.visibility_changed); + this.listenTo(this.model, 'change:progress', this.progress_changed); + this.listenTo(this.model, 'remove', this.unrender); + this.listenTo(App.vent, 'dashboard:destroyContent', this.destroy); + }, + + onDestroy: function() { + }, + + render: function() { + var data = this.model.toJSON(); + if (data.download) { + var status = data.download.status; + if (status === Api.DownloadStatus.Offline || status === Api.DownloadStatus.TempOffline) + data.offline = true; + else if (status === Api.DownloadStatus.Online) + data.online = true; + else if (status === Api.DownloadStatus.Waiting) + data.waiting = true; + else if (status === Api.DownloadStatus.Downloading) + data.downloading = true; + else if (this.model.isFailed()) + data.failed = true; + else if (this.model.isFinished()) + data.finished = true; + } + + this.$el.html(this.template(data)); + if (this.model.get('selected')) + this.$el.addClass('ui-selected'); + else + this.$el.removeClass('ui-selected'); + + if (this.model.get('visible')) + this.$el.show(); + else + this.$el.hide(); + + return this; + }, + + select: function(e) { + e.preventDefault(); + var checked = this.$el.hasClass('ui-selected'); + // toggle class immediately, so no re-render needed + this.model.set('selected', !checked, {silent: true}); + this.$el.toggleClass('ui-selected'); + App.vent.trigger('file:selection'); + }, + + visibility_changed: function(visible) { + // TODO: improve animation, height is not available when element was not visible + if (visible) + this.$el.slideOut(true); + else { + this.$el.calculateHeight(true); + this.$el.slideIn(true); + } + }, + + progress_changed: function() { + // TODO: progress for non download statuses + if (!this.model.isDownload()) + return; + + if (this.model.get('download').status === Api.DownloadStatus.Downloading) { + var bar = this.$('.progress .bar'); + if (!bar) { // ensure that the dl bar is rendered + this.render(); + bar = this.$('.progress .bar'); + } + + bar.width(this.model.get('progress') + '%'); + bar.html(' ' + formatTime(this.model.get('eta'))); + } else if (this.model.get('download').status === Api.DownloadStatus.Waiting) { + this.$('.second').html( + '<i class="icon-time"></i> ' + formatTime(this.model.get('eta'))); + + } else // Every else state can be rendered normally + this.render(); + + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/filterView.js b/pyload/web/app/scripts/views/dashboard/filterView.js new file mode 100644 index 000000000..9d8c46e4e --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/filterView.js @@ -0,0 +1,167 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'models/Package', 'hbs!tpl/dashboard/actionbar'], + /*jslint -W040: false*/ + function($, Backbone, _, App, Api, Package, template) { + 'use strict'; + + // Modified version of type ahead show, nearly the same without absolute positioning + function show() { + this.$menu + .insertAfter(this.$element) + .show(); + + this.shown = true; + return this; + } + + // Renders the actionbar for the dashboard, handles everything related to filtering displayed files + return Backbone.Marionette.ItemView.extend({ + + events: { + 'click .li-check': 'toggle_selection', + 'click .filter-type': 'filter_type', + 'click .filter-state': 'switch_filter', + 'submit .form-search': 'search' + }, + + ui: { + 'search': '.search-query', + 'stateMenu': '.dropdown-toggle .state', + 'select': '.btn-check', + 'name': '.breadcrumb .active' + }, + + template: template, + // Visible dl state + state: null, + // bit mask of filtered, thus not visible media types + types: 0, + + initialize: function() { + this.state = Api.DownloadState.All; + + // Apply the filter before the content is shown + this.listenTo(App.vent, 'dashboard:contentReady', this.apply_filter); + this.listenTo(App.vent, 'dashboard:updated', this.apply_filter); + this.listenTo(App.vent, 'dashboard:updated', this.updateName); + }, + + onRender: function() { + // use our modified method + $.fn.typeahead.Constructor.prototype.show = show; + this.ui.search.typeahead({ + minLength: 2, + source: this.getSuggestions + }); + + }, + + // TODO: app level api request + search: function(e) { + e.stopPropagation(); + var query = this.ui.search.val(); + this.ui.search.val(''); + + var pack = new Package(); + // Overwrite fetch method to use a search + // TODO: quite hackish, could be improved to filter packages + // or show performed search + pack.fetch = function(options) { + pack.search(query, options); + }; + + App.dashboard.openPackage(pack); + }, + + getSuggestions: function(query, callback) { + $.ajax(App.apiRequest('searchSuggestions', {pattern: query}, { + method: 'POST', + success: function(data) { + callback(data); + } + })); + }, + + switch_filter: function(e) { + e.stopPropagation(); + var element = $(e.target); + var state = parseInt(element.data('state'), 10); + var menu = this.ui.stateMenu.parent().parent(); + menu.removeClass('open'); + + if (state === Api.DownloadState.Finished) { + menu.removeClass().addClass('dropdown finished'); + } else if (state === Api.DownloadState.Unfinished) { + menu.removeClass().addClass('dropdown active'); + } else if (state === Api.DownloadState.Failed) { + menu.removeClass().addClass('dropdown failed'); + } else { + menu.removeClass().addClass('dropdown'); + } + + this.state = state; + this.ui.stateMenu.text(element.text()); + this.apply_filter(); + }, + + // Applies the filtering to current open files + apply_filter: function() { + if (!App.dashboard.files) + return; + + var self = this; + App.dashboard.files.map(function(file) { + var visible = file.get('visible'); + if (visible !== self.is_visible(file)) { + file.set('visible', !visible, {silent: true}); + file.trigger('change:visible', !visible); + } + }); + + App.vent.trigger('dashboard:filtered'); + }, + + // determine if a file should be visible + // TODO: non download files + is_visible: function(file) { + // bit is set -> not visible + if (file.get('media') & this.types) + return false; + + if (this.state === Api.DownloadState.Finished) + return file.isFinished(); + else if (this.state === Api.DownloadState.Unfinished) + return file.isUnfinished(); + else if (this.state === Api.DownloadState.Failed) + return file.isFailed(); + + return true; + }, + + updateName: function() { + // TODO +// this.ui.name.text(App.dashboard.package.get('name')); + }, + + toggle_selection: function() { + App.vent.trigger('selection:toggle'); + }, + + filter_type: function(e) { + var el = $(e.target); + var type = parseInt(el.data('type'), 10); + + // Bit is already set, so type is not visible, will become visible now + if (type & this.types) { + el.find('i').removeClass('icon-remove').addClass('icon-ok'); + } else { // type will be hidden + el.find('i').removeClass('icon-ok').addClass('icon-remove'); + } + + this.types ^= type; + + this.apply_filter(); + return false; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/packageView.js b/pyload/web/app/scripts/views/dashboard/packageView.js new file mode 100644 index 000000000..2738fcbea --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/packageView.js @@ -0,0 +1,75 @@ +define(['jquery', 'app', 'views/abstract/itemView', 'underscore', 'hbs!tpl/dashboard/package'], + function($, App, itemView, _, template) { + 'use strict'; + + // Renders a single package item + return itemView.extend({ + + tagName: 'li', + className: 'package-view', + template: template, + events: { + 'click .package-name, .btn-open': 'open', + 'click .icon-refresh': 'restart', + 'click .select': 'select', + 'click .btn-delete': 'deleteItem' + }, + + // Ul for child packages (unused) + ul: null, + // Currently unused + expanded: false, + + initialize: function() { + this.listenTo(this.model, 'filter:added', this.hide); + this.listenTo(this.model, 'filter:removed', this.show); + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model, 'remove', this.unrender); + + // Clear drop down menu + var self = this; + this.$el.on('mouseleave', function() { + self.$('.dropdown-menu').parent().removeClass('open'); + }); + }, + + onDestroy: function() { + }, + + // Render everything, optional only the fileViews + render: function() { + this.$el.html(this.template(this.model.toJSON())); + this.$el.initTooltips(); + + return this; + }, + + unrender: function() { + itemView.prototype.unrender.apply(this); + + // TODO: display other package + App.vent.trigger('dashboard:loading', null); + }, + + + // TODO + // Toggle expanding of packages + expand: function(e) { + e.preventDefault(); + }, + + open: function(e) { + e.preventDefault(); + App.dashboard.openPackage(this.model); + }, + + select: function(e) { + e.preventDefault(); + var checked = this.$('.select').hasClass('icon-check'); + // toggle class immediately, so no re-render needed + this.model.set('selected', !checked, {silent: true}); + this.$('.select').toggleClass('icon-check').toggleClass('icon-check-empty'); + App.vent.trigger('package:selection'); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/selectionView.js b/pyload/web/app/scripts/views/dashboard/selectionView.js new file mode 100644 index 000000000..25b7998df --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/selectionView.js @@ -0,0 +1,154 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/dashboard/select'], + function($, Backbone, _, App, template) { + 'use strict'; + + // Renders context actions for selection packages and files + return Backbone.Marionette.ItemView.extend({ + + el: '#selection-area', + template: template, + + events: { + 'click .icon-check': 'deselect', + 'click .icon-pause': 'pause', + 'click .icon-trash': 'trash', + 'click .icon-refresh': 'restart' + }, + + // Element of the action bar + actionBar: null, + // number of currently selected elements + current: 0, + + initialize: function() { + this.$el.calculateHeight().height(0); + var render = _.bind(this.render, this); + + App.vent.on('dashboard:updated', render); + App.vent.on('dashboard:filtered', render); + App.vent.on('package:selection', render); + App.vent.on('file:selection', render); + App.vent.on('selection:toggle', _.bind(this.select_toggle, this)); + + + // API events, maybe better to rely on internal ones? + App.vent.on('package:deleted', render); + App.vent.on('file:deleted', render); + }, + + get_files: function(all) { + var files = []; + if (App.dashboard.files) + if (all) + files = App.dashboard.files.where({visible: true}); + else + files = App.dashboard.files.where({selected: true, visible: true}); + + return files; + }, + + get_packs: function() { + if (!App.dashboard.tree.get('packages')) + return []; // TODO + + return App.dashboard.tree.get('packages').where({selected: true}); + }, + + render: function() { + var files = this.get_files().length; + var packs = this.get_packs().length; + + if (files + packs > 0) { + this.$el.html(this.template({files: files, packs: packs})); + this.$el.initTooltips('bottom'); + } + + if (files + packs > 0 && this.current === 0) + this.$el.slideOut(); + else if (files + packs === 0 && this.current > 0) + this.$el.slideIn(); + + // TODO: accessing ui directly, should be events + if (files > 0) { + App.actionbar.currentView.ui.select.addClass('icon-check').removeClass('icon-check-empty'); + App.dashboard.ui.packages.addClass('ui-files-selected'); + } + else { + App.actionbar.currentView.ui.select.addClass('icon-check-empty').removeClass('icon-check'); + App.dashboard.ui.packages.removeClass('ui-files-selected'); + } + + this.current = files + packs; + }, + + // Deselects all items + deselect: function() { + this.get_files().map(function(file) { + file.set('selected', false); + }); + + this.get_packs().map(function(pack) { + pack.set('selected', false); + }); + + this.render(); + }, + + pause: function() { + alert('Not implemented yet'); + this.deselect(); + }, + + trash: function() { + _.confirm('dialogs/confirmDelete', function() { + + var pids = []; + // TODO: delete many at once + this.get_packs().map(function(pack) { + pids.push(pack.get('pid')); + pack.destroy(); + }); + + // get only the fids of non deleted packages + var fids = _.filter(this.get_files(),function(file) { + return !_.contains(pids, file.get('package')); + }).map(function(file) { + file.destroyLocal(); + return file.get('fid'); + }); + + if (fids.length > 0) + $.ajax(App.apiRequest('deleteFiles', {fids: fids})); + + this.deselect(); + }, this); + }, + + restart: function() { + this.get_files().map(function(file) { + file.restart(); + }); + this.get_packs().map(function(pack) { + pack.restart(); + }); + + this.deselect(); + }, + + // Select or deselect all visible files + select_toggle: function() { + var files = this.get_files(); + if (files.length === 0) { + this.get_files(true).map(function(file) { + file.set('selected', true); + }); + + } else + files.map(function(file) { + file.set('selected', false); + }); + + this.render(); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/headerView.js b/pyload/web/app/scripts/views/headerView.js new file mode 100644 index 000000000..60a47ad62 --- /dev/null +++ b/pyload/web/app/scripts/views/headerView.js @@ -0,0 +1,260 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'collections/ProgressList', + 'views/progressView', 'views/notificationView', 'helpers/formatSize', 'hbs!tpl/header/layout', + 'hbs!tpl/header/status', 'hbs!tpl/header/progressbar', 'hbs!tpl/header/progressSup', 'hbs!tpl/header/progressSub' , 'flot'], + function( + $, _, Backbone, App, ServerStatus, ProgressList, ProgressView, NotificationView, formatSize, template, templateStatus, templateProgress, templateSup, templateSub) { + 'use strict'; + // Renders the header with all information + return Backbone.Marionette.ItemView.extend({ + + modelEvents: { + 'change': 'render' + }, + + events: { + 'click .icon-list': 'toggle_taskList', + 'click .popover .close': 'toggle_taskList', + 'click .btn-grabber': 'open_grabber', + 'click .logo': 'gotoDashboard' + }, + + ui: { + progress: '.progress-list', + speedgraph: '#speedgraph' + }, + + template: template, + + // view + grabber: null, + speedgraph: null, + + // models and data + ws: null, + status: null, + progressList: null, + speeds: null, + + // sub view + notificationView: null, + + // save if last progress was empty + wasEmpty: false, + lastStatus: null, + + initialize: function() { + var self = this; + this.notificationView = new NotificationView(); + + this.model = App.user; + + this.status = new ServerStatus(); + this.listenTo(this.status, 'change', this.update); + + this.progressList = new ProgressList(); + this.listenTo(this.progressList, 'add', function(model) { + self.ui.progress.appendWithAnimation(new ProgressView({model: model}).render().el); + }); + + // TODO: button to start stop refresh + var ws = App.openWebSocket('/async'); + ws.onopen = function() { + ws.send(JSON.stringify('start')); + }; + // TODO compare with polling + ws.onmessage = _.bind(this.onData, this); + ws.onerror = function(error) { + console.log(error); + alert('WebSocket error' + error); + }; + ws.onclose = function() { + alert('WebSocket was closed'); + }; + + this.ws = ws; + }, + + gotoDashboard: function() { + App.navigate(''); + }, + + initGraph: function() { + var totalPoints = 120; + var data = []; + + // init with empty data + while (data.length < totalPoints) + data.push([data.length, 0]); + + this.speeds = data; + this.speedgraph = $.plot(this.ui.speedgraph, [this.speeds], { + series: { + lines: { show: true, lineWidth: 2 }, + shadowSize: 0, + color: '#fee247' + }, + xaxis: { ticks: [] }, + yaxis: { ticks: [], min: 1, autoscaleMargin: 0.1, tickFormatter: function(data) { + return formatSize(data * 1024); + }, position: 'right' }, + grid: { + show: true, +// borderColor: "#757575", + borderColor: 'white', + borderWidth: 1, + labelMargin: 0, + axisMargin: 0, + minBorderMargin: 0 + } + }); + + }, + + // Must be called after view was attached + init: function() { + this.initGraph(); + this.update(); + }, + + update: function() { + // TODO: what should be displayed in the header + // queue/processing size? + + var status = this.status.toJSON(); + status.maxspeed = _.max(this.speeds, function(speed) { + return speed[1]; + })[1] * 1024; + this.$('.status-block').html( + templateStatus(status) + ); + + var data = {tasks: 0, downloads: 0, speed: 0, single: false}; + this.progressList.each(function(progress) { + if (progress.isDownload()) { + data.downloads++; + data.speed += progress.get('download').speed; + } else + data.tasks++; + }); + + // Show progress of one task + if (data.tasks + data.downloads === 1) { + var progress = this.progressList.at(0); + data.single = true; + data.eta = progress.get('eta'); + data.percent = progress.getPercent(); + data.name = progress.get('name'); + data.statusmsg = progress.get('statusmsg'); + } + + data.etaqueue = status.eta; + data.linksqueue = status.linksqueue; + data.sizequeue = status.sizequeue; + + // Render progressbar only when needed + if (!_.isEqual([data.tasks, data.downloads], this.lastStatus)) { + this.lastStatus = [data.tasks, data.downloads]; + this.$('#progress-info').html(templateProgress(data)); + } else { + this.$('#progress-info .bar').width(data.percent + '%'); + } + + // render upper and lower part + this.$('.sup').html(templateSup(data)); + this.$('.sub').html(templateSub(data)); + + return this; + }, + + toggle_taskList: function() { + this.$('.popover').animate({opacity: 'toggle'}); + }, + + open_grabber: function() { + var self = this; + _.requireOnce(['views/linkgrabber/modalView'], function(ModalView) { + if (self.grabber === null) + self.grabber = new ModalView(); + + self.grabber.show(); + }); + }, + + onData: function(evt) { + var data = JSON.parse(evt.data); + if (data === null) return; + + if (data['@class'] === 'ServerStatus') { + this.status.set(data); + + // There tasks at the server, but not in queue: so fetch them + // or there are tasks in our queue but not on the server + if (this.status.get('notifications') && !this.notificationView.tasks.hasTaskWaiting() || + !this.status.get('notifications') && this.notificationView.tasks.hasTaskWaiting()) + this.notificationView.tasks.fetch(); + + this.speeds = this.speeds.slice(1); + this.speeds.push([this.speeds[this.speeds.length - 1][0] + 1, Math.floor(data.speed / 1024)]); + + // TODO: if everything is 0 re-render is not needed + this.speedgraph.setData([this.speeds]); + // adjust the axis + this.speedgraph.setupGrid(); + this.speedgraph.draw(); + + } + else if (_.isArray(data)) + this.onProgressUpdate(data); + else if (data['@class'] === 'EventInfo') + this.onEvent(data.eventname, data.event_args); + else + console.log('Unknown Async input', data); + + }, + + onProgressUpdate: function(progress) { + // generate a unique id + _.each(progress, function(prog) { + if (prog.download) + prog.pid = prog.download.fid; + else + prog.pid = prog.plugin + prog.name; + }); + + this.progressList.set(progress); + // update currently open files with progress + this.progressList.each(function(prog) { + if (prog.isDownload() && App.dashboard.files) { + var file = App.dashboard.files.get(prog.get('download').fid); + if (file) { + file.set({ + progress: prog.getPercent(), + eta: prog.get('eta'), + size: prog.get('total') + }, {silent: true}); + file.setDownloadStatus(prog.get('download').status); + file.trigger('change:progress'); + } + } + }); + + if (progress.length === 0) { + // only render one time when last was not empty already + if (!this.wasEmpty) { + this.update(); + this.wasEmpty = true; + } + } else { + this.wasEmpty = false; + this.update(); + } + }, + + onEvent: function(event, args) { + args.unshift(event); + console.log('Core send event', args); + App.vent.trigger.apply(App.vent, args); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/inputLoader.js b/pyload/web/app/scripts/views/input/inputLoader.js new file mode 100644 index 000000000..04d591d30 --- /dev/null +++ b/pyload/web/app/scripts/views/input/inputLoader.js @@ -0,0 +1,8 @@ +define(['./textInput'], function(textInput) { + 'use strict'; + + // selects appropriate input element + return function(input) { + return textInput; + }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/inputRenderer.js b/pyload/web/app/scripts/views/input/inputRenderer.js new file mode 100644 index 000000000..c20f15708 --- /dev/null +++ b/pyload/web/app/scripts/views/input/inputRenderer.js @@ -0,0 +1,22 @@ +define(['jquery', 'underscore', './inputLoader'], function($, _, load_input) { + 'use strict'; + + // Renders list of ConfigItems to an container + // Optionally binds change event to view + return function(container, items, template, onChange, view) { + _.each(items, function(item) { + var json = item.toJSON(); + var el = $('<div>').html(template(json)); + var InputView = load_input(item.get('input')); + var input = new InputView(json).render(); + item.set('inputView', input); + + if (_.isFunction(onChange) && view) { + view.listenTo(input, 'change', onChange); + } + + el.find('.controls').append(input.el); + container.append(el); + }); + }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/inputView.js b/pyload/web/app/scripts/views/input/inputView.js new file mode 100644 index 000000000..1860fcaf1 --- /dev/null +++ b/pyload/web/app/scripts/views/input/inputView.js @@ -0,0 +1,86 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { + 'use strict'; + + // Renders input elements + return Backbone.View.extend({ + + tagName: 'input', + + input: null, + value: null, + description: null, + default_value: null, + + // enables tooltips + tooltip: true, + + initialize: function(options) { + this.input = options.input; + this.default_value = this.input.default_value; + this.value = options.value; + this.description = options.description; + }, + + render: function() { + this.renderInput(); + // data for tooltips + if (this.description && this.tooltip) { + this.$el.data('content', this.description); + // TODO: render default value in popup? +// this.$el.data('title', "TODO: title"); + this.$el.popover({ + placement: 'right', + trigger: 'hover' +// delay: { show: 500, hide: 100 } + }); + } + + return this; + }, + + renderInput: function() { + // Overwrite this + }, + + showTooltip: function() { + if (this.description && this.tooltip) + this.$el.popover('show'); + }, + + hideTooltip: function() { + if (this.description && this.tooltip) + this.$el.popover('hide'); + }, + + destroy: function() { + this.undelegateEvents(); + this.unbind(); + if (this.onDestroy) { + this.onDestroy(); + } + this.$el.removeData().unbind(); + this.remove(); + }, + + // focus the input element + focus: function() { + this.$el.focus(); + }, + + // Clear the input + clear: function() { + + }, + + // retrieve value of the input + getVal: function() { + return this.value; + }, + + // the child class must call this when the value changed + setVal: function(value) { + this.value = value; + this.trigger('change', value); + } + }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/textInput.js b/pyload/web/app/scripts/views/input/textInput.js new file mode 100644 index 000000000..0eebbf91e --- /dev/null +++ b/pyload/web/app/scripts/views/input/textInput.js @@ -0,0 +1,36 @@ +define(['jquery', 'backbone', 'underscore', './inputView'], function($, Backbone, _, inputView) { + 'use strict'; + + return inputView.extend({ + + // TODO + tagName: 'input', + events: { + 'keyup': 'onChange', + 'focus': 'showTooltip', + 'focusout': 'hideTooltip' + }, + + renderInput: function() { + this.$el.attr('type', 'text'); + this.$el.attr('name', 'textInput'); + + if (this.default_value) + this.$el.attr('placeholder', this.default_value); + + if (this.value) + this.$el.val(this.value); + + return this; + }, + + clear: function() { + this.$el.val(''); + }, + + onChange: function(e) { + this.setVal(this.$el.val()); + } + + }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/collectorView.js b/pyload/web/app/scripts/views/linkgrabber/collectorView.js new file mode 100644 index 000000000..08b426aff --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/collectorView.js @@ -0,0 +1,36 @@ +define(['jquery', 'underscore', 'backbone', 'app', './packageView'], + function($, _, Backbone, App, packageView) { + 'use strict'; + return Backbone.Marionette.CollectionView.extend({ + itemView: packageView, + + initialize: function() { + this.listenTo(App.vent, 'linkcheck:updated', _.bind(this.onData, this)); + }, + + onData: function(rid, result) { + this.updateData({data: result}); + }, + + updateData: function(result) { + var self = this; + _.each(result.data, function(links, name) { + var pack = self.collection.get(name); + if (!pack) { + pack = new self.collection.model({name: name}); + self.collection.add(pack); + } + + // Remove links from other packages and delete empty ones + self.collection.each(function(pack2) { + console.log(pack2, links); + if (pack2 !== pack) + if (pack2.removeLinks(links)) + self.collection.remove(pack2); + }); + + pack.updateLinks(links); + }); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/modalView.js b/pyload/web/app/scripts/views/linkgrabber/modalView.js new file mode 100644 index 000000000..8e24f259b --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/modalView.js @@ -0,0 +1,121 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/CollectorPackage', 'views/abstract/modalView', './collectorView', 'hbs!tpl/linkgrabber/modal'], + function($, _, Backbone, App, CollectorPackage, modalView, CollectorView, template) { + 'use strict'; + // Modal dialog for package adding - triggers package:added when package was added + return modalView.extend({ + + className: 'modal linkgrabber', + events: { + 'keyup #inputLinks': 'addOnKeyUp', + 'click .btn-container': 'selectContainer', + 'change #inputContainer': 'checkContainer', + 'keyup #inputURL': 'checkURL', + 'click .btn-remove-all': 'clearAll' + }, + + template: template, + + // Holds the view that display the packages + collectorView: null, + + inputSize: 0, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + this.listenTo(App.vent, 'package:added', _.bind(this.onAdded, this)); + }, + + addOnKeyUp: function(e) { + // Enter adds the links + if (e.keyCode === 13) + this.checkLinks(); + + var inputSize = this.$('#inputLinks').val().length; + + // TODO: checkbox to disable this + // add links when several characters was pasted into box + if (inputSize > this.inputSize + 4) + this.checkLinks(); + else + this.inputSize = inputSize; + }, + + checkLinks: function() { + var self = this; + // split, trim and remove empty links + var links = _.filter(_.map(this.$('#inputLinks').val().split('\n'), function(link) { + return $.trim(link); + }), function(link) { + return link.length > 0; + }); + + var options = App.apiRequest('checkLinks', + {links: links}, + { + success: function(data) { + self.collectorView.updateData(data); + } + }); + + $.ajax(options); + this.$('#inputLinks').val(''); + this.inputSize = 0; + }, + + selectContainer: function(e) { + this.$('#inputContainer').trigger('click'); + }, + + checkContainer: function(e) { + this.$('form').attr('action', App.apiUrl('api/checkContainer')); + this.$('form').trigger('submit'); + }, + + checkURL: function(e) { + // check is triggered on enter + if (e.keyCode !== 13) + return; + + var self = this; + $.ajax(App.apiRequest('checkHTML', { + html: '', + url: $(e.target).val() + }, { + success: function(data) { + self.collectorView.updateData(data); + } + })); + + $(e.target).val(''); + }, + + // deletes every package + clearAll: function(e) { + this.collectorView.collection.reset(); + + }, + + // Hide when there are no more packages + onAdded: function() { + if (this.collectorView !== null) { + if (this.collectorView.collection.length === 0) + this.hide(); + } + }, + + onRender: function() { + // anonymous collection + this.collectorView = new CollectorView({collection: new (Backbone.Collection.extend({ + model: CollectorPackage + }))()}); + this.collectorView.setElement(this.$('.prepared-packages')); + }, + + onDestroy: function() { + if (this.collectorView) + this.collectorView.close(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/packageView.js b/pyload/web/app/scripts/views/linkgrabber/packageView.js new file mode 100644 index 000000000..356d39b4b --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/packageView.js @@ -0,0 +1,86 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'hbs!tpl/linkgrabber/package'], + function($, _, Backbone, App, template) { + 'use strict'; + return Backbone.Marionette.ItemView.extend({ + + tagName: 'div', + className: 'row-fluid package', + template: template, + + modelEvents: { + change: 'render' + }, + + ui: { + 'name': '.name', + 'table': 'table' + }, + + events: { + 'click .btn-expand': 'expand', + 'click .name': 'renamePackage', + 'keyup .name input': 'saveName', + 'click .btn-add': 'addPackage', + 'click .btn-delete': 'deletePackage', + 'click .btn-mini': 'deleteLink' + }, + + expanded: false, + + serializeData: function() { + var data = this.model.toJSON(); + data.expanded = this.expanded; + return data; + }, + + addPackage: function(e) { + e.stopPropagation(); + this.model.add(); + return false; + }, + + renamePackage: function(e) { + e.stopPropagation(); + + this.ui.name.addClass('edit'); + this.ui.name.find('input').focus(); + + var self = this; + $(document).one('click', function() { + self.ui.name.removeClass('edit'); + self.ui.name.focus(); + }); + + return false; + }, + + saveName: function(e) { + if (e.keyCode === 13) { + this.model.setName(this.ui.name.find('input').val()); + } + }, + + deletePackage: function() { + this.model.destroy(); + }, + + deleteLink: function(e) { + var el = $(e.target); + var id = parseInt(el.data('index'), 10); + + var model = this.model.get('links').at(id); + if (model) + model.destroy(); + + this.render(); + }, + + expand: function(e) { + e.stopPropagation(); + this.expanded ^= true; + this.ui.table.toggle(); + return false; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/loginView.js b/pyload/web/app/scripts/views/loginView.js new file mode 100644 index 000000000..9f15c81b3 --- /dev/null +++ b/pyload/web/app/scripts/views/loginView.js @@ -0,0 +1,54 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/login'], + function($, Backbone, _, App, template) { + 'use strict'; + + // Renders context actions for selection packages and files + return Backbone.Marionette.ItemView.extend({ + template: template, + + events: { + 'submit form': 'login' + }, + + ui: { + 'form': 'form' + }, + + login: function(e) { + e.stopPropagation(); + + var self = this; + var data = this.ui.form.serialize(); + // set flag to load user representation + data += '&user=true'; + var options = App.apiRequest('login', null, { + data: data, + type: 'post', + success: function(data) { + console.log('User logged in', data); + // TODO: go to last page + if (data) { + App.user.set(data); + App.user.save(); + App.navigate(''); + } + else { + self.wrongLogin(); + } + }, + error: function() { + self.wrongLogin(); + } + }); + + $.ajax(options); + return false; + }, + + // TODO: improve + wrongLogin: function() { + alert('Wrong login'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/notificationView.js b/pyload/web/app/scripts/views/notificationView.js new file mode 100644 index 000000000..93d07a0f3 --- /dev/null +++ b/pyload/web/app/scripts/views/notificationView.js @@ -0,0 +1,85 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'collections/InteractionList', 'hbs!tpl/notification'], + function($, Backbone, _, App, InteractionList, template) { + 'use strict'; + + // Renders context actions for selection packages and files + return Backbone.Marionette.ItemView.extend({ + + // Only view for this area so it's hardcoded + el: '#notification-area', + template: template, + + events: { + 'click .btn-query': 'openQuery', + 'click .btn-notification': 'openNotifications' + }, + + tasks: null, + // area is slided out + visible: false, + // the dialog + modal: null, + + initialize: function() { + this.tasks = new InteractionList(); + + App.vent.on('interaction:added', _.bind(this.onAdd, this)); + App.vent.on('interaction:deleted', _.bind(this.onDelete, this)); + + var render = _.bind(this.render, this); + this.listenTo(this.tasks, 'add', render); + this.listenTo(this.tasks, 'remove', render); + + }, + + onAdd: function(task) { + this.tasks.add(task); + }, + + onDelete: function(task) { + this.tasks.remove(task); + }, + + onRender: function() { + this.$el.calculateHeight().height(0); + }, + + render: function() { + + // only render when it will be visible + if (this.tasks.length > 0) + this.$el.html(this.template(this.tasks.toJSON())); + + if (this.tasks.length > 0 && !this.visible) { + this.$el.slideOut(); + this.visible = true; + } + else if (this.tasks.length === 0 && this.visible) { + this.$el.slideIn(); + this.visible = false; + } + + return this; + }, + + openQuery: function() { + var self = this; + + _.requireOnce(['views/queryModal'], function(ModalView) { + if (self.modal === null) { + self.modal = new ModalView(); + self.modal.parent = self; + } + + self.modal.model = self.tasks.at(0); + self.modal.render(); + self.modal.show(); + }); + + }, + + openNotifications: function() { + + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/progressView.js b/pyload/web/app/scripts/views/progressView.js new file mode 100644 index 000000000..7b9dbb74b --- /dev/null +++ b/pyload/web/app/scripts/views/progressView.js @@ -0,0 +1,46 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'views/abstract/itemView', + 'hbs!tpl/header/progress', 'hbs!tpl/header/progressStatus', 'helpers/pluginIcon'], + function($, Backbone, _, App, Api, ItemView, template, templateStatus, pluginIcon) { + 'use strict'; + + // Renders single file item + return ItemView.extend({ + + idAttribute: 'pid', + tagName: 'li', + template: template, + events: { + }, + + // Last name + name: null, + + initialize: function() { + this.listenTo(this.model, 'change', this.update); + this.listenTo(this.model, 'remove', this.unrender); + }, + + onDestroy: function() { + }, + + // Update html without re-rendering + update: function() { + if (this.name !== this.model.get('name')) { + this.name = this.model.get('name'); + this.render(); + } + + this.$('.bar').width(this.model.getPercent() + '%'); + this.$('.progress-status').html(templateStatus(this.model.toJSON())); + }, + + render: function() { + // TODO: icon + // TODO: other states + // TODO: non download progress + this.$el.css('background-image', 'url(' + pluginIcon('todo') + ')'); + this.$el.html(this.template(this.model.toJSON())); + return this; + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/queryModal.js b/pyload/web/app/scripts/views/queryModal.js new file mode 100644 index 000000000..ce624814a --- /dev/null +++ b/pyload/web/app/scripts/views/queryModal.js @@ -0,0 +1,69 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', './input/inputLoader', 'hbs!tpl/dialogs/interactionTask'], + function($, _, App, modalView, load_input, template) { + 'use strict'; + return modalView.extend({ + + events: { + 'click .btn-success': 'submit', + 'submit form': 'submit' + }, + template: template, + + // the notificationView + parent: null, + + model: null, + input: null, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + }, + + renderContent: function() { + var data = { + title: this.model.get('title'), + plugin: this.model.get('plugin'), + description: this.model.get('description') + }; + + var input = this.model.get('input').data; + if (this.model.isCaptcha()) { + data.captcha = input[0]; + data.type = input[1]; + } + return data; + }, + + onRender: function() { + // instantiate the input + var input = this.model.get('input'); + var InputView = load_input(input); + this.input = new InputView({input: input}); + // only renders after wards + this.$('#inputField').append(this.input.render().el); + }, + + submit: function(e) { + e.stopPropagation(); + // TODO: load next task + + this.model.set('result', this.input.getVal()); + var self = this; + this.model.save({success: function() { + self.hide(); + }}); + + this.input.clear(); + return false; + }, + + onShow: function() { + this.input.focus(); + }, + + onHide: function() { + this.input.destroy(); + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/configSectionView.js b/pyload/web/app/scripts/views/settings/configSectionView.js new file mode 100644 index 000000000..38d4cb869 --- /dev/null +++ b/pyload/web/app/scripts/views/settings/configSectionView.js @@ -0,0 +1,90 @@ +define(['jquery', 'underscore', 'backbone', 'app', '../abstract/itemView', '../input/inputRenderer', + 'hbs!tpl/settings/config', 'hbs!tpl/settings/configItem'], + function($, _, Backbone, App, itemView, renderForm, template, templateItem) { + 'use strict'; + + // Renders settings over view page + return itemView.extend({ + + tagName: 'div', + + template: template, + + // Will only render one time with further attribute updates + rendered: false, + + events: { + 'click .btn-primary': 'submit', + 'click .btn-reset': 'reset' + }, + + initialize: function() { + this.listenTo(this.model, 'destroy', this.destroy); + }, + + render: function() { + if (!this.rendered) { + this.$el.html(this.template(this.model.toJSON())); + + // initialize the popover + this.$('.page-header a').popover({ + placement: 'left' +// trigger: 'hover' + }); + + // Renders every single element + renderForm(this.$('.control-content'), + this.model.get('items'), templateItem, + _.bind(this.render, this), this); + + this.rendered = true; + } + // Enable button if something is changed + if (this.model.hasChanges()) + this.$('.btn-primary').removeClass('disabled'); + else + this.$('.btn-primary').addClass('disabled'); + + // Mark all inputs that are modified + _.each(this.model.get('items'), function(item) { + var input = item.get('inputView'); + var el = input.$el.parent().parent(); + if (item.isChanged()) + el.addClass('info'); + else + el.removeClass('info'); + }); + + return this; + }, + + onDestroy: function() { + // TODO: correct cleanup after building up so many views and models + }, + + submit: function(e) { + e.stopPropagation(); + // TODO: success / failure popups + var self = this; + this.model.save({success: function() { + self.render(); + App.vent.trigger('config:change'); + }}); + + }, + + reset: function(e) { + e.stopPropagation(); + // restore the original value + _.each(this.model.get('items'), function(item) { + if (item.has('inputView')) { + var input = item.get('inputView'); + input.setVal(item.get('value')); + input.render(); + } + }); + this.render(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/pluginChooserModal.js b/pyload/web/app/scripts/views/settings/pluginChooserModal.js new file mode 100644 index 000000000..242d11a5a --- /dev/null +++ b/pyload/web/app/scripts/views/settings/pluginChooserModal.js @@ -0,0 +1,72 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/addPluginConfig', + 'helpers/pluginIcon', 'select2'], + function($, _, App, modalView, template, pluginIcon) { + 'use strict'; + return modalView.extend({ + + events: { + 'click .btn-add': 'add' + }, + template: template, + plugins: null, + select: null, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + var self = this; + $.ajax(App.apiRequest('getAvailablePlugins', null, {success: function(data) { + self.plugins = _.sortBy(data, function(item) { + return item.name; + }); + self.render(); + }})); + }, + + onRender: function() { + // TODO: could be a seperate input type if needed on multiple pages + if (this.plugins) + this.select = this.$('#pluginSelect').select2({ + escapeMarkup: function(m) { + return m; + }, + formatResult: this.format, + formatSelection: this.formatSelection, + data: {results: this.plugins, text: function(item) { + return item.label; + }}, + id: function(item) { + return item.name; + } + }); + }, + + onShow: function() { + }, + + onHide: function() { + }, + + format: function(data) { + var s = '<div class="plugin-select" style="background-image: url(' + pluginIcon(data.name) + ')">' + data.label; + s += '<br><span>' + data.description + '<span></div>'; + return s; + }, + + formatSelection: function(data) { + if (!data || _.isEmpty(data)) + return ''; + + return '<img class="logo-select" src="' + pluginIcon(data.name) + '"> ' + data.label; + }, + + add: function(e) { + e.stopPropagation(); + if (this.select) { + var plugin = this.select.val(); + App.vent.trigger('config:open', plugin); + this.hide(); + } + } + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/settingsView.js b/pyload/web/app/scripts/views/settings/settingsView.js new file mode 100644 index 000000000..ff86efdf9 --- /dev/null +++ b/pyload/web/app/scripts/views/settings/settingsView.js @@ -0,0 +1,184 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/ConfigHolder', './configSectionView', + 'hbs!tpl/settings/layout', 'hbs!tpl/settings/menu', 'hbs!tpl/settings/actionbar'], + function($, _, Backbone, App, ConfigHolder, ConfigSectionView, template, templateMenu, templateBar) { + 'use strict'; + + // Renders settings over view page + return Backbone.Marionette.ItemView.extend({ + + template: template, + templateMenu: templateMenu, + + events: { + 'click .settings-menu li > a': 'change_section', + 'click .icon-remove': 'deleteConfig' + }, + + ui: { + 'menu': '.settings-menu', + 'content': '.setting-box > form' + }, + + selected: null, + modal: null, + + coreConfig: null, // It seems collections are not needed + pluginConfig: null, + + // currently open configHolder + config: null, + lastConfig: null, + isLoading: false, + + initialize: function() { + this.actionbar = Backbone.Marionette.ItemView.extend({ + template: templateBar, + events: { + 'click .btn': 'choosePlugin' + }, + choosePlugin: _.bind(this.choosePlugin, this) + + }); + this.listenTo(App.vent, 'config:open', this.openConfig); + this.listenTo(App.vent, 'config:change', this.refresh); + + this.refresh(); + }, + + refresh: function() { + var self = this; + $.ajax(App.apiRequest('getCoreConfig', null, {success: function(data) { + self.coreConfig = data; + self.renderMenu(); + }})); + $.ajax(App.apiRequest('getPluginConfig', null, {success: function(data) { + self.pluginConfig = data; + self.renderMenu(); + }})); + }, + + onRender: function() { + // set a height with css so animations will work + this.ui.content.height(this.ui.content.height()); + }, + + renderMenu: function() { + var plugins = [], + addons = []; + + // separate addons and default plugins + // addons have an activated state + _.each(this.pluginConfig, function(item) { + if (item.activated === null) + plugins.push(item); + else + addons.push(item); + }); + + this.$(this.ui.menu).html(this.templateMenu({ + core: this.coreConfig, + plugin: plugins, + addon: addons + })); + + // mark the selected element + this.$('li[data-name="' + this.selected + '"]').addClass('active'); + }, + + openConfig: function(name) { + // Do nothing when this config is already open + if (this.config && this.config.get('name') === name) + return; + + this.lastConfig = this.config; + this.config = new ConfigHolder({name: name}); + this.loading(); + + var self = this; + this.config.fetch({success: function() { + if (!self.isLoading) + self.show(); + + }, failure: _.bind(this.failure, this)}); + + }, + + loading: function() { + this.isLoading = true; + var self = this; + this.ui.content.fadeOut({complete: function() { + if (self.config.isLoaded()) + self.show(); + + self.isLoading = false; + }}); + + }, + + show: function() { + // TODO animations are bit sloppy + this.ui.content.css('display', 'block'); + var oldHeight = this.ui.content.height(); + + // this will destroy the old view + if (this.lastConfig) + this.lastConfig.trigger('destroy'); + else + this.ui.content.empty(); + + // reset the height + this.ui.content.css('height', ''); + // append the new element + this.ui.content.append(new ConfigSectionView({model: this.config}).render().el); + // get the new height + var height = this.ui.content.height(); + // set the old height again + this.ui.content.height(oldHeight); + this.ui.content.animate({ + opacity: 'show', + height: height + }); + }, + + failure: function() { + // TODO + this.config = null; + }, + + change_section: function(e) { + // TODO check for changes + // TODO move this into render? + + var el = $(e.target).closest('li'); + + this.selected = el.data('name'); + this.openConfig(this.selected); + + this.ui.menu.find('li.active').removeClass('active'); + el.addClass('active'); + e.preventDefault(); + }, + + choosePlugin: function(e) { + var self = this; + _.requireOnce(['views/settings/pluginChooserModal'], function(Modal) { + if (self.modal === null) + self.modal = new Modal(); + + self.modal.show(); + }); + }, + + deleteConfig: function(e) { + e.stopPropagation(); + var el = $(e.target).parent().parent(); + var name = el.data('name'); + var self = this; + $.ajax(App.apiRequest('deleteConfig', {plugin: name}, { success: function() { + self.refresh(); + }})); + return false; + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/finishedView.js b/pyload/web/app/scripts/views/setup/finishedView.js new file mode 100644 index 000000000..9f0f8db19 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/finishedView.js @@ -0,0 +1,25 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/finished'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'Finished', + template: template, + + events: { + 'click .btn-blue': 'confirm' + }, + + ui: { + }, + + onRender: function() { + }, + + confirm: function() { + this.model.submit(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/setupView.js b/pyload/web/app/scripts/views/setup/setupView.js new file mode 100644 index 000000000..8ab6fba51 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/setupView.js @@ -0,0 +1,121 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setup/layout', 'hbs!tpl/setup/actionbar', 'hbs!tpl/setup/error', + './welcomeView', './systemView', './userView', './finishedView'], + function($, Backbone, _, App, Setup, template, templateBar, templateError, welcomeView, systemView, userView, finishedView) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + template: template, + + events: { + }, + + ui: { + page: '.setup-page' + }, + + pages: [ + welcomeView, + systemView, + userView, + finishedView + ], + + page: 0, + view: null, + error: null, + + initialize: function() { + var self = this; + this.model = new Setup(); + + this.actionbar = Backbone.Marionette.ItemView.extend({ + template: templateBar, + view: this, + events: { + 'click .select-page': 'selectPage' + }, + + initialize: function() { + this.listenTo(self.model, 'page:changed', this.render); + }, + + serializeData: function() { + return { + page: this.view.page, + max: this.view.pages.length - 1, + pages: _.map(this.view.pages, function(p) { + return p.prototype.name; + }) + }; + }, + + selectPage: function(e) { + this.view.openPage(parseInt($(e.target).data('page'), 10)); + } + + }); + this.listenTo(this.model, 'page:next', function() { + self.openPage(self.page + 1); + }); + this.listenTo(this.model, 'page:prev', function() { + self.openPage(self.page - 1); + }); + + this.listenTo(this.model, 'error', this.onError); + this.model.fetch(); + }, + + openPage: function(page) { + console.log('Change page', page); + // check if number is reasonable + if (!_.isNumber(page) || !_.isFinite(page) || page < 0 || page >= this.pages.length) + return; + + if (page === this.page) + return; + + // Render error directly + if (this.error) { + this.onRender(); + return; + } + + this.page = page; + + var self = this; + this.ui.page.fadeOut({complete: function() { + self.onRender(); + }}); + + this.model.trigger('page:changed', page); + }, + + onError: function(model, xhr) { + console.log('Setup error', xhr); + this.error = xhr; + this.onRender(); + }, + + onRender: function() { + + // close old opened view + if (this.view) + this.view.close(); + + // Render error if occurred + if (this.error) { + this.ui.page.html(templateError(this.error)); + return; + } + + this.view = new this.pages[this.page]({model: this.model}); + this.ui.page.empty(); + + var el = this.view.render().el; + this.ui.page.append(el); + + this.ui.page.fadeIn(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/systemView.js b/pyload/web/app/scripts/views/setup/systemView.js new file mode 100644 index 000000000..b4c0f7e12 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/systemView.js @@ -0,0 +1,25 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/system'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'System', + template: template, + + events: { + 'click .btn-blue': 'nextPage' + }, + + ui: { + }, + + onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/userView.js b/pyload/web/app/scripts/views/setup/userView.js new file mode 100644 index 000000000..95eaa0dc2 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/userView.js @@ -0,0 +1,39 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/user'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'User', + template: template, + + events: { + 'click .btn-blue': 'submit' + }, + + ui: { + username: '#username', + password: '#password', + password2: '#password2' + }, + + onRender: function() { + }, + + submit: function() { + var pw = this.ui.password.val(); + var pw2 = this.ui.password2.val(); + + // TODO more checks and error messages + if (pw !== pw2) { + return; + } + + this.model.set('user', this.ui.username.val()); + this.model.set('password', pw); + + this.model.trigger('page:next'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/welcomeView.js b/pyload/web/app/scripts/views/setup/welcomeView.js new file mode 100644 index 000000000..a964e0d42 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/welcomeView.js @@ -0,0 +1,25 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/welcome'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'Language', + template: template, + + events: { + 'click .btn-blue': 'nextPage' + }, + + ui: { + }, + + onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/styles/default/accounts.less b/pyload/web/app/styles/default/accounts.less new file mode 100644 index 000000000..efc8b5518 --- /dev/null +++ b/pyload/web/app/styles/default/accounts.less @@ -0,0 +1,43 @@ +@import "common"; + +.account-list { + + .account-type { + background-size: 32px 32px; + background-repeat: no-repeat; + background-position: left; + padding-left: 40px; + font-weight: bold; + } + + .account-name { + padding-top: 8px; + } + +} + +.form-account { + + // Bit wider control labels / same as config page + .control-label { + width: 180px; + } + .controls { + margin-left: 200px; + } + .form-actions { + padding-left: 200px; + } + +} + +.logo-select { + width: 20px; + height: 20px; +} + +.vertical-header { + .rotate(-90deg); + font-weight: bold; + text-transform: uppercase; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/admin.less b/pyload/web/app/styles/default/admin.less new file mode 100644 index 000000000..92524c153 --- /dev/null +++ b/pyload/web/app/styles/default/admin.less @@ -0,0 +1,17 @@ +@import "common"; + +/* + Admin +*/ + +#btn_newuser { + float: right; +} + +#user_permissions { + float: right; +} + +.userperm { + width: 115px; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/dashboard.less b/pyload/web/app/styles/default/dashboard.less new file mode 100644 index 000000000..336070737 --- /dev/null +++ b/pyload/web/app/styles/default/dashboard.less @@ -0,0 +1,335 @@ +@import "bootstrap/less/mixins"; +@import "common"; + +/* + Dashboard +*/ + +#dashboard ul { + margin: 0; + list-style: none; +} + +.sidebar-header { + font-size: 25px; + line-height: 25px; + margin: 4px 0; + border-bottom: 1px dashed @grey; +} + +/* + Packages +*/ +.package-list { + list-style: none; + margin-left: 0; +} + +@frame-top: 20px; +@frame-bottom: 18px; + +.package-frame { + position: absolute; + top: -@frame-top; + left: -@frame-top / 2; + right: -@frame-top / 2; + bottom: -@frame-bottom + 2px; // + size of visible bar + z-index: -1; // lies under package + border: 1px solid @grey; + border-radius: 5px; + box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.75); +} + +.package-view { + padding-bottom: 4px; + margin: 8px 0; + position: relative; + overflow: hidden; + .hyphens; + + + i { + cursor: pointer; + } + + & > i { + vertical-align: middle; + } + + .progress { + position: absolute; + height: @frame-bottom; + line-height: @frame-bottom; + font-size: 12px; + text-align: center; + border-radius: 0; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + bottom: 0; + left: 0; + right: 0; + margin-bottom: 0; + background-image: none; + color: @light; + background-color: @yellow; + } + + .bar-info { + background-image: none; + background-color: @blue; + } + + &:hover { + overflow: visible; + z-index: 10; + + .package-frame { + background-color: @light; + } + } + + &.ui-selected:hover { + color: @light; + + .package-frame { + background-color: @dark; + } + + } +} + +.package-name { + cursor: pointer; +} + +.package-indicator { + position: absolute; + top: 0; + right: 0; + float: right; + color: @blue; + text-shadow: @yellowDark 1px 1px; + height: @frame-top; + line-height: @frame-top; + + & > i:hover { + color: @green; + } + + .dropdown-menu { + text-shadow: none; + } + + .tooltip { + text-shadow: none; + width: 100%; + } + + .btn-move { + color: @green; + display: none; + } + +} + +.ui-files-selected .btn-move { + display: inline; +} + +// Tag area with different effect on hover +.tag-area { + position: absolute; + top: -2px; + left: 0; + + .badge { + font-size: 11px; + line-height: 11px; + } + + .badge i { + cursor: pointer; + &:hover:before { + content: "\f024"; // show Remove icon + } + } + + .badge-ghost { + visibility: hidden; + cursor: pointer; + opacity: 0.5; + } + + &:hover .badge-ghost { + visibility: visible; + } + +} + +/* + File View +*/ + +.file-list { + list-style: none; + margin: 0; +} + +@file-height: 22px; + +.file-view { + position: relative; + padding: 0 4px; + border-top: 1px solid #dddddd; + line-height: @file-height; + + &:first-child { + border-top: none; + } + + &:hover, &.ui-selected:hover { + border-radius: 5px; + .gradient(top, @blue, @blueLight); + color: @light; + } + + &.ui-selected { + .gradient(top, @yellow, @yellowDark); + color: @dark; + border-color: @greenDark; + + .file-row.downloading .bar { + .gradient(top, @green, @greenLight); + } + + } + + img { // plugin logo + margin-top: -2px; + padding: 0 2px; + height: @file-height; + width: @file-height; + } + + .icon-chevron-down:hover { + cursor: pointer; + color: @yellow; + } + +} + +.file-row { + min-height: 0 !important; +// padding-left: 5px; + padding-top: 4px; + padding-bottom: 4px; + + // TODO: better styling for filestatus + &.second { +// border-radius: 4px; +// background: @light; + font-size: small; + font-weight: bold; +// box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.75); +// .default-shadow; + } + + &.third { + margin-left: 0; + position: relative; + font-size: small; + } + + .dropdown-menu { + font-size: medium; + } +} + +/* + TODO: more colorful states + better fileView design +*/ + +.file-row.finished { +// .gradient(top, @green, @greenLight); +// color: @light; + color: @green; +} + +.file-row.failed { +// .gradient(top, @red, @redLight); +// color: @light; + color: @red; +} + +.file-row.downloading { + + .progress { + height: @file-height; + background: @light; + margin: 0; + } + + .bar { + text-align: left; + .gradient(top, @yellow, @yellowDark); + .transition-duration(2s); + color: @dark; + } + +} + +/* +FANCY CHECKBOXES +*/ +.file-view .checkbox { + width: 20px; + height: 21px; + background: url(../../images/default/checks_sheet.png) left top no-repeat; + cursor: pointer; +} + +.file-view.ui-selected .checkbox { + background: url(../../images/default/checks_sheet.png) -21px top no-repeat; +} + +/* + Actionbar +*/ + +.form-search { + position: relative; + + .dropdown-menu { + min-width: 100%; + position: absolute; + right: 0; + left: auto; + } + +} + +.li-check > a { + padding-left: 8px !important; + padding-right: 8px !important; +} + +li.finished > a, li.finished:hover > a { + background-color: @green; + color: @light; + + .caret, .caret:hover { + border-bottom-color: @light !important; + border-top-color: @light !important; + } +} + +li.failed > a, li.failed:hover > a { + background-color: @red; + color: @light; + + .caret, .caret:hover { + border-bottom-color: @light !important; + border-top-color: @light !important; + } +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/linkgrabber.less b/pyload/web/app/styles/default/linkgrabber.less new file mode 100644 index 000000000..010dbadec --- /dev/null +++ b/pyload/web/app/styles/default/linkgrabber.less @@ -0,0 +1,65 @@ +.linkgrabber { + width: 800px !important; + + .pull-left { + padding-right: 20px; + } + + input, textarea { + width: 130px; + } + +} + +.prepared-packages { + hr { + margin: 0; + } + + .package { + margin-bottom: 10px; + + & > .btn { + margin-bottom: 3px; + } + + } + + .name { + padding: 0 2px; + + input { + display: none; + } + + &:hover { + border: 1px @grey dashed; + } + + &.edit { + border: none; + input { + display: inline; + } + + strong { + display: none; + } + } + + } + + .link-name { + .hyphens(); + width: 50%; + } + + img { + height: 22px; + } + + .table { + margin-bottom: 0; + } + +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/main.less b/pyload/web/app/styles/default/main.less new file mode 100644 index 000000000..6153b576e --- /dev/null +++ b/pyload/web/app/styles/default/main.less @@ -0,0 +1,22 @@ +@import "bootstrap/less/bootstrap"; +@import "bootstrap/less/responsive"; +@import "font-awesome/less/font-awesome"; + +@FontAwesomePath: "../../fonts"; + +@import "pyload-common/styles/base"; +@import "pyload-common/styles/basic-layout"; + +@import "style"; +@import "linkgrabber"; +@import "dashboard"; +@import "settings"; +@import "accounts"; +@import "admin"; +@import "setup"; + +@ResourcePath: "../.."; +@DefaultFont: 'Abel', sans-serif; + +// Changed dimensions +@header-height: 70px;;
\ No newline at end of file diff --git a/pyload/web/app/styles/default/settings.less b/pyload/web/app/styles/default/settings.less new file mode 100644 index 000000000..34bfcb92a --- /dev/null +++ b/pyload/web/app/styles/default/settings.less @@ -0,0 +1,121 @@ +@import "common"; + +/* + Settings +*/ +.settings-menu { + background-color: #FFF; + box-shadow: 0 0 5px #000; // border: 10px solid #EEE; + + .nav-header { + background: @blueDark; + color: @light; + } + + li > a, .nav-header { + margin-left: -16px; + margin-right: -16px; + text-shadow: none; + } + + i { + margin-top: 0; + } + + .plugin, .addon { + a { + padding-left: 28px; + background-position: 4px 2px; + background-repeat: no-repeat; + background-size: 20px 20px; + } + + .icon-remove { + display: none; + } + + &:hover { + i { + display: block; + } + } + + } + + .addon { + div { + font-size: small; + } + .addon-on { + color: @green; + } + + .addon-off { + color: @red; + } + + } + + border-top-left-radius: 0; + border-top-right-radius: 0; + + .nav > li > a:hover { + color: @blueDark; + } +} + +.setting-box { + border: 10px solid @blueDark; + box-shadow: 0 0 5px @dark; // .gradient(bottom, @yellowLightest, @light); + overflow: hidden; + + .page-header { + margin: 0; + + .btn { + float: right; + margin-top: 5px; + } + + .popover { + font-size: medium; + } + + } + + // Bit wider control labels + .control-label { + width: 180px; + } + .controls { + margin-left: 200px; + } + .form-actions { + padding-left: 200px; + } + +} + +/* + Plugin select +*/ + +.plugin-select { + background-position: left 2px; + background-repeat: no-repeat; + background-size: 20px 20px; + padding-left: 24px; + + font-weight: bold; + span { + line-height: 14px; + font-size: small; + font-weight: normal; + } + +} + +.logo-select { + width: 20px; + height: 20px; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/setup.less b/pyload/web/app/styles/default/setup.less new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/web/app/styles/default/setup.less diff --git a/pyload/web/app/styles/default/style.less b/pyload/web/app/styles/default/style.less new file mode 100644 index 000000000..ad60e5b59 --- /dev/null +++ b/pyload/web/app/styles/default/style.less @@ -0,0 +1,287 @@ +@import "bootstrap/less/mixins"; +@import "common"; + +/* + Header +*/ +header { // background-color: @greyDark; + .gradient(to bottom, #222222, #111111); + height: @header-height; + position: fixed; + top: 0; + vertical-align: top; + width: 100%; + z-index: 10; + color: #ffffff; + + a { + color: #ffffff; + } + .container-fluid, .row-fluid { + height: @header-height; + } + + span.title { + color: white; + float: left; + font-family: SansationRegular, sans-serif; + font-size: 40px; + line-height: @header-height; + cursor: default; + } + + .logo { + margin-right: 10px; + margin-top: 10px; + width: 105px; + height: 107px; + background-size: auto; + cursor: pointer; + } + +} + +@header-inner-height: @header-height - 16px; + +// centered header element +.centered { + height: @header-inner-height; + margin: 8px 0; +} + +.header-block { + .centered; + float: left; + line-height: @header-inner-height / 3; // 3 rows + font-size: small; +} + +.status-block { + min-width: 15%; +} + +.header-btn { + float: right; + position: relative; + .centered; + + .lower { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin-left: 0; + + button { + width: 100% / 3; // 3 buttons + } + + } +} + +#progress-area { + .centered; + position: relative; + margin-top: 8px; + line-height: 16px; + + .sub { + font-size: small; + } + + .popover { // display: block; + max-width: none; + width: 120%; + left: -60%; // Half of width + margin-left: 50%; + top: 100%; + } + + .popover-title, .popover-content { + color: @greyDark; + } + + .icon-list { + cursor: pointer; + margin-right: 2px; // same as globalprogress margin + + &:hover { + color: @yellow; + } + } + .close { + line-height: 14px; + } +} + +.progress-list { + list-style: none; + margin: 0; + font-size: small; + + li { + background-repeat: no-repeat; + background-size: 32px 32px; + background-position: 0px 8px; + padding-left: 40px; + + &:not(:last-child) { + margin-bottom: 5px; + padding-bottom: 5px; + border-bottom: 1px dashed @greyLight; + } + + .progress { + height: 8px; + margin-bottom: 0; + + .bar { + .transition-duration(2s); + .gradient(bottom, @blue, @blueLight); + } + } + } +} + +#globalprogress { + background-color: @greyDark; + background-image: none; + height: 8px; + margin: 4px 0; + border-radius: 8px; + border: 2px solid @grey; + + .bar { + color: @dark; + background-image: none; + background-color: @yellow; + .transition-duration(2s); + + &.running { + width: 100%; + .stripes(@yellowLighter, @yellowDark); + } + } +} + +.speedgraph-container { + // Allows speedgraph to take up remaining space + display: block; + overflow: hidden; + padding: 0 8px; + + #speedgraph { + float: right; + width: 100%; + .centered; + // height: @header-height - 16px; + // margin: 8px 0; + font-family: sans-serif; + } +} + +.header-area { + display: none; // hidden by default + position: absolute; + bottom: -28px; + line-height: 18px; + top: @header-height; + padding: 4px 10px 6px 10px; + text-align: center; + border-radius: 0 0 6px 6px; + color: @light; + background-color: @greyDark; + .default-shadow; +} + +#notification-area { + .header-area; + left: 140px; + + .badge { + vertical-align: top; + } + + .btn-query, .btn-notification { + cursor: pointer; + } +} + +#selection-area { + .header-area; + left: 50%; + min-width: 15%; + + i { + cursor: pointer; + + &:hover { + color: @yellow; + } + } + +} + +/* + Actionbar +*/ + +.nav > li > a:hover { + color: @blue; +} + +.actionbar { + padding-top: 2px; + padding-bottom: 3px; + margin-bottom: 5px; + border-bottom: 1px dashed @grey; + + height: @actionbar-height; + + & > li > a, & > li > button { + margin-top: 4px; + } + + .breadcrumb { + margin: 0; + padding-top: 10px; + padding-bottom: 0; + + .active { + color: @grey; + } + } + + form { + margin-top: 6px; + margin-bottom: 0; + } + + select { + margin: 2px 0 0; + } + + input, button { + padding-top: 2px; + padding-bottom: 2px; + } + + .dropdown-menu i { + margin-top: 4px; + padding-right: 5px; + } + +} + +/* + 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; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/font.css b/pyload/web/app/styles/font.css new file mode 100644 index 000000000..088b6f14c --- /dev/null +++ b/pyload/web/app/styles/font.css @@ -0,0 +1,13 @@ +/** + * @file + * Font styling + */ + +@font-face { + font-family: 'Abel'; + font-style: normal; + font-weight: 400; + src: local('Abel'), local('Abel-Regular'); + src: url(../fonts/Abel-Regular.woff) format('woff'); + url(../fonts/Abel-Regular.ttf) format('truetype'); +} diff --git a/pyload/web/app/templates/default/accounts/account.html b/pyload/web/app/templates/default/accounts/account.html new file mode 100644 index 000000000..7039eae8c --- /dev/null +++ b/pyload/web/app/templates/default/accounts/account.html @@ -0,0 +1,41 @@ +<div class="span3 account-type" style="background-image: url({{ pluginIcon plugin }})"> + {{ plugin }} <br> + {{#if valid }} + <span class="text-success"> + {{#if premium}} + {{_ "premium"}} + {{else}} + {{_ "valid" }} + {{/if}} + </span> + {{else}} + <span class="text-error"> + {{_ "invalid" }} + </span> + {{/if}} +</div> +<div class="span2 account-name"> + {{ loginname }} + {{# if shared}} + TODO: shared + {{/if}} +</div> +<div class="span2 account-data"> + {{_ "Traffic left:"}}<br> + {{ formatSize trafficleft }} +</div> +<div class="span2 account-data"> + {{_ "Valid until:"}}<br> + {{ formatTime validuntil }} +</div> +<div class="span3"> + {{#if activated }} + <button type="button" class="btn btn-success"><i class="icon-check"></i></button> + {{else}} + <button type="button" class="btn btn-success"><i class="icon-check-empty"></i></button> + {{/if}} + + <button type="button" class="btn btn-blue"><i class="icon-pencil"></i></button> + <button type="button" class="btn btn-yellow"><i class="icon-refresh"></i></button> + <button type="button" class="btn btn-danger"><i class="icon-trash"></i></button> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/accounts/actionbar.html b/pyload/web/app/templates/default/accounts/actionbar.html new file mode 100644 index 000000000..d16f6d6e0 --- /dev/null +++ b/pyload/web/app/templates/default/accounts/actionbar.html @@ -0,0 +1,5 @@ +<ul class="actionbar nav span8 offset3"> + <li> + <button class="btn btn-small btn-blue btn-add">{{ _ "Add Account" }}</button> + </li> +</ul>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/accounts/editAccount.html b/pyload/web/app/templates/default/accounts/editAccount.html new file mode 100755 index 000000000..57c767226 --- /dev/null +++ b/pyload/web/app/templates/default/accounts/editAccount.html @@ -0,0 +1,38 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>{{_ "Edit account" }}</h3> +</div> +<div class="modal-body"> + <form class="form-horizontal form-account" autocomplete="off"> + <div class="control-group"> + <label class="control-label"> + Account + </label> + + <div class="controls"> + <img src="{{ pluginIcon plugin }}" style="padding-right: 2px"> + {{ loginname }} + </div> + </div> + <div class="control-group"> + <label class="control-label" for="password"> + Password + </label> + + <div class="controls"> + <input type="password" id="password"> + </div> + </div> + {{#if config }} + <legend> + {{ _ "Configuration" }} + </legend> + {{/if}} + <div class="account-config"> + </div> + </form> +</div> +<div class="modal-footer"> + <a class="btn btn-success btn-save">Save</a> + <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/accounts/layout.html b/pyload/web/app/templates/default/accounts/layout.html new file mode 100644 index 000000000..6bb1a221f --- /dev/null +++ b/pyload/web/app/templates/default/accounts/layout.html @@ -0,0 +1,10 @@ +<div class="span3"> + <h1 class="vertical-header"> + {{ _ "Accounts" }} + </h1> +</div> +<div class="span8"> + <div class="container-fluid account-list"> + + </div> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/admin.html b/pyload/web/app/templates/default/admin.html new file mode 100644 index 000000000..2eb90d7e0 --- /dev/null +++ b/pyload/web/app/templates/default/admin.html @@ -0,0 +1,223 @@ +{% extends 'default/base.html' %} + +{% block title %}{{ _("Admin") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Admin") }} +{% endblock %} + +{% block css %} + <link href="static/css/default/admin.less" rel="stylesheet/less" type="text/css" media="screen"/> + <link rel="stylesheet" type="text/css" href="static/css/fontawesome.css" /> +{% endblock %} + +{% block require %} +{% endblock %} + +{% block content %} + <div class="container-fluid"> + <div class="row-fluid"> + <div id="userlist" class="span10"> + <div class="page-header"> + <h1>Admin Bereich + <small>Userverwaltung, Systeminfos</small> + <a id="btn_newuser" class="btn btn-warning btn-large" type="button"><i class="iconf-plus-sign iconf-large "></i></a> + </h1> + + + + </div> + + <div class="dropdown"> + <span class="label name">User</span> + <a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="iconf-user iconf-8x"></i></a> + <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu"> + <li><a tabindex="-1" id="useredit" href="#" role="button" data-backdrop="true" data-controls-modal="event-modal" data-keyboard="true"><i class="icon-pencil"></i>Edit</a></li> + <li><a tabindex="-1" href="#"><i class="icon-tasks"></i>Statistik</a></li> + <li class="divider"></li> + <li><a tabindex="-1" href="#"><i class="icon-remove-sign"></i>Delete</a></li> + </ul> + </div> + + <div id="event-modal" class="modal hide fade"> + <div class="modal-header"> + <a class="close" id="useredit_close" href="#">x</a> + <h3>User Settings</h3> + </div> + <div class="modal-body"> + <p>Set password and permissions</p> + <table style="width:100%;" class="table "> + <td> + <div class="input-prepend"> + <span class="add-on"><i class="iconf-key"></i></span> + <input class="span2" style="min-width:120px;" id="prependedInput" type="text" placeholder="New Password"> + </div> + <div class="input-prepend"> + <span class="add-on"><i class="icon-repeat"></i></span> + <input class="span2" style="min-width:120px;" id="prependedInput" type="text" placeholder="Repeat"> + </div> + <br> + <br> + <br> + <form class="form-horizontal"> + <div class="control-group"> + <label class="control-label" for="onoff">Administrator</label> + + <div class="controls"> + <div class="btn-group" id="onoff" data-toggle="buttons-radio"> + <button type="button" class="btn btn-primary" >On</button> + <button type="button" class="btn btn-primary active">Off</button> + </div> + </div> + </div> + </form> + </td> + <td> + <div id="user_permissions"> + <h3>Permissions</h3> + <div class="btn-group btn-group-vertical" data-toggle="buttons-checkbox"> + <button type="button" class="btn btn-inverse userperm">Accounts</button> + <button type="button" class="btn btn-inverse userperm active">Add</button> + <button type="button" class="btn btn-inverse userperm">Delete</button> + <button type="button" class="btn btn-inverse userperm active">Download</button> + <button type="button" class="btn btn-inverse userperm active">List</button> + <button type="button" class="btn btn-inverse userperm">Logs</button> + <button type="button" class="btn btn-inverse userperm">Modify</button> + <button type="button" class="btn btn-inverse userperm">Settings</button> + <button type="button" class="btn btn-inverse userperm active">Status</button> + </div> + </div> + </td> + </table> + </div> + <div class="modal-footer"> + <a class="btn btn-primary" id="useredit_save"href="#">Save</a> + + </div> + </div> + + + + </div> + + <div class="span2"> + <br> + <h2>Support</h2> + <table> + <tr> + <td> + <i class="icon-globe"></i> + </td> + <td> + <a href="#">Wiki |</a> + <a href="#">Forum |</a> + <a href="#">Chat</a> + </td> + </tr> + <tr> + <td> + <i class="icon-book"></i> + </td> + <td> + <a href="#">Documentation</a> + </td> + </tr> + <tr> + <td> + <i class="icon-fire"></i> + </td> + <td> + <a href="#">Development</a> + </td> + </tr> + <tr> + <td> + <i class="icon-bullhorn"></i> + </td> + <td> + <a href="#">Issue Tracker</a> + </td> + </tr> + </table> + <br> + <a href="#" class="btn btn-inverse" id="info" rel="popover" data-content="<table class='table table-striped'> + <tbody> + <tr> + <td>Python:</td> + <td>2.6.4 </td> + </tr> + <tr> + <td>Betriebssystem:</td> + <td>nt win32</td> + </tr> + <tr> + <td>pyLoad Version:</td> + <td>0.4.9</td> + </tr> + <tr> + <td>Installationsordner:</td> + <td>C:\pyLoad</td> + </tr> + <tr> + <td>Konfigurationsordner:</td> + <td>C:\Users\Marvin\pyload</td> + </tr> + <tr> + <td>Downloadordner:</td> + <td>C:\Users\Marvin\new</td> + </tr> + <tr> + <td>HDD:</td> + <td>1.67 TiB <div class='progress progress-striped active'> + <div class='bar' style='width: 40%;'></div> +</div></td> + </tr> + <tr> + <td>Sprache:</td> + <td>de</td> + </tr> + <tr> + <td>Webinterface Port:</td> + <td>8000</td> + </tr> + <tr> + <td>Remote Interface Port:</td> + <td>7227</td> + </tr> + </tbody> + </table>" title="Systeminformationen">System</a> + + </div> + </div> + </div> + + <script src="static/js/libs/jquery-1.9.0.js"></script> + {##} + <script src="static/js/libs/bootstrap-2.2.2.js"></script> + <script type="text/javascript"> + $('#info').popover({ + placement: 'left', + trigger: 'click', + html:'true', + }); + + $('.dropdown-toggle').dropdown(); + + $("#btn_newuser").click(function() { + + str = "<div class='dropdown1'><span class='label name'>User</span><a class='dropdown-toggle' data-toggle='dropdown1' href='#'><i class='iconf-user iconf-8x'></i></a><ul class='dropdown-menu' role='menu' aria-labelledby='dropdownMenu'><li><a tabindex='-1' href='#'>Action</a></li><li><a tabindex='-1' href='#'>Another action</a></li><li><a tabindex='-1' href='#'>Something else here</a></li><li class='divider'></li><li><a tabindex='-1' href='#'>Separated link</a></li></ul></div>"; + + $("#userlist").append(str); + + }); + + $("#useredit").click(function() { + $('#event-modal').modal(); + }); + $("#useredit_close").click(function() { + $('#event-modal').modal('hide'); + }); + $("#useredit_save").click(function() { + $('#event-modal').modal('hide'); + }); + + </script> +{% endblock %}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/actionbar.html b/pyload/web/app/templates/default/dashboard/actionbar.html new file mode 100644 index 000000000..25b7676e5 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/actionbar.html @@ -0,0 +1,56 @@ +<div class="span2 offset1"> +</div> +<ul class="actionbar nav nav-pills span9"> + <li class="li-check"> + <a href="#"><i class="icon-check-empty btn-check"></i></a> + </li> + + <li> + <ul class="breadcrumb"> + <li><a href="#">{{_ "Local"}}</a> <span class="divider">/</span></li> + <li class="active"></li> + </ul> + </li> + + <li style="float: right;"> + <form class="form-search" action="#"> + <div class="input-append"> + <input type="text" class="search-query" style="width: 120px"> + <button type="submit" class="btn">{{ _ "Search" }}</button> + </div> + </form> + </li> + <li class="dropdown" style="float: right;"> + <a class="dropdown-toggle type" + data-toggle="dropdown" + href="#"> + {{_ "Type" }} + <b class="caret"></b> + </a> + <ul class="dropdown-menu"> + <li><a class="filter-type" data-type="2" href="#"><i class="icon-ok"></i> Audio</a></li> + <li><a class="filter-type" data-type="4" href="#"><i class="icon-ok"></i> Image</a></li> + <li><a class="filter-type" data-type="8" href="#"><i class="icon-ok"></i> Video</a></li> + <li><a class="filter-type" data-type="16" href="#"><i class="icon-ok"></i> Document</a></li> + <li><a class="filter-type" data-type="32" href="#"><i class="icon-ok"></i> Archive</a></li> + <li><a class="filter-type" data-type="64" href="#"><i class="icon-ok"></i> Executable</a></li> + <li><a class="filter-type" data-type="1" href="#"><i class="icon-ok"></i> Other</a></li> + </ul> + </li> + <li class="dropdown" style="float: right;"> + <a class="dropdown-toggle" + data-toggle="dropdown" + href="#"> + <span class="state"> + {{ _ "All" }} + </span> + <b class="caret"></b> + </a> + <ul class="dropdown-menu"> + <li><a class="filter-state" data-state="0" href="#">{{ _ "All" }}</a></li> + <li><a class="filter-state" data-state="1" href="#">{{ _ "Finished" }}</a></li> + <li><a class="filter-state" data-state="2" href="#">{{ _ "Unfinished" }}</a></li> + <li><a class="filter-state" data-state="3" href="#">{{ _ "Failed" }}</a></li> + </ul> + </li> +</ul>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/file.html b/pyload/web/app/templates/default/dashboard/file.html new file mode 100644 index 000000000..4bf3c7a97 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/file.html @@ -0,0 +1,34 @@ +<div class="file-row first span6"> + <i class="checkbox"></i> + <span class="name"> + {{ name }} + </span> +</div> +<div class="file-row second span3 {{ fileClass this }}"> + {{ fileStatus this }} +</div> + +<div class="file-row third span3 pull-right"> + <i class="{{ fileIcon media }}"></i> + {{ formatSize size }} + <span class="pull-right"> + <img src="{{ pluginIcon download.plugin }}"/> + {{ download.plugin }} + <i class="icon-chevron-down" data-toggle="dropdown"></i> + <ul class="dropdown-menu" role="menu"> + <li><a href="#" class="btn-delete"><i class="icon-trash"></i> Delete</a></li> + <li><a href="#" class="btn-restart"><i class="icon-refresh"></i> Restart</a></li> + <!--{# TODO: only show when finished #}--> + <li><a href="download/{{ fid }}" target="_blank" class="btn-dowload"><i class="icon-download"></i> + Download</a></li> + <li><a href="#" class="btn-share"><i class="icon-share"></i> Share</a></li> + <li class="divider"></li> + <li class="dropdown-submenu pull-left"> + <a>Addons</a> + <ul class="dropdown-menu"> + <li><a>Test</a></li> + </ul> + </li> + </ul> + </span> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/layout.html b/pyload/web/app/templates/default/dashboard/layout.html new file mode 100644 index 000000000..cd84d3a26 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/layout.html @@ -0,0 +1,32 @@ +<div class="span3"> + <div class="sidebar-header"> + <i class="icon-hdd"></i> Local + <div class="pull-right" style="font-size: medium; line-height: normal"> + <i class="icon-chevron-down" style="font-size: 20px"></i> + </div> + <div class="clearfix"></div> + </div> + <ul class="package-list"> + + </ul> + <div class="sidebar-header"> + <i class="icon-group"></i> Shared + </div> + <ul class="package-list"> + <li>Shared content</li> + <li>from other user</li> + </ul> + <div class="sidebar-header"> + <i class="icon-sitemap"></i> Remote + </div> + <ul> + <li>Content from</li> + <li>remote sites or</li> + <li>other pyload instances</li> + </ul> +</div> +<div class="span9"> + <ul class="file-list"> + + </ul> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/package.html b/pyload/web/app/templates/default/dashboard/package.html new file mode 100644 index 000000000..83f4fa39e --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/package.html @@ -0,0 +1,50 @@ +{{#if selected }} + <i class="icon-check select"></i> + {{ else }} + <i class="icon-check-empty select"></i> + {{/if}} + <span class="package-name"> + {{ name }} + </span> + + <div class="package-frame"> + <div class="tag-area"> + <!--<span class="badge badge-success"><i class="icon-tag"></i>video</span>--> + <!--<span class="badge badge-success badge-ghost"><i class="icon-tag"></i> Add Tag</span>--> + </div> + <div class="package-indicator"> + <i class="icon-plus-sign btn-move" data-toggle="tooltip" title="Move files here"></i> + <i class="icon-pause" data-toggle="tooltip" title="Pause Package"></i> + <i class="icon-refresh" data-toggle="tooltip" title="Restart Package"></i> + {{#if shared }} + <i class="icon-eye-open" data-toggle="tooltip" title="Package is public"></i> + {{ else }} + <i class="icon-eye-close" data-toggle="tooltip" title="Package is private"></i> + {{/if}} + <i class="icon-chevron-down" data-toggle="dropdown"> + </i> + <ul class="dropdown-menu" role="menu"> + <li><a href="#" class="btn-open"><i class="icon-folder-open-alt"></i> Open</a></li> + <li><a href="#"><i class="icon-plus-sign"></i> Add links</a></li> + <li><a href="#"><i class="icon-edit"></i> Details</a></li> + <li><a href="#" class="btn-delete"><i class="icon-trash"></i> Delete</a></li> + <li><a href="#" class="btn-recheck"><i class="icon-refresh"></i> Recheck</a></li> + <li class="divider"></li> + <li class="dropdown-submenu"> + <a>Addons</a> + <ul class="dropdown-menu"> + <li><a>Test</a></li> + </ul> + </li> + </ul> + </div> + <div class="progress"> + <span style="position: absolute; left: 5px"> + {{ stats.linksdone }} / {{ stats.linkstotal }} + </span> + <div class="bar bar-info" style="width: {{ percent }}%"></div> + <span style="position: absolute; right: 5px"> + {{ formatSize stats.sizedone }} / {{ formatSize stats.sizetotal }} + </span> + </div> + </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/select.html b/pyload/web/app/templates/default/dashboard/select.html new file mode 100644 index 000000000..8f04d410e --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/select.html @@ -0,0 +1,11 @@ +<i class="icon-check" data-toggle="tooltip" title="Deselect"></i> +{{#if packs }}{{ ngettext "1 package" "%d packages" packs }}{{/if}} +{{#if files}} +{{#if packs}}, {{/if}} +{{ngettext "1 file" "%d files" files}} +{{/if }} +selected + | +<i class="icon-pause" data-toggle="tooltip" title="Pause"></i> +<i class="icon-trash" data-toggle="tooltip" title="Delete"></i> +<i class="icon-refresh" data-toggle="tooltip" title="Restart"></i>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/addAccount.html b/pyload/web/app/templates/default/dialogs/addAccount.html new file mode 100755 index 000000000..ff4851d1d --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/addAccount.html @@ -0,0 +1,42 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>{{_ "Add an account" }}</h3> +</div> +<div class="modal-body"> + <form class="form-horizontal" autocomplete="off"> + <legend> + {{_ "Please enter your account data" }} + </legend> + <div class="control-group"> + <label class="control-label" for="pluginSelect"> + Plugin + </label> + + <div class="controls"> + <input type="hidden" id="pluginSelect"> + </div> + </div> + <div class="control-group"> + <label class="control-label" for="login"> + Loginname + </label> + + <div class="controls"> + <input type="text" id="login"> + </div> + </div> + <div class="control-group"> + <label class="control-label" for="password"> + Password + </label> + + <div class="controls"> + <input type="password" id="password"> + </div> + </div> + </form> +</div> +<div class="modal-footer"> + <a class="btn btn-success btn-add">Add</a> + <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/addPluginConfig.html b/pyload/web/app/templates/default/dialogs/addPluginConfig.html new file mode 100755 index 000000000..815a704f7 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/addPluginConfig.html @@ -0,0 +1,26 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3> + {{_ "Choose a plugin" }} + </h3> +</div> +<div class="modal-body"> + <form class="form-horizontal"> + <legend> + {{_ "Please choose a plugin, which you want to configure" }} + </legend> + <div class="control-group"> + <label class="control-label" for="pluginSelect"> + Plugin + </label> + + <div class="controls"> + <input type="hidden" id="pluginSelect"> + </div> + </div> + </form> +</div> +<div class="modal-footer"> + <a class="btn btn-success btn-add">{{_ "Add"}}</a> + <a class="btn btn-close">{{_ "Close"}}</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/confirmDelete.html b/pyload/web/app/templates/default/dialogs/confirmDelete.html new file mode 100644 index 000000000..a12c5f326 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/confirmDelete.html @@ -0,0 +1,11 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>{{_ "Please confirm"}}</h3> +</div> +<div class="modal-body"> + {{_ "Do you want to delete the selected items?"}} +</div> +<div class="modal-footer"> + <a class="btn btn-danger btn-confirm"><i class="icon-trash icon-white"></i> {{_ "Delete"}}</a> + <a class="btn btn-close">{{_ "Cancel"}}</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/interactionTask.html b/pyload/web/app/templates/default/dialogs/interactionTask.html new file mode 100755 index 000000000..722d43365 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/interactionTask.html @@ -0,0 +1,37 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3> + {{ title }} + <small style="background: url('{{ pluginIcon plugin }}') no-repeat right 0; background-size: 20px; padding-right: 22px"> + {{ plugin }} + </small> + </h3> +</div> +<div class="modal-body"> + <form class="form-horizontal" action="#"> + <legend>{{ description }}</legend> + {{#if captcha }} + <div class="control-group"> + <label class="control-label" for="captchaImage"> + Captcha Image + </label> + + <div class="controls"> + <img id="captchaImage" src="data:image/{{ type }};base64,{{ captcha }}"> + </div> + </div> + <div class="control-group"> + <label class="control-label" for="inputField">Captcha Text</label> + + <div class="controls" id="inputField"> + </div> + </div> + {{ else }} + {{ content }} + {{/if}} + </form> +</div> +<div class="modal-footer"> + <a class="btn btn-success">{{_ "Submit"}}</a> + <a class="btn btn-close">{{_ "Close"}}</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/modal.html b/pyload/web/app/templates/default/dialogs/modal.html new file mode 100755 index 000000000..1e44cc99c --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/modal.html @@ -0,0 +1,10 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>Dialog</h3> +</div> +<div class="modal-body"> +</div> +<div class="modal-footer"> + <a class="btn btn-close">Close</a> + <a class="btn btn-primary">Save</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/blank.html b/pyload/web/app/templates/default/header/blank.html new file mode 100644 index 000000000..305477d4e --- /dev/null +++ b/pyload/web/app/templates/default/header/blank.html @@ -0,0 +1,4 @@ +<div class="span3"> + <div class="logo"></div> + <span class="title visible-large-screen">pyLoad</span> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/layout.html b/pyload/web/app/templates/default/header/layout.html new file mode 100644 index 000000000..30df742fa --- /dev/null +++ b/pyload/web/app/templates/default/header/layout.html @@ -0,0 +1,61 @@ +<div class="span3"> + <div class="logo"></div> + <span class="title visible-large-screen">pyLoad</span> +</div> +<div class="span4 offset1"> + <div id="progress-area"> + <span id="progress-info"> + </span> + <div class="popover bottom"> + <div class="arrow"></div> + <div class="popover-inner"> + <h3 class="popover-title"> + {{_ "Running..."}} + <button type="button" class="close" aria-hidden="true">×</button> + </h3> + <div class="popover-content"> + <ul class="progress-list"></ul> + </div> + </div> + </div> + </div> +</div> +<div class="span4"> + <div class="header-block"> + <i class="icon-download-alt icon-white"></i> Max. Speed:<br> + <i class="icon-off icon-white"></i> Running:<br> + <i class="icon-refresh icon-white"></i> Reconnect:<br> + </div> + + <div class="header-block status-block"></div> + + <div class="header-btn"> + <div class="btn-group"> + <a class="btn btn-blue btn-small" href="#"><i class="icon-user icon-white"></i> {{ name }}</a> + <a class="btn btn-blue btn-small dropdown-toggle" data-toggle="dropdown" href="#"><span + class="caret"></span></a> + <ul class="dropdown-menu" style="right: 0; left: -100%"> + <li><a data-nav href="/"><i class="icon-list-alt"></i> Dashboard</a></li> + <li><a data-nav href="/settings"><i class="icon-wrench"></i> Settings</a></li> + <li><a data-nav href="/accounts"><i class="icon-key"></i> Accounts</a></li> + <li><a data-nav href="/admin"><i class="icon-cogs"></i> Admin</a></li> + <li class="divider"></li> + <li><a data-nav href="/logout"><i class="icon-signout"></i> Logout</a></li> + </ul> + </div> + <div class="btn-group lower"> + <button class="btn btn-success btn-grabber btn-mini" href="#"> + <i class="icon-plus icon-white"></i> + </button> + <button class="btn btn-blue btn-play btn-mini" href="#"> + <i class="icon-play icon-white"></i> + </button> + <button class="btn btn-danger btn-delete btn-mini" href="#"> + <i class="icon-remove icon-white"></i> + </button> + </div> + </div> +<span class="visible-desktop speedgraph-container"> + <div id="speedgraph"></div> +</span> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progress.html b/pyload/web/app/templates/default/header/progress.html new file mode 100644 index 000000000..740e18a4c --- /dev/null +++ b/pyload/web/app/templates/default/header/progress.html @@ -0,0 +1,10 @@ +{{ name }} +<span class="pull-right">{{ plugin }}</span> + +<div class="progress"> + <div class="bar" style="width: {{ percent }}%"></div> +</div> + +<div class="progress-status"> + <!-- rendered by progressInfo template --> +</div> diff --git a/pyload/web/app/templates/default/header/progressStatus.html b/pyload/web/app/templates/default/header/progressStatus.html new file mode 100644 index 000000000..2ee3719a5 --- /dev/null +++ b/pyload/web/app/templates/default/header/progressStatus.html @@ -0,0 +1,8 @@ +{{#if downloading }} + {{ formatSize done }} of {{ formatSize total }} ({{ formatSize download.speed }}/s) +{{ else }} + {{ statusmsg }} +{{/if}} +<span class="pull-right"> + {{ formatTimeLeft eta }} +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressSub.html b/pyload/web/app/templates/default/header/progressSub.html new file mode 100644 index 000000000..a3337e9bb --- /dev/null +++ b/pyload/web/app/templates/default/header/progressSub.html @@ -0,0 +1,6 @@ +{{#if linksqueue }} + {{ linksqueue }} downloads left ({{ formatSize sizequeue }}) +{{/if}} +<span class="pull-right"> + {{ formatTimeLeft etaqueue }} +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressSup.html b/pyload/web/app/templates/default/header/progressSup.html new file mode 100644 index 000000000..f2c0ac734 --- /dev/null +++ b/pyload/web/app/templates/default/header/progressSup.html @@ -0,0 +1,10 @@ +{{#if single }} + {{ truncate name 32}} ({{ statusmsg }}) +{{ else }} + {{#if downloads }} + {{ downloads }} downloads running {{#if speed }}({{ formatSize speed }}/s){{/if}} + {{ else }} + No running tasks + {{/if}} +{{/if}} +<i class="icon-list pull-right"></i>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressbar.html b/pyload/web/app/templates/default/header/progressbar.html new file mode 100644 index 000000000..2775e664b --- /dev/null +++ b/pyload/web/app/templates/default/header/progressbar.html @@ -0,0 +1,16 @@ + +<div class="sup"> +</div> + +<div class="progress" id="globalprogress"> + {{#if single }} + <div class="bar" style="width: {{ percent }}%"> + {{ else }} + <div class="bar {{#if downloads }}running{{/if}}"> + {{/if}} + </div> + </div> +</div> + +<div class="sub"> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/status.html b/pyload/web/app/templates/default/header/status.html new file mode 100644 index 000000000..f840b6e33 --- /dev/null +++ b/pyload/web/app/templates/default/header/status.html @@ -0,0 +1,3 @@ +<span class="pull-right maxspeed">{{ formatSize maxspeed }}/s</span><br> +<span class="pull-right running">{{ paused }}</span><br> +<span class="pull-right reconnect">{{#if reconnect }}true{{ else }}false{{/if}}</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/linkgrabber/modal.html b/pyload/web/app/templates/default/linkgrabber/modal.html new file mode 100755 index 000000000..3c50aa037 --- /dev/null +++ b/pyload/web/app/templates/default/linkgrabber/modal.html @@ -0,0 +1,42 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3> + {{_ "Add links" }} + <small>{{_ "paste & add links to pyLoad" }}</small> + </h3> +</div> + +<div class="modal-body"> + <div class="container-fluid"> + <div class="row-fluid"> + <div class="span4"> + <h3 class="pull-left">Links</h3> + <textarea id="inputLinks" rows="1" placeholder="{{_ " Paste your links here..."}}"></textarea> + </div> + <div class="span4"> + <form action="" method="post" enctype="multipart/form-data" target="uploadTarget"> + <h3 class="pull-left">{{_ "Container" }}</h3> + <button class="btn btn-blue btn-container">{{_ "Upload" }}</button> + <input type="file" name="data" id="inputContainer" style="display: none"> + </form> + <iframe id="uploadTarget" name="uploadTarget" style="display: none"></iframe> + </div> + <div class="span4"> + <h3 class="pull-left">{{_ "URL" }}</h3> + <input type="text" name="inputURL" id="inputURL" placeholder="{{ _ "Link to Website"}}"> + </div> + </div> + </div> + + <legend> + {{_ "Packages" }} <button class="btn btn-danger btn-small btn-remove-all"><i class="icon-trash"></i></button> + </legend> + <div class="container-fluid prepared-packages"> + + </div> +</div> + +<div class="modal-footer"> + <!--<a class="btn btn-success"><i class="icon-plus icon-white"></i> {{_ "Add"}}</a>--> + <a class="btn btn-close">{{_ "Close"}}</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/linkgrabber/package.html b/pyload/web/app/templates/default/linkgrabber/package.html new file mode 100644 index 000000000..d5d4c669b --- /dev/null +++ b/pyload/web/app/templates/default/linkgrabber/package.html @@ -0,0 +1,38 @@ +<span class="name"> + <strong>{{name }}</strong> + <input type="text" value="{{name}}"> +</span> - +<button class="btn btn-small btn-blue btn-expand"><i class="icon-arrow-down"></i> </button> <button class="btn btn-small btn-success btn-add"><i class="icon-plus"></i> </button> <button class="btn btn-small btn-danger btn-delete"><i class="icon-trash"></i> </button> <br> +<table class="table table-condensed" {{#unless expanded}}style="display: none"{{/unless}}> + <tbody> + {{#each links}} + <tr> + <td class="link-name">{{ name }}</td> + <td><img src="{{ pluginIcon plugin }}"> {{ plugin }}</td> + <td>{{ formatSize size }}</td> + <td>{{ linkStatus status }}</td> + <td><button class="btn btn-danger btn-mini" data-index={{@index}}><i class="icon-trash"></i></button></td> + </tr> + {{/each}} + </tbody> +</table> +<hr> +{{ ngettext "%d link" "%d links" length }} +{{#if size}} + - {{formatSize size}} +{{/if}} : +{{#if online}} +<span class="text-success"> + {{ online }} {{_ "online" }} +</span> +{{/if}} +{{#if offline}} +<span class="text-error"> + {{ offline }} {{_ "offline" }} +</span> +{{/if}} +{{#if unknown}} +<span class="text-info"> + {{ unknown }} {{_ "unknown" }} +</span> +{{/if}} diff --git a/pyload/web/app/templates/default/login.html b/pyload/web/app/templates/default/login.html new file mode 100644 index 000000000..9e8d9eeb6 --- /dev/null +++ b/pyload/web/app/templates/default/login.html @@ -0,0 +1,28 @@ +<br> +<div class="login"> + <form method="post" class="form-horizontal"> + <legend>Login</legend> + <div class="control-group"> + <label class="control-label" for="inputUser">Username</label> + <div class="controls"> + <input type="text" id="inputUser" 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> +<!-- TODO: Errors --> diff --git a/pyload/web/app/templates/default/notification.html b/pyload/web/app/templates/default/notification.html new file mode 100644 index 000000000..1b6d21e27 --- /dev/null +++ b/pyload/web/app/templates/default/notification.html @@ -0,0 +1,10 @@ +{{#if queries }} + <span class="btn-query"> + Queries <span class="badge badge-info">{{ queries }}</span> + </span> +{{/if}} +{{#if notifications }} + <span class="btn-notification"> + Notifications <span class="badge badge-success">{{ notifications }}</span> + </span> +{{/if}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/actionbar.html b/pyload/web/app/templates/default/settings/actionbar.html new file mode 100644 index 000000000..647d0af99 --- /dev/null +++ b/pyload/web/app/templates/default/settings/actionbar.html @@ -0,0 +1,5 @@ +<ul class="actionbar nav span8 offset3"> + <li> + <button class="btn btn-small btn-blue btn-add">Add Plugin</button> + </li> +</ul>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/config.html b/pyload/web/app/templates/default/settings/config.html new file mode 100644 index 000000000..fb7b0e727 --- /dev/null +++ b/pyload/web/app/templates/default/settings/config.html @@ -0,0 +1,17 @@ +<legend> + <div class="page-header"> + <h1>{{ label }} + <small>{{ description }}</small> + {{#if explanation }} + <a class="btn btn-small" data-title="Help" data-content="{{ explanation }}"><i + class="icon-question-sign"></i></a> + {{/if}} + </h1> + </div> +</legend> +<div class="control-content"> +</div> +<div class="form-actions"> + <button type="button" class="btn btn-primary">Save changes</button> + <button type="button" class="btn btn-reset">Reset</button> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/configItem.html b/pyload/web/app/templates/default/settings/configItem.html new file mode 100644 index 000000000..5b583b8df --- /dev/null +++ b/pyload/web/app/templates/default/settings/configItem.html @@ -0,0 +1,7 @@ + <div class="control-group"> + <label class="control-label">{{ label }}</label> + + <div class="controls"> + <!--{# <span class="help-inline">{{ description }}</span>#}--> + </div> + </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/layout.html b/pyload/web/app/templates/default/settings/layout.html new file mode 100644 index 000000000..143d0caad --- /dev/null +++ b/pyload/web/app/templates/default/settings/layout.html @@ -0,0 +1,11 @@ +<div class="span3"> + <ul class="nav nav-list well settings-menu"> + </ul> +</div> +<div class="span9"> + <div class="well setting-box"> + <form class="form-horizontal" action="#"> + <h1>Please choose a config section</h1> + </form> + </div> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/menu.html b/pyload/web/app/templates/default/settings/menu.html new file mode 100644 index 000000000..893fd7b5b --- /dev/null +++ b/pyload/web/app/templates/default/settings/menu.html @@ -0,0 +1,40 @@ +{{#if core}} +<li class="nav-header"><i class="icon-globe icon-white"></i> General</li> +{{#each core}} +<li data-name="{{ name }}"><a href="#">{{ label }}</a></li> +{{/each}} +{{/if}} +<li class="divider"></li> +<li class="nav-header"><i class="icon-th-large icon-white"></i> Addons</li> +{{#each addon }} +<li class="addon" data-name="{{ name }}"> + <a href="#" style="background-image: url({{ pluginIcon name }});"> + {{ label }} + <i class="icon-remove pull-right"></i> + {{#if activated }} + <div class="addon-on"> + active + {{else}} + <div class="addon-off"> + inactive + {{/if}} + {{#if user_context }} + <!--{# TODO: tooltip #}--> + <i class="icon-user pull-right"></i> + {{else}} + <i class="icon-globe pull-right"></i> + {{/if}} + </div> + </a> +</li> +{{/each}} +<li class="divider"></li> +<li class="nav-header"><i class="icon-th-list icon-white"></i> Plugin Configs</li> +{{#each plugin }} +<li class="plugin" data-name="{{ name }}"> + <a style="background-image: url({{ pluginIcon name }});"> + {{ label }} + <i class="icon-remove pull-right"></i> + </a> +</li> +{{/each}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/actionbar.html b/pyload/web/app/templates/default/setup/actionbar.html new file mode 100644 index 000000000..b109025b7 --- /dev/null +++ b/pyload/web/app/templates/default/setup/actionbar.html @@ -0,0 +1,24 @@ +<ul class="actionbar nav span8 offset3"> + <li class="pull-left"> + <ul class="breadcrumb"> + {{#each pages}} + <li> + <a href="#" class="{{#ifEq ../page @index}}active {{/ifEq}}select-page" + data-page="{{@index}}">{{this}}</a> + {{#ifEq ../max @index}} + + {{else}} + <span class="divider"> + <i class="icon-long-arrow-right"></i> + </span> + {{/ifEq}} + </li> + {{/each}} + </ul> + </li> + <li class="pull-right"> + <select> + <option>en</option> + </select> + </li> +</ul>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/error.html b/pyload/web/app/templates/default/setup/error.html new file mode 100644 index 000000000..37ce51283 --- /dev/null +++ b/pyload/web/app/templates/default/setup/error.html @@ -0,0 +1,14 @@ +{{#ifEq status 410}} + <h2 class="text-warning">{{ _ "Setup timed out" }}</h2> + <p>{{ _ "Setup was closed due to inactivity. Please restart it to continue configuration." }}</p> +{{else}} +{{#ifEq status 409}} + <h2 class="text-success">{{ _ "Setup finished" }}</h2> + <p>{{ _ "Setup was successful. You can restart pyLoad now." }}</p> +{{else}} + <h2 class="text-error"> + {{ _ "Setup failed" }} + </h2> + <p>{{ _ "Try to restart it or open a bug report." }}</p> +{{/ifEq}} +{{/ifEq}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/finished.html b/pyload/web/app/templates/default/setup/finished.html new file mode 100644 index 000000000..22a97649b --- /dev/null +++ b/pyload/web/app/templates/default/setup/finished.html @@ -0,0 +1,23 @@ +{{#if user}} + +<h2> + {{ _ "Nearly Done" }} +</h2> + +<p> + {{ _ "Please check your settings." }} +</p> + +<p> + <strong>Username:</strong> {{user}} +</p> + +<button class="btn btn-large btn-blue"> + {{ _ "Confirm" }} +</button> + +{{else}} + +<h2>{{ _ "Please add a user first." }}</h2> + +{{/if}} diff --git a/pyload/web/app/templates/default/setup/layout.html b/pyload/web/app/templates/default/setup/layout.html new file mode 100644 index 000000000..2e986173a --- /dev/null +++ b/pyload/web/app/templates/default/setup/layout.html @@ -0,0 +1,10 @@ +<div class="span3"> + <h1 class="vertical-header"> + {{ _ "Setup" }} + </h1> +</div> +<div class="span8"> + <div class="hero-unit setup-page"> + + </div> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/system.html b/pyload/web/app/templates/default/setup/system.html new file mode 100644 index 000000000..0c5023669 --- /dev/null +++ b/pyload/web/app/templates/default/setup/system.html @@ -0,0 +1,56 @@ +<h3>{{ _ "System" }} </h3> + +<dl class="dl-horizontal"> + {{#each system}} + <dt>{{ @key }}</dt> + <dd>{{ this }}</dd> + {{/each}} +</dl> + +<h3>{{_ "Dependencies" }}</h3> +<dl class="dl-horizontal"> + {{#each deps.core}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + <i class="icon-ok"></i> + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + <i class="icon-remove"></i> + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<h4>{{ _ "Optional" }}</h4> +<dl class="dl-horizontal"> + {{#each deps.opt}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + {{ _ "available" }} + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + {{ _ "not available" }} + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<button class="btn btn-blue"> + {{ _ "Next" }} +</button>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/user.html b/pyload/web/app/templates/default/setup/user.html new file mode 100644 index 000000000..5841276b7 --- /dev/null +++ b/pyload/web/app/templates/default/setup/user.html @@ -0,0 +1,34 @@ +<form class="form-horizontal"> + <div class="control-group"> + <label class="control-label"> + Username + </label> + + <div class="controls"> + <input type="text" id="username" placeholder="User" required> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password + </label> + + <div class="controls"> + <input type="password" id="password"> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password (again) + </label> + + <div class="controls"> + <input type="password" id="password2"> + </div> + </div> + <div class="control-group"> + <div class="controls"> + <a class="btn btn-blue">Submit</a> + </div> + </div> +</form> diff --git a/pyload/web/app/templates/default/setup/welcome.html b/pyload/web/app/templates/default/setup/welcome.html new file mode 100644 index 000000000..5a4f74d9f --- /dev/null +++ b/pyload/web/app/templates/default/setup/welcome.html @@ -0,0 +1,14 @@ +<h1>{{ _ "Welcome!" }}</h1> + +<p>{{ _ "pyLoad is running and ready for configuration." }}</p> + +<p> + {{ _ "Select your language:" }} + <select> + <option>en</option> + </select> +</p> + +<button class="btn btn-large btn-blue"> + {{ _ "Start configuration" }} +</button>
\ No newline at end of file diff --git a/pyload/web/app/unavailable.html b/pyload/web/app/unavailable.html new file mode 100644 index 000000000..091c4b6ef --- /dev/null +++ b/pyload/web/app/unavailable.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + <title>WebUI not available</title> +</head> +<body> + +<h1>WebUI not available</h1> +You are running a pyLoad version without prebuilt webUI. You can download a build from our website or deactivate the dev mode. +If desired you can build it yourself by running: +<ul> + <li>Install <a href="http://nodejs.org/download/">nodejs</a> for your OS. It's maybe already pre-installed. </li> + <li>npm -g install bower grunt-cli</li> + <li>Change to the pyload/web directory</li> + <i><li>npm install</li> + <li>bower install</li></i> +</ul> + +Everytime you want to test or apply your changes, you've made on the WebUI, run <i>grunt build</i> from the web directory. +</body> +</html> diff --git a/pyload/web/bower.json b/pyload/web/bower.json new file mode 100644 index 000000000..4da3634a0 --- /dev/null +++ b/pyload/web/bower.json @@ -0,0 +1,23 @@ +{ + "name": "pyload", + "version": "0.1.0", + "dependencies": { + "pyload-common": "https://github.com/pyload/pyload-common.git", + "requirejs": "~2.1.6", + "requirejs-text": "*", + "require-handlebars-plugin": "*", + "jquery": "~1.9.1", + "jquery.transit": "~0.9.9", + "jquery.cookie": "~1.3.1", + "jquery.animate-enhanced": "*", + "flot": "~0.8.1", + "underscore": "~1.5.1", + "backbone": "~1.0.0", + "backbone.marionette": "~1.1.0", + "handlebars.js": "1.0.0-rc.3", + "jed": "~0.5.4", + "select2": "~3.4.0", + "momentjs": "~2.1.0" + }, + "devDependencies": {} +} diff --git a/pyload/web/cnl_app.py b/pyload/web/cnl_app.py new file mode 100644 index 000000000..90aa76d72 --- /dev/null +++ b/pyload/web/cnl_app.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from os.path import join +import re +from urllib import unquote +from base64 import standard_b64decode +from binascii import unhexlify + +from pyload.utils.fs import save_filename + +from bottle import route, request, HTTPError +from webinterface import PYLOAD, DL_ROOT, JS + +try: + from Crypto.Cipher import AES +except: + pass + + +def local_check(function): + def _view(*args, **kwargs): + if request.environ.get('REMOTE_ADDR', "0") in ('127.0.0.1', 'localhost') \ + or request.environ.get('HTTP_HOST','0') in ('127.0.0.1:9666', 'localhost:9666'): + return function(*args, **kwargs) + else: + return HTTPError(403, "Forbidden") + + return _view + + +@route("/flash") +@route("/flash/:id") +@route("/flash", method="POST") +@local_check +def flash(id="0"): + return "JDownloader\r\n" + +@route("/flash/add", method="POST") +@local_check +def add(request): + package = request.POST.get('referer', None) + urls = filter(lambda x: x != "", request.POST['urls'].split("\n")) + + if package: + PYLOAD.addPackage(package, urls, 0) + else: + PYLOAD.generateAndAddPackages(urls, 0) + + return "" + +@route("/flash/addcrypted", method="POST") +@local_check +def addcrypted(): + + package = request.forms.get('referer', 'ClickAndLoad Package') + dlc = request.forms['crypted'].replace(" ", "+") + + dlc_path = join(DL_ROOT, save_filename(package) + ".dlc") + dlc_file = open(dlc_path, "wb") + dlc_file.write(dlc) + dlc_file.close() + + try: + PYLOAD.addPackage(package, [dlc_path], 0) + except: + return HTTPError() + else: + return "success\r\n" + +@route("/flash/addcrypted2", method="POST") +@local_check +def addcrypted2(): + + package = request.forms.get("source", None) + crypted = request.forms["crypted"] + jk = request.forms["jk"] + + crypted = standard_b64decode(unquote(crypted.replace(" ", "+"))) + if JS: + jk = "%s f()" % jk + jk = JS.eval(jk) + + else: + try: + jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1] + except: + ## Test for some known js functions to decode + if jk.find("dec") > -1 and jk.find("org") > -1: + org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1] + jk = list(org) + jk.reverse() + jk = "".join(jk) + else: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + + try: + Key = unhexlify(jk) + except: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + return "failed" + + IV = Key + + obj = AES.new(Key, AES.MODE_CBC, IV) + result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n") + + result = filter(lambda x: x != "", result) + + try: + if package: + PYLOAD.addPackage(package, result, 0) + else: + PYLOAD.generateAndAddPackages(result, 0) + except: + return "failed can't add" + else: + return "success\r\n" + +@route("/flashgot_pyload") +@route("/flashgot_pyload", method="POST") +@route("/flashgot") +@route("/flashgot", method="POST") +@local_check +def flashgot(): + if request.environ['HTTP_REFERER'] != "http://localhost:9666/flashgot" and request.environ['HTTP_REFERER'] != "http://127.0.0.1:9666/flashgot": + return HTTPError() + + autostart = int(request.forms.get('autostart', 0)) + package = request.forms.get('package', None) + urls = filter(lambda x: x != "", request.forms['urls'].split("\n")) + folder = request.forms.get('dir', None) + + if package: + PYLOAD.addPackage(package, urls, autostart) + else: + PYLOAD.generateAndAddPackages(urls, autostart) + + return "" + +@route("/crossdomain.xml") +@local_check +def crossdomain(): + rep = "<?xml version=\"1.0\"?>\n" + rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" + rep += "<cross-domain-policy>\n" + rep += "<allow-access-from domain=\"*\" />\n" + rep += "</cross-domain-policy>" + return rep + + +@route("/flash/checkSupportForUrl") +@local_check +def checksupport(): + + url = request.GET.get("url") + res = PYLOAD.checkURLs([url]) + supported = (not res[0][1] is None) + + return str(supported).lower() + +@route("/jdcheck.js") +@local_check +def jdcheck(): + rep = "jdownloader=true;\n" + rep += "var version='9.581;'" + return rep diff --git a/pyload/web/middlewares.py b/pyload/web/middlewares.py new file mode 100644 index 000000000..af355bf11 --- /dev/null +++ b/pyload/web/middlewares.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class StripPathMiddleware(object): + def __init__(self, app): + self.app = app + + def __call__(self, e, h): + e['PATH_INFO'] = e['PATH_INFO'].rstrip('/') + return self.app(e, h) + + +class PrefixMiddleware(object): + def __init__(self, app, prefix="/pyload"): + self.app = app + self.prefix = prefix + + def __call__(self, e, h): + path = e["PATH_INFO"] + if path.startswith(self.prefix): + e['PATH_INFO'] = path.replace(self.prefix, "", 1) + return self.app(e, h) diff --git a/pyload/web/package.json b/pyload/web/package.json new file mode 100644 index 000000000..e1a2defb7 --- /dev/null +++ b/pyload/web/package.json @@ -0,0 +1,37 @@ +{ + "name": "pyload", + "version": "0.1.0", + "repository": { + "type": "git", + "url": "git://github.com/pyload/pyload.git" + }, + "dependencies": {}, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-copy": "~0.4.1", + "grunt-contrib-concat": "~0.1.3", + "grunt-contrib-uglify": "~0.2.2", + "grunt-contrib-jshint": "~0.4.1", + "grunt-contrib-less": "~0.5.2", + "grunt-contrib-cssmin": "~0.6.0", + "grunt-contrib-connect": "~0.2.0", + "grunt-contrib-clean": "~0.4.0", + "grunt-contrib-htmlmin": "~0.1.3", + "grunt-contrib-requirejs": "~0.4.0", + "grunt-contrib-imagemin": "~0.1.3", + "grunt-contrib-watch": "~0.4.0", + "grunt-contrib-compress": "~0.5.0", + "grunt-rev": "~0.1.0", + "grunt-usemin": "~0.1.10", + "grunt-mocha": "~0.3.0", + "grunt-open": "~0.2.0", + "grunt-svgmin": "~0.1.0", + "grunt-concurrent": "~0.1.0", + "matchdep": "~0.1.1", + "rjs-build-analysis": "0.0.3", + "connect-livereload": "~0.2.0" + }, + "engines": { + "node": ">=0.8.0" + } +} diff --git a/pyload/web/pyload_app.py b/pyload/web/pyload_app.py new file mode 100644 index 000000000..50d9b9731 --- /dev/null +++ b/pyload/web/pyload_app.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: RaNaN +""" +import time +from os.path import join, exists + +from bottle import route, static_file, response, request, redirect, template + +from webinterface import PYLOAD, PROJECT_DIR, SETUP, APP_PATH, UNAVAILALBE + +from utils import login_required, add_json_header, select_language + +from pyload.utils import json_dumps + +APP_ROOT = join(PROJECT_DIR, APP_PATH) + +# Cache file names that are available gzipped +GZIPPED = {} + + +@route('/icons/<path:path>') +def serve_icon(path): + # TODO + return redirect('/images/icon.png') + # return static_file(path, root=join("tmp", "icons")) + + +@route("/download/:fid") +@login_required('Download') +def download(fid, api): + # TODO: check owner ship + path, name = api.getFilePath(fid) + return static_file(name, path, download=True) + + +@route("/i18n") +@route("/i18n/:lang") +def i18n(lang=None): + add_json_header(response) + + if lang is None: + pass + # TODO use lang from PYLOAD.config or setup + else: + # TODO auto choose language + lang = select_language(["en"]) + + return json_dumps({}) + +@route('/') +def index(): + if UNAVAILALBE: + return serve_static("unavailable.html") + + resp = serve_static('index.html') + # set variable depending on setup mode + setup = 'false' if SETUP is None else 'true' + ws = PYLOAD.getWSAddress() if PYLOAD else False + web = None + if PYLOAD: + web = PYLOAD.getConfigValue('webinterface', 'port') + elif SETUP: + web = SETUP.config['webinterface']['port'] + + # Render variables into the html page + if resp.status_code == 200: + content = resp.body.read() + resp.body = template(content, ws=ws, web=web, setup=setup) + resp.content_length = len(resp.body) + + return resp + +# Very last route that is registered, could match all uris +@route('/<path:path>') +def serve_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" + + # save if this resource is available as gz + if path not in GZIPPED: + GZIPPED[path] = exists(join(APP_ROOT, path + ".gz")) + + # gzipped and clients accepts it + # TODO: index.html is not gzipped, because of template processing + if GZIPPED[path] and "gzip" in request.get_header("Accept-Encoding", "") and path != "index.html": + response.headers['Vary'] = 'Accept-Encoding' + response.headers['Content-Encoding'] = 'gzip' + path += ".gz" + + resp = static_file(path, root=APP_ROOT) + # Also serve from .tmp folder in dev mode + if resp.status_code == 404 and APP_PATH == "app": + return static_file(path, root=join(PROJECT_DIR, '.tmp')) + + return resp
\ No newline at end of file diff --git a/pyload/web/servers.py b/pyload/web/servers.py new file mode 100644 index 000000000..a3c51e36b --- /dev/null +++ b/pyload/web/servers.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from bottle import ServerAdapter as BaseAdapter + +class ServerAdapter(BaseAdapter): + SSL = False + NAME = "" + + def __init__(self, host, port, key, cert, connections, debug, **kwargs): + BaseAdapter.__init__(self, host, port, **kwargs) + self.key = key + self.cert = cert + self.connection = connections + self.debug = debug + + @classmethod + def find(cls): + """ Check if server is available by trying to import it + + :raises Exception: importing C dependant library could also fail with other reasons + :return: True on success + """ + try: + __import__(cls.NAME) + return True + except ImportError: + return False + + def run(self, handler): + raise NotImplementedError + + +class CherryPyWSGI(ServerAdapter): + SSL = True + NAME = "threaded" + + @classmethod + def find(cls): + return True + + def run(self, handler): + from wsgiserver import CherryPyWSGIServer + + if self.cert and self.key: + CherryPyWSGIServer.ssl_certificate = self.cert + CherryPyWSGIServer.ssl_private_key = self.key + + server = CherryPyWSGIServer((self.host, self.port), handler, numthreads=self.connection) + server.start() + + +class FapwsServer(ServerAdapter): + """ Does not work very good currently """ + + NAME = "fapws" + + def run(self, handler): # pragma: no cover + import fapws._evwsgi as evwsgi + from fapws import base, config + + port = self.port + if float(config.SERVER_IDENT[-2:]) > 0.4: + # fapws3 silently changed its API in 0.5 + port = str(port) + evwsgi.start(self.host, port) + evwsgi.set_base_module(base) + + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return handler(environ, start_response) + + evwsgi.wsgi_cb(('', app)) + evwsgi.run() + + +# TODO: ssl +class MeinheldServer(ServerAdapter): + SSL = True + NAME = "meinheld" + + def run(self, handler): + from meinheld import server + + if self.quiet: + server.set_access_logger(None) + server.set_error_logger(None) + + server.listen((self.host, self.port)) + server.run(handler) + +# todo:ssl +class TornadoServer(ServerAdapter): + """ The super hyped asynchronous server by facebook. Untested. """ + + SSL = True + NAME = "tornado" + + def run(self, handler): # pragma: no cover + import tornado.wsgi, tornado.httpserver, tornado.ioloop + + container = tornado.wsgi.WSGIContainer(handler) + server = tornado.httpserver.HTTPServer(container) + server.listen(port=self.port) + tornado.ioloop.IOLoop.instance().start() + + +class BjoernServer(ServerAdapter): + """ Fast server written in C: https://github.com/jonashaag/bjoern """ + + NAME = "bjoern" + + def run(self, handler): + from bjoern import run + + run(handler, self.host, self.port) + + +# todo: ssl +class EventletServer(ServerAdapter): + + SSL = True + NAME = "eventlet" + + def run(self, handler): + from eventlet import wsgi, listen + + try: + wsgi.server(listen((self.host, self.port)), handler, + log_output=(not self.quiet)) + except TypeError: + # Needed to ignore the log + class NoopLog: + def write(self, *args): + pass + + # Fallback, if we have old version of eventlet + wsgi.server(listen((self.host, self.port)), handler, log=NoopLog()) + + +class FlupFCGIServer(ServerAdapter): + + SSL = False + NAME = "flup" + + def run(self, handler): # pragma: no cover + import flup.server.fcgi + from flup.server.threadedserver import ThreadedServer + + def noop(*args, **kwargs): + pass + + # Monkey patch signal handler, it does not work from threads + ThreadedServer._installSignalHandlers = noop + + self.options.setdefault('bindAddress', (self.host, self.port)) + flup.server.fcgi.WSGIServer(handler, **self.options).run() + +# Order is important and gives every server precedence over others! +all_server = [BjoernServer, TornadoServer, EventletServer, CherryPyWSGI] +# Some are deactivated because they have some flaws +##all_server = [FapwsServer, MeinheldServer, BjoernServer, TornadoServer, EventletServer, CherryPyWSGI]
\ No newline at end of file diff --git a/pyload/web/setup_app.py b/pyload/web/setup_app.py new file mode 100644 index 000000000..939fcb600 --- /dev/null +++ b/pyload/web/setup_app.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from time import time + +from pyload.utils import json_dumps + +from bottle import route, request, response, HTTPError, redirect + +from webinterface import PROJECT_DIR, SETUP + +from utils import add_json_header + +# returns http error +def error(code, msg): + return HTTPError(code, json_dumps(msg), **dict(response.headers)) + + +def setup_required(func): + def _view(*args, **kwargs): + global timestamp + + # setup needs to be running + if SETUP is None: + return error(404, "Not Found") + + # setup finished + if timestamp == 0: + return error(409, "Done") + + # setup timed out due to inactivity + if timestamp + TIMEOUT * 60 < time(): + return error(410, "Timeout") + + timestamp = time() + + return func(*args, **kwargs) + + return _view + +# setup will close after inactivity +TIMEOUT = 15 +timestamp = time() + + +@route("/setup") +@setup_required +def setup(): + add_json_header(response) + + return json_dumps({ + "system": SETUP.check_system(), + "deps": SETUP.check_deps() + }) + + +@route("/setup_done") +@setup_required +def setup_done(): + global timestamp + add_json_header(response) + + SETUP.addUser( + request.params['user'], + request.params['password'] + ) + + SETUP.save() + + # mark setup as finished + timestamp = 0 + + return error(409, "Done") diff --git a/pyload/web/utils.py b/pyload/web/utils.py new file mode 100644 index 000000000..7e8ee3f13 --- /dev/null +++ b/pyload/web/utils.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from bottle import request, HTTPError, redirect + +try: + import zlib +except ImportError: + zlib = None + +from webinterface import PYLOAD, SETUP + + +def add_json_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", request.get_header('Origin', '*')) + r.headers.append("Access-Control-Allow-Credentials", "true") + + +def set_session(request, user): + s = request.environ.get('beaker.session') + s["uid"] = user.uid + s.save() + return s + + +def get_user_api(s): + if s: + uid = s.get("uid", None) + if (uid is not None) and (PYLOAD is not None): + return PYLOAD.withUserContext(uid) + 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 select_language(langs): + accept = request.headers.get('Accept-Language', '') + # TODO + + return langs[0] + + +def login_required(perm=None): + def _dec(func): + def _view(*args, **kwargs): + + # In case of setup, no login methods can be accessed + if SETUP is not None: + redirect("/setup") + + s = request.environ.get('beaker.session') + api = get_user_api(s) + if api is not None: + if 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': + return HTTPError(403, "Forbidden") + else: + return redirect("/login") + + return _view + + return _dec diff --git a/pyload/web/webinterface.py b/pyload/web/webinterface.py new file mode 100644 index 000000000..21c5f4a03 --- /dev/null +++ b/pyload/web/webinterface.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2013 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 sys + +from os.path import join, abspath, dirname, exists + +PROJECT_DIR = abspath(dirname(__file__)) +PYLOAD_DIR = abspath(join(PROJECT_DIR, "..", "..")) + +import bottle +from bottle import run, app + +from middlewares import StripPathMiddleware, PrefixMiddleware + +SETUP = None +PYLOAD = None + +import ServerThread + +if not ServerThread.core: + if ServerThread.setup: + SETUP = ServerThread.setup + config = SETUP.config + else: + raise Exception("Could not access pyLoad Core") +else: + PYLOAD = ServerThread.core.api + config = ServerThread.core.config + +from pyload.utils.JsEngine import JsEngine +JS = JsEngine() + +TEMPLATE = config.get('webinterface', 'template') +DL_ROOT = config.get('general', 'download_folder') +PREFIX = config.get('webinterface', 'prefix') + +if PREFIX: + PREFIX = PREFIX.rstrip("/") + if PREFIX and not PREFIX.startswith("/"): + PREFIX = "/" + PREFIX + +APP_PATH = "app" +UNAVAILALBE = True + +# webUI build is available +if exists(join(PROJECT_DIR, "app", "components")) and exists(join(PROJECT_DIR, ".tmp")) and config.get('webinterface', 'develop'): + UNAVAILALBE = False +elif exists(join(PROJECT_DIR, "dist", "index.html")): + APP_PATH = "dist" + UNAVAILALBE = False + +DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv +bottle.debug(DEBUG) + + +# Middlewares +from beaker.middleware import SessionMiddleware + +session_opts = { + 'session.type': 'file', + 'session.cookie_expires': False, + 'session.data_dir': './tmp', + 'session.auto': False +} + +session = SessionMiddleware(app(), session_opts) +web = StripPathMiddleware(session) + +if PREFIX: + web = PrefixMiddleware(web, prefix=PREFIX) + +import api_app +import cnl_app +import setup_app +# Last routes to register, +import pyload_app + +# Server Adapter +def run_server(host, port, server): + run(app=web, host=host, port=port, quiet=True, server=server) + + +if __name__ == "__main__": + run(app=web, port=8001) @@ -1,3 +1,6 @@ +[metadata] +description-file = README.md + [build_sphinx] source-dir = doc build-dir = doc/_build @@ -5,3 +8,9 @@ all_files = 1 [upload_sphinx] upload-dir = doc/_build/html + +[pep8] +format = default +exclude = .git,lib,apitypes.py,apitypes_debug.py,thriftgen +ignore = W292,E261,E262,E302,E701 +max-line-length = 139 diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..21664ccc8 --- /dev/null +++ b/setup.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +from os import path + +from setuptools import setup + +PROJECT_DIR = path.abspath(path.dirname(__file__)) + +extradeps = [] +if sys.version_info <= (2, 5): + extradeps += 'simplejson' + +from pyload import __version__ + +setup( + name="pyload", + version=__version__, + description='Fast, lightweight and full featured download manager.', + long_description=open(path.join(PROJECT_DIR, "README.md")).read(), + keywords=('pyload', 'download-manager', 'one-click-hoster', 'download'), + url="http://pyload.org", + download_url='http://pyload.org/download', + license='AGPL v3', + author="pyLoad Team", + author_email="support@pyload.org", + platforms=('Any',), + #package_dir={'pyload': 'src'}, + packages=['pyload'], + #package_data=find_package_data(), + #data_files=[], + include_package_data=True, + exclude_package_data={'pyload': ['docs*', 'scripts*', 'tests*']}, #exluced from build but not from sdist + # 'bottle >= 0.10.0' not in list, because its small and contain little modifications + install_requires=['pycurl', 'Beaker >= 1.6'] + extradeps, + extras_require={ + 'SSL': ["pyOpenSSL"], + 'DLC': ['pycrypto'], + 'Lightweight webserver': ['bjoern'], + 'RSS plugins': ['feedparser'], + 'Few Hoster plugins': ['BeautifulSoup>=3.2, <3.3'] + }, + #setup_requires=["setuptools_hg"], + test_suite='nose.collector', + tests_require=['nose', 'websocket-client >= 0.8.0', 'requests >= 1.2.2'], + entry_points={ + 'console_scripts': [ + 'pyload = pyload.Core:main', + 'pyload-cli = pyload.cli.Cli:main' + ]}, + zip_safe=False, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Topic :: Internet :: WWW/HTTP", + "Environment :: Console", + "Environment :: Web Environment", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2" + ] +)
\ No newline at end of file diff --git a/systemCheck.py b/systemCheck.py index 60fe0313b..bc9605a8a 100644 --- a/systemCheck.py +++ b/systemCheck.py @@ -4,44 +4,36 @@ import sys #from module import InitHomeDir -#very ugly prints, but at least it works with python 3 - def main(): print("##### System Information #####\n") - print("Platform:", sys.platform) - print("Operating System:", os.name) - print("Python:", sys.version.replace("\n", "")+ "\n") + print("Platform: " + sys.platform) + print("Operating System: " + os.name) + print("Python: " + sys.version.replace("\n", "")+ "\n") try: import pycurl - print("pycurl:", pycurl.version) + print("pycurl: " + pycurl.version) except: - print("pycurl:", "missing") + print("pycurl: missing") try: import Crypto - print("py-crypto:", Crypto.__version__) + print("py-crypto: " + Crypto.__version__) except: - print("py-crypto:", "missing") + print("py-crypto: missing") try: import OpenSSL - print("OpenSSL:", OpenSSL.version.__version__) + print("OpenSSL: " + OpenSSL.version.__version__) except: - print("OpenSSL:", "missing") + print("OpenSSL: missing") try: import Image - print("image libary:", Image.VERSION) + print("image library: " + Image.VERSION) except: - print("image libary:", "missing") - - try: - import PyQt4.QtCore - print("pyqt:", PyQt4.QtCore.PYQT_VERSION_STR) - except: - print("pyqt:", "missing") + print("image library: missing") print("\n\n##### System Status #####") print("\n## pyLoadCore ##") @@ -50,10 +42,10 @@ 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") + core_err.append("Your python version is too old, Please use at least Python 2.5") try: import pycurl @@ -64,18 +56,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_info.append("Install tesseract to try automatic captcha resolution.") try: import OpenSSL @@ -94,24 +86,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 +94,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,15 +102,19 @@ 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) - + if __name__ == "__main__": main() - raw_input("Press Enter to Exit.") + # comp. with py2 and 3 + try: + input("Press Enter to Exit.") + except SyntaxError: # will raise in py2 + pass 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..42585939e --- /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 pyload.plugins.Base import Fail +from pyload.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..4f32ee9e7 --- /dev/null +++ b/tests/HosterPluginTester.py @@ -0,0 +1,149 @@ +# -*- 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 + +from nose.tools import nottest + +from helper.Stubs import Core +from helper.parser import parse_config +from helper.PluginTester import PluginTester + +from pyload.datatypes.PyFile import PyFile, statusMap +from pyload.plugins.Base import Fail +from pyload.utils import accumulate +from pyload.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, status): + # Print to stdout to see whats going on + print "%s: %s, %s" % (name, url.encode("utf8"), status) + log(DEBUG, "%s: %s, %s", name, url.encode("utf8"), status) + + # url and plugin should be only important thing + pyfile = PyFile(self.core, -1, url, url, 0, 0, 0, 0, url, name, "", 0, 0, 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 status == "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 status == "failed": + pass + elif isinstance(e, Fail) and status == "offline" and e.message == "offline": + pass + else: + raise + +# setup methods +c = Core() + +sections = parse_config(join(dirname(__file__), "hosterlinks.txt")) + +for f in sections["files"]: + name, hash = f.rsplit(" ", 1) + HosterPluginTester.files[name] = str(hash) + +del sections["files"] + +urls = [] +status = {} + +for k, v in sections.iteritems(): + if k not in statusMap: + print "Unknown status %s" % k + for url in v: + urls.append(url) + status[url] = k + +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, status, sig): + def _test(self): + self.test_plugin(__plugin, url, status) + + _test.func_name = sig + return _test + + tmp_status = status.get(url) + if tmp_status != "online": + sig = "test_LINK%d_%s" % (i, tmp_status) + else: + sig = "test_LINK%d" % i + + # set test method + setattr(_testerClass, sig, meta(plugin, url, tmp_status, sig)) + + #register class + locals()[plugin] = _testerClass + # remove from locals, or tested twice + del _testerClass diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/__init__.py diff --git a/tests/api/ApiProxy.py b/tests/api/ApiProxy.py new file mode 100644 index 000000000..0da79a204 --- /dev/null +++ b/tests/api/ApiProxy.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + + +from pyload.remote.apitypes_debug import classes, methods + +from tests.helper.config import credentials + +class ApiProxy: + """ Proxy that does type checking on the api """ + + def __init__(self, api, user=credentials[0], pw=credentials[1]): + self.api = api + self.user = user + self.pw = pw + + if user and pw is not None: + self.api.login(user, pw) + + def assert_type(self, result, type): + if not type: return # void + try: + # Complex attribute + if isinstance(type, tuple): + # Optional result + if type[0] is None: + # Only check if not None + if result is not None: self.assert_type(result, type[1]) + + # List + elif type[0] == list: + assert isinstance(result, list) + for item in result: + self.assert_type(item, type[1]) + # Dict + elif type[0] == dict: + assert isinstance(result, dict) + for k, v in result.iteritems(): + self.assert_type(k, type[1]) + self.assert_type(v, type[2]) + + # Struct - Api class + elif hasattr(result, "__name__") and result.__name__ in classes: + for attr, atype in zip(result.__slots__, classes[result.__name__]): + self.assert_type(getattr(result, attr), atype) + else: # simple object + assert isinstance(result, type) + except AssertionError: + print "Assertion for %s as %s failed" % (result, type) + raise + + + def call(self, func, *args, **kwargs): + result = getattr(self.api, func)(*args, **kwargs) + self.assert_type(result, methods[func]) + + return result + + + def __getattr__(self, item): + def call(*args, **kwargs): + return self.call(item, *args, **kwargs) + + return call + +if __name__ == "__main__": + + from pyload.remote.JSONClient import JSONClient + + api = ApiProxy(JSONClient(), "User", "test") + api.getServerVersion()
\ No newline at end of file diff --git a/tests/api/ApiTester.py b/tests/api/ApiTester.py new file mode 100644 index 000000000..e9a185947 --- /dev/null +++ b/tests/api/ApiTester.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +from pyload.remote.JSONClient import JSONClient +from pyload.remote.WSClient import WSClient + +from tests.helper.config import webAddress, wsAddress + +from ApiProxy import ApiProxy + +class ApiTester: + + tester= [] + + @classmethod + def register(cls, tester): + cls.tester.append(tester) + + @classmethod + def get_methods(cls): + """ All available methods for testing """ + methods = [] + for t in cls.tester: + methods.extend(getattr(t, attr) for attr in dir(t) if attr.startswith("test_")) + return methods + + def __init__(self): + ApiTester.register(self) + self.api = None + + def setApi(self, api): + self.api = api + + def enableJSON(self): + self.api = ApiProxy(JSONClient(webAddress)) + + def enableWS(self): + self.api = ApiProxy(WSClient(wsAddress)) diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/api/__init__.py diff --git a/tests/api/test_JSONBackend.py b/tests/api/test_JSONBackend.py new file mode 100644 index 000000000..d13d6709f --- /dev/null +++ b/tests/api/test_JSONBackend.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +from nose.tools import raises, assert_equal + +from requests.auth import HTTPBasicAuth +import requests + +import json + +from pyload.remote.apitypes import Forbidden +from pyload.remote.JSONClient import JSONClient + +from tests.helper.config import credentials, webAddress + +class TestJSONBackend: + + def setUp(self): + self.client = JSONClient(webAddress) + + def test_login(self): + self.client.login(*credentials) + self.client.getServerVersion() + self.client.logout() + + def test_wronglogin(self): + ret = self.client.login("WrongUser", "wrongpw") + assert ret is False + + def test_httpauth(self): + # cheap http auth + ret = requests.get(webAddress + "/getServerVersion", auth=HTTPBasicAuth(*credentials)) + assert_equal(ret.status_code, 200) + assert ret.text + + def test_jsonbody(self): + payload = {'section': 'webinterface', 'option': 'port'} + headers = {'content-type': 'application/json'} + + ret = requests.get(webAddress + "/getConfigValue", headers=headers, + auth=HTTPBasicAuth(*credentials), data=json.dumps(payload)) + + assert_equal(ret.status_code, 200) + assert ret.text + + @raises(Forbidden) + def test_access(self): + self.client.getServerVersion() + + @raises(AttributeError) + def test_unknown_method(self): + self.client.login(*credentials) + self.client.sdfdsg()
\ No newline at end of file diff --git a/tests/api/test_WebSocketBackend.py b/tests/api/test_WebSocketBackend.py new file mode 100644 index 000000000..a9288104f --- /dev/null +++ b/tests/api/test_WebSocketBackend.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +from nose.tools import raises + +from pyload.remote.apitypes import Forbidden +from pyload.remote.WSClient import WSClient + +from tests.helper.config import credentials, wsAddress + +class TestWebSocketBackend: + + def setUp(self): + self.client = WSClient(wsAddress) + self.client.connect() + + def tearDown(self): + self.client.close() + + def test_login(self): + self.client.login(*credentials) + self.client.getServerVersion() + self.client.logout() + + def test_wronglogin(self): + ret = self.client.login("WrongUser", "wrongpw") + assert ret is False + + @raises(Forbidden) + def test_access(self): + self.client.getServerVersion() + + @raises(AttributeError) + def test_unknown_method(self): + self.client.login(*credentials) + self.client.sdfdsg() diff --git a/tests/api/test_api.py b/tests/api/test_api.py new file mode 100644 index 000000000..668470fe4 --- /dev/null +++ b/tests/api/test_api.py @@ -0,0 +1,50 @@ +from unittest import TestCase +from random import choice + +from pyload.Core import Core + +from ApiTester import ApiTester + + +class TestAPI(TestCase): + """ + Test all available testers randomly and on all backends + """ + _multiprocess_can_split_ = True + core = None + + #TODO: parallel testing + @classmethod + def setUpClass(cls): + from test_noargs import TestNoArgs + + cls.core = Core() + cls.core.start(False, False, True) + for Test in (TestNoArgs,): + t = Test() + t.enableJSON() + t = Test() + t.enableWS() + t = Test() + t.setApi(cls.core.api) + + cls.methods = ApiTester.get_methods() + + @classmethod + def tearDownClass(cls): + cls.core.shutdown() + + def test_random(self, n=10000): + for i in range(n): + func = choice(self.methods) + func() + + def test_random2(self, n): + self.test_random(n) + + def test_random3(self, n): + self.test_random(n) + + def test_random4(self, n): + self.test_random(n) + diff --git a/tests/api/test_noargs.py b/tests/api/test_noargs.py new file mode 100644 index 000000000..e84946e45 --- /dev/null +++ b/tests/api/test_noargs.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import inspect + +from ApiTester import ApiTester + +from pyload.remote.apitypes import Iface + +IGNORE = ('quit', 'restart') + +class TestNoArgs(ApiTester): + def setUp(self): + self.enableJSON() + +# Setup test_methods dynamically, only these which require no arguments +for name in dir(Iface): + if name.startswith("_") or name in IGNORE: continue + + spec = inspect.getargspec(getattr(Iface, name)) + if len(spec.args) == 1 and (not spec.varargs or len(spec.varargs) == 0): + def meta_test(name): #retain local scope + def test(self): + getattr(self.api, name)() + test.func_name = "test_%s" % name + return test + + setattr(TestNoArgs, "test_%s" % name, meta_test(name)) + + del meta_test
\ No newline at end of file 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/code_analysis.sh b/tests/code_analysis.sh new file mode 100755 index 000000000..e027bac2f --- /dev/null +++ b/tests/code_analysis.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +sloccount --duplicates --wide --details pyload > sloccount.sc + +echo "Running pep8" +pep8 pyload > pep8.txt + +echo "Running pylint" +pylint --reports=no pyload > pylint.txt || exit 0 + +#echo "Running pyflakes" +# pyflakes to pylint syntak +#find -name '*.py' |egrep -v '^.(/tests/|/pyload/lib)'|xargs pyflakes > pyflakes.log || : +# Filter warnings and strip ./ from path +#cat pyflakes.log | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' | grep -i -E -v "'_'|pypath|webinterface|pyreq|addonmanager" > pyflakes.txt +#sed -i 's/^.\///g' pyflakes.txt
\ No newline at end of file diff --git a/tests/config/pyload.conf.org b/tests/config/pyload.conf.org new file mode 100644 index 000000000..0a422f258 --- /dev/null +++ b/tests/config/pyload.conf.org @@ -0,0 +1,75 @@ +version: 2 + +[remote] +nolocalauth = False +activated = True +port = 7558 +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 = 8921 + +[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 Binary files differnew file mode 100644 index 000000000..26af474c1 --- /dev/null +++ b/tests/config/pyload.db.org diff --git a/tests/crypterlinks.txt b/tests/crypterlinks.txt new file mode 100644 index 000000000..2873915c0 --- /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.ddlstorage.com/folder/EmkJPLTYJp||4 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..b3c93311c --- /dev/null +++ b/tests/helper/PluginTester.py @@ -0,0 +1,119 @@ +# -*- 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 glob import glob + +from pycurl import LOW_SPEED_TIME, FORM_FILE +from json import loads + +from Stubs import Thread, Core, noop + +from pyload.network.RequestFactory import getRequest +from pyload.plugins.Base import Abort, Fail +from pyload.plugins.Hoster import Hoster + +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() + + 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("CaptchaService config %s not found." % conf) + f = open(conf, "rb") + req = getRequest() + + #raise timeout threshold + req.c.setopt(LOW_SPEED_TIME, 300) + + 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 invalidCaptcha(self): + log(DEBUG, "Captcha invalid") + +Hoster.invalidCaptcha = invalidCaptcha + +def correctCaptcha(self): + log(DEBUG, "Captcha correct") + +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..81b7d8a09 --- /dev/null +++ b/tests/helper/Stubs.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +import sys +from os.path import abspath, dirname, join +from time import strftime +from traceback import format_exc +from collections import defaultdict + +sys.path.append(abspath(join(dirname(__file__), "..", "..", "pyload", "lib"))) +sys.path.append(abspath(join(dirname(__file__), "..", ".."))) + +import __builtin__ + +from pyload.Api import Role +from pyload.datatypes.User import User +from pyload.datatypes.PyPackage import PyPackage +from pyload.threads.BaseThread import BaseThread +from pyload.config.ConfigParser import ConfigParser +from pyload.network.RequestFactory import RequestFactory +from pyload.PluginManager import PluginManager +from pyload.utils.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.evm = NoopClass() + self.interactionManager = self.im = NoopClass() + self.scheduler = NoopClass() + self.js = JsEngine() + self.cache = {} + self.packageCache = {} + + self.statusMsg = defaultdict(lambda: "statusmsg") + + self.log = LogStub() + + def getServerVersion(self): + return "TEST_RUNNER on %s" % strftime("%d %h %Y") + + def path(self, path): + return path + + def updateFile(self, *args): + pass + + def updatePackage(self, *args): + pass + + def processingIds(self, *args): + return [] + + def getPackage(self, *args): + return PyPackage(self, 0, "tmp", "tmp", -1, 0, "", "", "", 0, "", 0, 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 + +adminUser = User(None, uid=0, role=Role.Admin) +normalUser = User(None, uid=1, role=Role.User)
\ No newline at end of file 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/helper/config.py b/tests/helper/config.py new file mode 100644 index 000000000..81f7e6768 --- /dev/null +++ b/tests/helper/config.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +# Test configuration +credentials = ("TestUser", "pwhere") +webPort = 8921 +wsPort = 7558 + +webAddress = "http://localhost:%d/api" % webPort +wsAddress = "ws://localhost:%d/api" % wsPort
\ No newline at end of file diff --git a/tests/helper/parser.py b/tests/helper/parser.py new file mode 100644 index 000000000..5031ca7c3 --- /dev/null +++ b/tests/helper/parser.py @@ -0,0 +1,22 @@ + +import codecs + +def parse_config(path): + f = codecs.open(path, "rb", "utf_8") + result = {} + + current_section = None + for line in f.readlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + + if line.startswith("["): + current_section = line.replace("[", "").replace("]", "") + result[current_section] = [] + else: + if not current_section: + raise Exception("Line without section: %s" % line) + result[current_section].append(line) + + return result diff --git a/tests/hosterlinks.txt b/tests/hosterlinks.txt new file mode 100755 index 000000000..f717fee28 --- /dev/null +++ b/tests/hosterlinks.txt @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# Valid files, with md5 hash +[files] + +# Two variants of filename with special chars +räándöóm.bin 5dde9e312311d964572f5a33a0992a2c +random (机会,ឱកស,ランダム,隨機,лчаный,cơhội,შანსი,కంħن↓∂ƒ_স€קสม®¢äöář_æžĐşiồй_ныứ&+).bin 5dde9e312311d964572f5a33a0992a2c +# One host changed the name to this, with one little difference +random (机会,ឱកស,ランダム,隨機,лчаный,cơhội,შანსი,కంħن↓∂ƒ_স€קสม®¢äöář_æžđşiồй_ныứ&+).bin 5dde9e312311d964572f5a33a0992a2c + +random100.bin f346c3ea47d8bfce3a12033129dec8ff + +[online] + +http://download.pyload.org/random%20(%e6%9c%ba%e4%bc%9a%2c%e1%9e%b1%e1%9e%80%e1%9e%9f%2c%e3%83%a9%e3%83%b3%e3%83%80%e3%83%a0%2c%e9%9a%a8%e6%a9%9f%2c%d0%bb%d1%87%d0%b0%d0%bd%d1%8b%d0%b9%2cc%c6%a1h%e1%bb%99i%2c%e1%83%a8%e1%83%90%e1%83%9c%e1%83%a1%e1%83%98%2c%e0%b0%95%e0%b0%82%c4%a7%d9%86%e2%86%93%e2%88%82%c6%92_%e0%a6%b8%e2%82%ac%d7%a7%e0%b8%aa%e0%b8%a1%c2%ae%c2%a2%c3%a4%c3%b6%c3%a1%c5%99_%c3%a6%c5%be%c4%90%c5%9fi%e1%bb%93%d0%b9_%d0%bd%d1%8b%e1%bb%a9%26%2b).bin + +http://www.putlocker.com/file/06DB5A3B9CBAD597 +http://www59.zippyshare.com/v/56973477/file.html +http://www.share-online.biz/dl/IAOREQPMWG +http://hbcyum.1fichier.com/ +http://bitshare.com/?f=5a1m0nia +http://uploaded.net/file/n4fhw1ol + +[offline]
\ No newline at end of file diff --git a/tests/manager/__init__.py b/tests/manager/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/manager/__init__.py diff --git a/tests/manager/test_accountManager.py b/tests/manager/test_accountManager.py new file mode 100644 index 000000000..1b328f892 --- /dev/null +++ b/tests/manager/test_accountManager.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +from unittest import TestCase + +from tests.helper.Stubs import Core, adminUser, normalUser + +from pyload.database import DatabaseBackend +from pyload.AccountManager import AccountManager + + +class TestAccountManager(TestCase): + @classmethod + def setUpClass(cls): + cls.core = Core() + cls.db = DatabaseBackend(cls.core) + cls.core.db = cls.db + cls.db.setup() + + @classmethod + def tearDownClass(cls): + cls.db.shutdown() + + def setUp(self): + self.db.purgeAccounts() + self.manager = AccountManager(self.core) + + def test_access(self): + account = self.manager.updateAccount("Http", "User", "somepw", adminUser) + + assert account is self.manager.updateAccount("Http", "User", "newpw", adminUser) + self.assertEqual(account.password, "newpw") + + assert self.manager.getAccount("Http", "User") is account + assert self.manager.getAccount("Http", "User", normalUser) is None + + def test_config(self): + account = self.manager.updateAccount("Http", "User", "somepw", adminUser) + info = account.toInfoData() + + self.assertEqual(info.config[0].name, "domain") + self.assertEqual(info.config[0].value, "") + self.assertEqual(account.getConfig("domain"), "") + + account.setConfig("domain", "df") + + info = account.toInfoData() + self.assertEqual(info.config[0].value, "df") + + info.config[0].value = "new" + + account.updateConfig(info.config) + self.assertEqual(account.getConfig("domain"), "new") + + + def test_shared(self): + account = self.manager.updateAccount("Http", "User", "somepw", adminUser) + + assert self.manager.selectAccount("Http", adminUser) is account + assert account.loginname == "User" + + assert self.manager.selectAccount("Something", adminUser) is None + assert self.manager.selectAccount("Http", normalUser) is None + + account.shared = True + + assert self.manager.selectAccount("Http", normalUser) is account + assert self.manager.selectAccount("sdf", normalUser) is None + + + diff --git a/tests/manager/test_configManager.py b/tests/manager/test_configManager.py new file mode 100644 index 000000000..ca572bf20 --- /dev/null +++ b/tests/manager/test_configManager.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +from unittest import TestCase +from collections import defaultdict + +from nose.tools import raises + +from tests.helper.Stubs import Core, adminUser, normalUser + +from pyload.Api import InvalidConfigSection +from pyload.database import DatabaseBackend +from pyload.config.ConfigParser import ConfigParser +from pyload.config.ConfigManager import ConfigManager +from pyload.utils import primary_uid + +adminUser = primary_uid(adminUser) +normalUser = primary_uid(normalUser) + +class TestConfigManager(TestCase): + + @classmethod + def setUpClass(cls): + cls.core = Core() + cls.db = DatabaseBackend(cls.core) + cls.core.db = cls.db + cls.db.manager = cls.core + cls.db.manager.statusMsg = defaultdict(lambda: "statusmsg") + cls.parser = ConfigParser() + cls.config = ConfigManager(cls.core, cls.parser) + cls.db.setup() + + def setUp(self): + self.db.clearAllConfigs() + # used by some tests, needs to be deleted + self.config.delete("plugin", adminUser) + + + def addConfig(self): + self.config.addConfigSection("plugin", "Name", "desc", "something", + [("value", "str", "label", "default")]) + + 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" + assert self.db.loadConfigsForUser(0)["plugin"] == "some value" + + self.db.deleteConfig("plugin", 0) + + assert 0 not in self.db.loadAllConfigs() + assert "plugin" not in self.db.loadConfigsForUser(0) + + self.db.deleteConfig("plugin") + + assert not self.db.loadAllConfigs() + assert self.db.loadConfig("plugin") == "" + + def test_parser(self): + assert self.config.get("general", "language") + self.config["general"]["language"] = "de" + assert self.config["general"]["language"] == "de" + assert self.config.get("general", "language", adminUser) == "de" + + def test_user(self): + self.addConfig() + + assert self.config["plugin"]["value"] == "default" + assert self.config.get("plugin", "value", adminUser) == "default" + assert self.config.get("plugin", "value", normalUser) == "default" + + assert self.config.set("plugin", "value", False, user=normalUser) + assert self.config.get("plugin", "value", normalUser) is False + assert self.config["plugin"]["value"] == "default" + + assert self.config.set("plugin", "value", True, user=adminUser) + assert self.config.get("plugin", "value", adminUser) is True + assert self.config["plugin"]["value"] is True + assert self.config.get("plugin", "value", normalUser) is False + + self.config.delete("plugin", normalUser) + assert self.config.get("plugin", "value", normalUser) == "default" + + self.config.delete("plugin") + assert self.config.get("plugin", "value", adminUser) == "default" + assert self.config["plugin"]["value"] == "default" + + # should not trigger something + self.config.delete("foo") + + def test_sections(self): + self.addConfig() + + i = 0 + # there should be only one section, with no values + for name, config, values in self.config.iterSections(adminUser): + assert name == "plugin" + assert values == {} + i +=1 + assert i == 1 + + assert self.config.set("plugin", "value", True, user=adminUser) + + i = 0 + # now we assert the correct values + for name, config, values in self.config.iterSections(adminUser): + assert name == "plugin" + assert values == {"value":True} + i +=1 + assert i == 1 + + def test_get_section(self): + self.addConfig() + self.assertEqual(self.config.getSection("plugin")[0].label, "Name") + + # TODO: more save tests are needed + def test_saveValues(self): + self.addConfig() + self.config.saveValues(adminUser, "plugin") + + @raises(InvalidConfigSection) + def test_restricted_access(self): + self.config.get("general", "language", normalUser) + + @raises(InvalidConfigSection) + def test_error(self): + self.config.get("foo", "bar") + + @raises(InvalidConfigSection) + def test_error_set(self): + self.config.set("foo", "bar", True)
\ No newline at end of file diff --git a/tests/manager/test_filemanager.py b/tests/manager/test_filemanager.py new file mode 100644 index 000000000..a7507cada --- /dev/null +++ b/tests/manager/test_filemanager.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +from random import choice + +from tests.helper.Stubs import Core +from tests.helper.BenchmarkTest import BenchmarkTest + +from pyload.database import DatabaseBackend +# disable asyncronous queries +DatabaseBackend.async = DatabaseBackend.queue + +from pyload.Api import DownloadState +from pyload.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.getQueueStats()[0] + 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, None) + + def test_get_filtered(self): + all = self.m.getTree(-1, True, None) + finished = self.m.getTree(-1, True, DownloadState.Finished) + unfinished = self.m.getTree(-1, True, DownloadState.Unfinished) + + assert len(finished.files) + len(unfinished.files) == len(all.files) == self.m.db.filecount() + + + def test_get_files_root(self): + view = self.m.getTree(-1, True, None) + + 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, None) + 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, None) + 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, None) + 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, None) + + 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, None) + 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, None) + 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, None) + 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/manager/test_interactionManager.py b/tests/manager/test_interactionManager.py new file mode 100644 index 000000000..5ff74c0f0 --- /dev/null +++ b/tests/manager/test_interactionManager.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from unittest import TestCase + +from tests.helper.Stubs import Core + +from pyload.Api import InputType, Interaction +from pyload.interaction.InteractionManager import InteractionManager + + +class TestInteractionManager(TestCase): + ADMIN = None + USER = 1 + + def assertEmpty(self, list1): + return self.assertListEqual(list1, []) + + @classmethod + def setUpClass(cls): + cls.core = Core() + + def setUp(self): + self.im = InteractionManager(self.core) + + self.assertFalse(self.im.isClientConnected(self.ADMIN)) + self.assertFalse(self.im.isTaskWaiting(self.ADMIN)) + self.assertEmpty(self.im.getTasks(self.ADMIN)) + + def test_notifications(self): + n = self.im.createNotification("test", "notify") + + self.assertTrue(self.im.isTaskWaiting(self.ADMIN)) + self.assertListEqual(self.im.getTasks(self.ADMIN), [n]) + + n.seen = True + self.assertFalse(self.im.isTaskWaiting(self.ADMIN)) + + for i in range(10): + self.im.createNotification("title", "test") + + self.assertEqual(len(self.im.getTasks(self.ADMIN)), 11) + self.assertFalse(self.im.getTasks(self.USER)) + self.assertFalse(self.im.getTasks(self.ADMIN, Interaction.Query)) + + + def test_captcha(self): + t = self.im.createCaptchaTask("1", "png", "", owner=self.ADMIN) + + self.assertEqual(t.type, Interaction.Captcha) + self.assertListEqual(self.im.getTasks(self.ADMIN), [t]) + self.assertEmpty(self.im.getTasks(self.USER)) + t.setShared() + self.assertListEqual(self.im.getTasks(self.USER), [t]) + + t2 = self.im.createCaptchaTask("2", "png", "", owner=self.USER) + self.assertTrue(self.im.isTaskWaiting(self.USER)) + self.assertEmpty(self.im.getTasks(self.USER, Interaction.Query)) + self.im.removeTask(t) + + self.assertListEqual(self.im.getTasks(self.ADMIN), [t2]) + self.assertIs(self.im.getTaskByID(t2.iid), t2) + + + def test_query(self): + t = self.im.createQueryTask(InputType.Text, "text", owner=self.ADMIN) + + self.assertEqual(t.description, "text") + self.assertListEqual(self.im.getTasks(self.ADMIN, Interaction.Query), [t]) + self.assertEmpty(self.im.getTasks(Interaction.Captcha)) + + + def test_clients(self): + self.im.getTasks(self.ADMIN, Interaction.Captcha) + + self.assertTrue(self.im.isClientConnected(self.ADMIN)) + self.assertFalse(self.im.isClientConnected(self.USER)) + + + def test_users(self): + t = self.im.createCaptchaTask("1", "png", "", owner=self.USER) + self.assertListEqual(self.im.getTasks(self.ADMIN), [t]) diff --git a/tests/nosetests.sh b/tests/nosetests.sh new file mode 100755 index 000000000..5b277ecb8 --- /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=pyload --cover-erase --process-timeout=60 +coverage xml diff --git a/tests/other/__init__.py b/tests/other/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/other/__init__.py diff --git a/tests/other/test_configparser.py b/tests/other/test_configparser.py new file mode 100644 index 000000000..0efd41aee --- /dev/null +++ b/tests/other/test_configparser.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from nose.tools import raises + +from tests.helper.Stubs import Core + +from pyload.config.ConfigParser import ConfigParser + +class TestConfigParser(): + + @classmethod + def setUpClass(cls): + # Only needed to get imports right + cls.core = Core() + cls.config = ConfigParser() + + def test_dict(self): + + assert self.config["general"]["language"] + self.config["general"]["language"] = "de" + assert self.config["general"]["language"] == "de" + + def test_contains(self): + + assert "general" in self.config + assert "foobaar" not in self.config + + def test_iter(self): + for section, config, values in self.config.iterSections(): + assert isinstance(section, basestring) + assert isinstance(config.config, dict) + assert isinstance(values, dict) + + def test_get(self): + assert self.config.getSection("general")[0].config + + @raises(KeyError) + def test_invalid_config(self): + print self.config["invalid"]["config"] + + @raises(KeyError) + def test_invalid_section(self): + print self.config["general"]["invalid"]
\ No newline at end of file diff --git a/tests/other/test_curlDownload.py b/tests/other/test_curlDownload.py new file mode 100644 index 000000000..17af1cdd4 --- /dev/null +++ b/tests/other/test_curlDownload.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +from os import stat + +from unittest import TestCase + +from tests.helper.Stubs import Core +from pyload.network.Bucket import Bucket +from pyload.plugins.network.CurlRequest import CurlRequest +from pyload.plugins.network.CurlDownload import CurlDownload + +class TestCurlRequest(TestCase): + + cookieURL = "http://forum.pyload.org" + + def setUp(self): + self.dl = CurlDownload(Bucket()) + + def tearDown(self): + self.dl.close() + + def test_download(self): + + assert self.dl.context is not None + + self.dl.download("http://pyload.org/lib/tpl/pyload/images/pyload-logo-edited3.5-new-font-small.png", "/tmp/random.bin") + + print self.dl.size, self.dl.arrived + assert self.dl.size == self.dl.arrived > 0 + assert stat("/tmp/random.bin").st_size == self.dl.size + + def test_cookies(self): + + req = CurlRequest({}) + req.load(self.cookieURL) + + assert len(req.cj) > 0 + + dl = CurlDownload(Bucket(), req) + + assert req.context is dl.context is not None + + dl.download(self.cookieURL + "/cookies.php", "cookies.txt") + cookies = open("cookies.txt", "rb").read().splitlines() + + self.assertEqual(len(cookies), len(dl.context)) + for c in cookies: + k, v = c.strip().split(":") + self.assertIn(k, req.cj) + + + def test_attributes(self): + assert self.dl.size == 0 + assert self.dl.speed == 0 + assert self.dl.arrived == 0
\ No newline at end of file diff --git a/tests/other/test_curlRequest.py b/tests/other/test_curlRequest.py new file mode 100644 index 000000000..6bd4a2772 --- /dev/null +++ b/tests/other/test_curlRequest.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from tests.helper.Stubs import Core +from pyload.plugins.network.CurlRequest import CurlRequest + +from unittest import TestCase + + +class TestCurlRequest(TestCase): + # This page provides a test which prints all set cookies + cookieURL = "http://forum.pyload.org" + + def setUp(self): + self.req = CurlRequest({}) + + def tearDown(self): + self.req.close() + + def test_load(self): + self.req.load("http://pyload.org") + + def test_cookies(self): + self.req.load(self.cookieURL, cookies=False) + assert len(self.req.cj) == 0 + + self.req.load(self.cookieURL) + assert len(self.req.cj) > 1 + + cookies = dict([c.strip().split(":") for c in self.req.load(self.cookieURL + "/cookies.php").splitlines()]) + for k, v in cookies.iteritems(): + self.assertIn(k, self.req.cj) + self.assertEqual(v, self.req.cj[k].value) + + for c in self.req.cj: + self.assertIn(c, cookies) + + cookies = self.req.load(self.cookieURL + "/cookies.php", cookies=False) + self.assertEqual(cookies, "") + + + def test_auth(self): + pass
\ No newline at end of file diff --git a/tests/other/test_filedatabase.py b/tests/other/test_filedatabase.py new file mode 100644 index 000000000..f2a60e997 --- /dev/null +++ b/tests/other/test_filedatabase.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +from tests.helper.Stubs import Core +from tests.helper.BenchmarkTest import BenchmarkTest + +from pyload.Api import DownloadState, PackageInfo, FileInfo +from pyload.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.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(state=DownloadState.Unfinished) + 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 + names = self.db.getMatchingFilenames("1") + for name in names: + assert "1" in 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.downloadstats() == (0,0) + assert self.db.queuestats() == (0,0) + assert self.db.processcount() == 0 + + def test_update(self): + p1 = self.db.addPackage("name", "folder", 0, "password", "site", "comment", self.pstatus, 0) + pack = self.db.getPackageInfo(p1) + assert isinstance(pack, PackageInfo) + + pack.folder = "new folder" + pack.comment = "lol" + pack.tags.append("video") + + self.db.updatePackage(pack) + + pack = self.db.getPackageInfo(p1) + assert pack.folder == "new folder" + assert pack.comment == "lol" + assert "video" in pack.tags + + def assert_file(self, f): + try: + assert f is not None + assert isinstance(f, FileInfo) + 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 + assert isinstance(p, PackageInfo) + 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 + assert isinstance(p.tags, list) + assert p.shared in (0, 1) + 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/other/test_requestFactory.py b/tests/other/test_requestFactory.py new file mode 100644 index 000000000..751e7f03b --- /dev/null +++ b/tests/other/test_requestFactory.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from tests.helper.Stubs import Core + +from pyload.plugins.network.CurlRequest import CurlRequest +from pyload.network.RequestFactory import RequestFactory + +class TestRequestFactory: + + @classmethod + def setUpClass(cls): + cls.req = RequestFactory(Core()) + + def test_get_request(self): + req = self.req.getRequest() + + new_req = self.req.getRequest(req.getContext()) + assert new_req.getContext() == req.getContext() + + cj = CurlRequest.CONTEXT_CLASS() + assert self.req.getRequest(cj).context is cj + + def test_get_request_class(self): + + self.req.getRequest(None, CurlRequest) + + def test_get_download(self): + dl = self.req.getDownloadRequest() + dl.close() + + # with given request + req = self.req.getRequest() + dl = self.req.getDownloadRequest(req) + + assert req.context is dl.context + assert req.options is dl.options + + dl.close()
\ No newline at end of file diff --git a/tests/other/test_syntax.py b/tests/other/test_syntax.py new file mode 100644 index 000000000..396fc8f4b --- /dev/null +++ b/tests/other/test_syntax.py @@ -0,0 +1,40 @@ +# -*- 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 tests.helper import Stubs + +class TestSyntax(TestCase): + pass + + +for path, dirs, files in walk(join(PATH, "pyload")): + + 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 + + # 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 diff --git a/tests/plugin_tests.sh b/tests/plugin_tests.sh new file mode 100755 index 000000000..789dddd91 --- /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=pyload.plugins --with-id +coverage xml diff --git a/tests/quit_pyload.sh b/tests/quit_pyload.sh new file mode 100755 index 000000000..3bfb3e13a --- /dev/null +++ b/tests/quit_pyload.sh @@ -0,0 +1,7 @@ +#!/bin/bash +PYTHON=python +which python2 > /dev/null && PYTHON=python2 +$PYTHON pyload.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..9ffc04b4f --- /dev/null +++ b/tests/run_pyload.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +PIDFILE="tests/pyload.pid" +if [ -f "$PIDFILE" ] +then + kill -9 $(<"$PIDFILE") +fi + +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 pyload.py -d --configdir=tests/config > pyload.out 2> pyload.err & + +for i in {1..30}; do + grep "pyLoad is up and running" pyload.out > /dev/null && echo "pyLoad started" && break + sleep 1 +done + +echo "pyLoad start script finished" + diff --git a/tests/test_api.py b/tests/test_api.py deleted file mode 100644 index f8901f731..000000000 --- a/tests/test_api.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -from module.common import APIExerciser -from nose.tools import nottest - - -class TestApi: - - def __init__(self): - self.api = APIExerciser.APIExerciser(None, True, "TestUser", "pwhere") - - def test_login(self): - assert self.api.api.login("crapp", "wrong pw") is False - - #takes really long, only test when needed - @nottest - def test_random(self): - - for i in range(0, 100): - self.api.testAPI() diff --git a/tests/test_json.py b/tests/test_json.py deleted file mode 100644 index ff56e8f5a..000000000 --- a/tests/test_json.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- - -from urllib import urlencode -from urllib2 import urlopen, HTTPError -from json import loads - -from logging import log - -url = "http://localhost:8001/api/%s" - -class TestJson: - - def call(self, name, post=None): - if not post: post = {} - post["session"] = self.key - u = urlopen(url % name, data=urlencode(post)) - return loads(u.read()) - - 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 - - def test_access(self): - try: - urlopen(url % "getServerVersion") - except HTTPError, e: - assert e.code == 403 - else: - assert False - - def test_status(self): - ret = self.call("statusServer") - log(1, str(ret)) - assert "pause" in ret - assert "queue" in ret - - def test_unknown_method(self): - try: - self.call("notExisting") - except HTTPError, e: - assert e.code == 404 - else: - assert False
\ No newline at end of file |