From 1a90d5024f2a11426246a4ef0f70f086bdf7a8bd Mon Sep 17 00:00:00 2001 From: "Moritz Schappler (MSPC2011)" Date: Sun, 26 Nov 2017 16:51:46 +0100 Subject: [PATCH 1/2] [service.watchedlist] 1.2.1 --- service.watchedlist/.gitignore | 3 + service.watchedlist/LICENSE.txt | 674 +++++++ service.watchedlist/addon.xml | 39 + service.watchedlist/changelog.txt | 79 + service.watchedlist/default.py | 29 + service.watchedlist/fanart.jpg | Bin 0 -> 147130 bytes service.watchedlist/icon.png | Bin 0 -> 22921 bytes service.watchedlist/lib/__init__.py | 1 + .../lib/watchedlist/__init__.py | 1 + service.watchedlist/lib/watchedlist/api.py | 105 + service.watchedlist/lib/watchedlist/utils.py | 240 +++ .../lib/watchedlist/watchedlist.py | 1695 +++++++++++++++++ service.watchedlist/manual.py | 30 + service.watchedlist/resources/__init__.py | 1 + .../resources/language/English/strings.po | 416 ++++ .../resources/language/German/strings.po | 386 ++++ .../resources/language/Hebrew/strings.po | 410 ++++ service.watchedlist/resources/settings.xml | 37 + 18 files changed, 4146 insertions(+) create mode 100644 service.watchedlist/.gitignore create mode 100644 service.watchedlist/LICENSE.txt create mode 100644 service.watchedlist/addon.xml create mode 100644 service.watchedlist/changelog.txt create mode 100644 service.watchedlist/default.py create mode 100644 service.watchedlist/fanart.jpg create mode 100644 service.watchedlist/icon.png create mode 100644 service.watchedlist/lib/__init__.py create mode 100644 service.watchedlist/lib/watchedlist/__init__.py create mode 100644 service.watchedlist/lib/watchedlist/api.py create mode 100644 service.watchedlist/lib/watchedlist/utils.py create mode 100644 service.watchedlist/lib/watchedlist/watchedlist.py create mode 100644 service.watchedlist/manual.py create mode 100644 service.watchedlist/resources/__init__.py create mode 100644 service.watchedlist/resources/language/English/strings.po create mode 100644 service.watchedlist/resources/language/German/strings.po create mode 100644 service.watchedlist/resources/language/Hebrew/strings.po create mode 100644 service.watchedlist/resources/settings.xml diff --git a/service.watchedlist/.gitignore b/service.watchedlist/.gitignore new file mode 100644 index 0000000000..3677a81970 --- /dev/null +++ b/service.watchedlist/.gitignore @@ -0,0 +1,3 @@ +*.pyo +*.pyc +*~ diff --git a/service.watchedlist/LICENSE.txt b/service.watchedlist/LICENSE.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/service.watchedlist/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/service.watchedlist/addon.xml b/service.watchedlist/addon.xml new file mode 100644 index 0000000000..3bf6ee26e3 --- /dev/null +++ b/service.watchedlist/addon.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + executable + + + + + Save watched Movies and TV Episodes independent from filenames + Gesehen-Status von Filmen und Serien-Episoden unabhängig von Dateinamen speichern + + Export: Searches the Kodi-Database for watched files. Determine imdb-id and thetvdb-id to identify movies and TV-episodes. Then save the list to a new independent table. + Import: Set the watched state for each video file in Kodi. + Automatic background process without user interaction. + + Export: Durchsucht die Kodi-Datenbank nach gesehenen Filmen und ordnet den Dateien die eindeutigen Kennnummern von imdb und TheTVDB zu. Gesehene Filme und Episoden werden jeweils in einer eigenen Datenbank gespeichert. + Import: Der Gesehen-Status von Medien in Kodi wird aus der Gesehenen-Tabelle geholt. + Die Synchronisierung geschieht automatisch im Hintergrund ohne Benutzereingriff. + This script does not regard the file format. A Movie watched in DVD quality will also set the 1080p version as watched. + Dieses Script ignoriert das Dateiformat. Ein gesehener Film in DVD-Qualität wird auch in der BlueRay-Version als gesehen gekennzeichnet. + all + GNU GENERAL PUBLIC LICENSE. Version 2, June 1991 + http://forum.kodi.tv/showthread.php?tid=164652 + http://kodi.wiki/view/Add-on:WatchedList + + https://github.com/SchapplM/xbmc-addon-service-watchedlist + + diff --git a/service.watchedlist/changelog.txt b/service.watchedlist/changelog.txt new file mode 100644 index 0000000000..4482a9fbfc --- /dev/null +++ b/service.watchedlist/changelog.txt @@ -0,0 +1,79 @@ +version 1.2.1 (26.11.2017) +* Fixed Dropbox API: Change from v1 to v2 + +version 1.2 (21.05.2017) +* Added API and python module to use this addon in other addons +* minor fixes + +version 1.1.4 (28.04.2017) + * Fixed error in English language file + +version 1.1.3 (16.04.2017) + * Added verbosity option for graphical user notifications + * Added limitation of number of backup files + +version 1.1.2 (12.06.2016) + * Added fix to avoid infinite waiting if startup delay time is 0 + +version 1.1.1 (29.05.2016) + * Added fix for non-working python dropbox import + +version 1.1.0 (02.04.2016) + * Added dropbox synchronization for the database + +version 1.0.5 (24.12.2015) + * Enabled IPv6 adresses for hostnames + +version 1.0.4 (16.05.2015) + * Fixed mysql timestamp bug + +version 1.0.3 (11.01.2015) + * Fixed wrong implementation of startup delay + * Fixed missing startup execution if autostart mode is set to periodic + +version 1.0.2 (10.01.2015) + * Fixed support for Database file got lost in version 1.0.0 + +version 1.0.1 (13.12.2014) + * Fixed unwanted waiting time in autostart + +version 1.0.0 (01.09.2014) + * Added mySQL Database support for the WatchedList Database + +version 0.9.8 (31.08.2014) + * Fix for Unicode Path problems + * Fix for SQLite Error handling + +version 0.9.7 (14.06.2014) + * Added custom sleep function to handle requested shutdown in Gotham + +version 0.9.6 (23.09.2013) + * Fixed tvshow id with imdb-scraper + +version 0.9.5 (18.09.2013) + * Fixed bug in watching user actions + +version 0.9.4 (09.09.2013) + * Fixed bug in watching user actions + +version 0.9.3 (04.09.2013) + * Fixed minor bugs + +version 0.9.2 (01.08.2013) + * Fixed minor bugs + +version 0.9.1 (24.06.2013) + * Fixed: Network share problems in Linux + +version 0.9.0 (09.06.2013) + * Added: Check watched-states permanently and enable to set media as unwatched + +version 0.8.1 (22.05.2013) + * Fixed: Duplicate movies and tv episodes get marked as watched + +version 0.8.0 (16.05.2013) + * Fixed: lastplayed-information is now also stored in the database (although it is sometimes missing in XBMC) + * Added: Exceptions are catched with buggalo and bug reports are sent + +version 0.7.0 (11.05.2013) + * Initial release diff --git a/service.watchedlist/default.py b/service.watchedlist/default.py new file mode 100644 index 0000000000..f8188fe560 --- /dev/null +++ b/service.watchedlist/default.py @@ -0,0 +1,29 @@ +""" +This file is entry point for automatic start via Kodi +""" + +import lib.watchedlist.utils as utils +from lib.watchedlist.watchedlist import WatchedList +import xbmc + +__remotedebug__ = False +# Append pydev remote debugger +if __remotedebug__: + utils.log("Initialize remote debugging.") + # Make pydev debugger works for auto reload. + try: + import pydevd + pydevd.settrace('localhost', port=60678, stdoutToServer=True, stderrToServer=True) + except ImportError: + sys.stderr.write("Error: " + + "You must add org.python.pydev.debug.pysrc to your PYTHONPATH.") + utils.showNotification('WatchedList Error', 'remote debug could not be imported.') + sys.exit(1) + except: + utils.showNotification('WatchedList Error', 'remote debug in pydev is activated, but remote server not responding.') + sys.exit(1) + +# Run the program +xbmc.sleep(1500) # wait 1.5 seconds to prevent import-errors +utils.log("WatchedList Database Service starting...") +WatchedList().runProgram() diff --git a/service.watchedlist/fanart.jpg b/service.watchedlist/fanart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..55fbe5380188ffa0b57187ffd40f8c7c87fdc4e4 GIT binary patch literal 147130 zcmb4qWmFVS)b}nd-AhV?yTH;7l1mA@^wKFI-Cd$fm(;Q>-Q8V^(kiJE3J6H^2SGpt zB!t)JyyyAwethqIm@{+FnV$Q*ckZ40#rt&tHS#gyF#rez0D%7z;Qkvx4L}GYAS3`0 z5)u#+5fKuTQj?OBkdV?*Qjt?L(lIeH(lIbFvq3qSSst=7FmMWTKIGwr@xvZ)2#E^u zi9-2ceE-u3kcfzgl$ey3l$4f_nSq(_|C{ay0W`#Tp9lc>KyCmY4G^COcs~kY1px5y z@c(DH|4aW$AOIl|0r5XBk{STS0}fE*wAZ>0D%0A;`qeu(^IsHLfj7{!jrd6exT@j&HcP0_2woef&D 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

-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ԗ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

eXTq>h@)YKFV?Nf}HbGnO~U&uIFN8C+`} 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 z zQ+n1EF_8YT%e-^3YE*|=w}x@O=$2{&p$JFw#DLpW&gD;?mJ;9`tbt}G($wqM4Vu#aM^sWJ8s?o`X%`tKQu;Gk_nw?& z=U*Jk6tx5?vr=Y6m6-SKQYg8QGtS&yUWI;(w2t;~R!U{|%$|tEAfnC@10?-Pw2xkN zvn|wYHxSYzt{Yj88#;u;4&->WK6&AvsJ_G$6w(EO3tMU8x`}{~UAqs81A9OybP6ynZ6?ly=Ql3T1=vUwl-M6Fj4SvIV7tUx<3}ONOl9 zNgIMc`g(m8J3J_oEli6GAfK6qGgD6|Bh)NPYqfIjxQBE~^B9b`nX#i=LbNch>oO`i za#Rv;+YeqVAew^ycK(cJ;(Z!67G@4%zOnq|Ag)T+ZtpO23$GxmjK8#P1-eWORGEl& z30fSTnv=(-ItW{%t>=kz?uIxK+cxgXaCH-UH7C|z)`n4UVl-kfnumZbMBaBYbyK*} z@r4x7A@m$;5*p-NB3A11Y3a7p2W5@z1<)3|`@kX<0BM{3gJRL_<3$q2#_8#)L@&=e zAi-GlQjN?0uSe|jU_0X47KE}J6vC*C*kF-IMogsDrUp4eHebc!40O7@Hc)}wPx^S* z`(&ftTW#AyMw@efHahe~=Qn%(azWPJ!ZY#9T!Y?%^5LUO(HdCtB>G+~C_|}S=zQ^I zo!jl@jU-b;d^j4W(O!f{+iTqE>D`R;&$EXU40BC9F{N?T7ef`oII8ryQXxCfG z?9niylg$?yd9!BqGr)(mYIdFa$y(BcvDnY=!6(;yHw2S0jbXsMMBYqllMv9|UO&Ny zIeGriyt#4`w#9VbzN+%H&P}OPzh#U@Cw*x+p$QL78xnLKy(i{~8t=Nq7>G=J`hNjf z8dpAnqm+MxR4C%$? zWI6KNf1bP+7blA`6)2(wfQTUdo$=j8)}k}}kcXqe>_q`n4RN0{bHD{65D>#}l|1NzIo<|cDUi-7qh z&l(|4{O=FH;znU2Z5;e+d~=X$x*^Z#Hk^mK`KN4MQP;)f6skON(5yvy6KS#MUgvLA z5ck&9CObXo-pLnC+;Ceq1g~FN5Q%hSG|QvU4kX+q7=P#GxW!5|IVBd0>FciVlip3J zeGFRq7qIszNPbL5+&WMC8Mt{9Sa)Yfz-zICv!3 zk<~L00MB0r@LUy_zZ4j{$@kERYrONcHi^6l+TGMF$&}%4cbXUQxJQy{sM*>aBLtLs zDz3$)2z}Q_`!s9!buGs`e8w3>XvqDA-4lX6rE*o*AT!l}0Etb?VzGLuU%YEeAKxWA z1^;oKU|=e%tuziftweCXFFRT68GL5ZP*>0T`neUQxkg%N_mKVfR}KSSEq=~~vlsE@ zl+1`-(AhA^#*I}Z4#yp{0q+P|h0a|*%oN#gTIj02GkVQyU3FG@i&*&YaIHF;_|zrO zo%Pyd^tXLsH5%WAscCv{Qk9#aHY0)2PFTt%ooV%DQ>rbl+EqcxY<<3#pg~fL)W*{B zV`oe5Njv#s>6VMgsoBW7xO>P6;{0Mf%Q06^%9_N=n-|F%=(Pa0T$J+IM>#TjfZuCv zTsPAoH2B2dc$WTe_GPK%_GmbGDsElzC9^F;?smH@g5}J$ z=841s^38xVU(=V`w@c>w;<^4jXcJ-mcG6(hK-6;fF-zcszG=<_t2cA;iOyFJhs~Qx z3Ag6#&}|5r6O|7z#jXlw`s2SZqBd1}1bG|A@3c}3NGsXFOlQBKtMA7qj^t{!R6#}H zecpIPGm znnr>bn~C||$2*4kb~X9YIqgBGVj5*{-UJj9Y0KU=!@+#Vhn|g?@&?&9;<{E*642g# z-yM5bYs}X+me%F~ZO35MSPScs8ugF3O=?zND%ieCHp=;BIre%T@>+y%^#rJCvQ|VC z${UMH9ItXUUBsgVUHTFs2Ho0fwZa7G!KuWk!9N!v#C#F4u_+r%9OMZVX9l}KMPil52( z6CSu-Yf6MRRiSJQi%?{&Bp|?k7lkN&w$r|4qOhtXGv}x~8F9Fmrk%8svB&FS5pbuI z&9<4_2xO7+95wA8FPG8-Qxt5T|II+ZKgTTeHaAhliTvci?`)gy-pMb{C!gLNVag7U zaE7P4wYt;PcV_o6t?w!L&;_#KCUO4_&BBQL(<(3SSIyf~@d`cuyl*UiDzZ}ib3gro zv@4_fl1F?D3PQZrM2&NDve(2i6U*}?Y4fYFgO&9p_*dU`xp2+3q~Yk?tVM|V-!Mk9 zZZ3zZ|7DtRw*J%F-an~uI;)aIzcSr6I!iaBsbD)#Z@yB3>q@(`j%b)mv#V^wwenDN zX$@gd>8AwRRg2sU3XJ4uHbLULw+rL~qs3}PRefJ=5*}Ac{xZFV9O=TN26n_r;8UuNsgzSLZ6H~Im9*mWCa>VO_SnMX!S&iNG+oMlg8_)!}( zqL@(+p`i&uiuEt$Npd3`vm$;I=RP3)IZ;{{R;i+dp&pS2GRD3uf(Q@nfNqoG-I0|I z-QAefBn;qQA#sZf18eeXkuutA%t4Y&_l$WbMf^LA7P+JtK?0{5INu{jGx`=@D%pF` zUc_j>pMdD4c|FQupDh_)Py)Lap&+KKno>p})dFNSScd}_p#%`uDx}iyB`r{l=rr|P z0aw}UnzdXSZ&V{9R_o}AKNj-=`=|@1$%hz#$xJGs6iTrt@nThZ95Adyt!GOzW z@4h@S<>*IhEwESo;un>-d7@3i8WF?I7k2TuFWSWPi?O>tomWAe%O`WRx+&fuG#|wO z+frr!kki8N%Z=wpx!6YYAhR-qHEZGMOR7CFuzE-~6WTi;P(*8z`3i7Go;AbuRrMew;hK%O_b>KQP$O zcU9G7ZwpeX(_qjjYON1Ct@I8ekC<7jE617r8&D6}{bY?~QUX~T5wJw0YSkX!Z}xI+ z#T;j9e9G_!TEa|7gqd~stJy5r!|+9mW_T{@;p>a3Jt9lek21sA%JrOpMw-y(ahO#6PkQ^v@un% zNT+M!)llG%kSEn6**{LW8^v$W4~7)Xr@1z1WKsi&Gz)z6Q(*gNBHGuD0sV#83A-EU zC2NQ<=V#9`Yu==Ogm8So5NI{eca;G+GkmWvt5o}}0ake}MPNGlj1DIaiLfD6E0$s< zmm)7HrzQO66H!-nHq%q?t!akxlzDnpc~MSNA#@|(IRVkIMUazYP!JvctfrSZcz1y3 zO?*u!Es7WuW5IH6lE`IA0arO-I6V!#%6brOmK)>m6fvB6Q?$v?Lc51#m#-9}`6sO$ zs3^NDP8pFRl0hUL00z8SM|Z63bmnSS~Jdc7`pA^v%B>YPn0kd>ZOYI*26>&Be< zII{nNSs6&o3}QO$oerRPG1z-=HE^;|6z;0bAlT(ZF?Hd3(^1S)EioEK1f;hQ0jGKY z%P>a=<9Ja}Z@m{P%Sha`a>i6o6$OsV`KuckE*|9wV5$s`;Ak9to19+v{&5xc<63+R z#-Q@EMhYfV^@i9_R?4kp9Di!k5)3wqei^WbHa5w#mLp487TxKkOmjj{mJvF8)Fv3; zADr+Tvgb(xjtr~0lL)eh9>Ej{3`E-M8$))@sD*~BaE0X(?#b~l*Es{l_ zg1GM3fk?$<){65VGOtD5M^x0}CGvafYV8d0^&*U+>xo-bzutbx-FADiXDSd+m+%E< zj%gzEybtW8rY4=FnqmsJ{XR7x1@SDf{YAdypm@W}l1i>bjnQ0DQHiyF7Zg zcW4!=7lN%#MWjB~f$^L9F-TeXJgK-SUtuVq#SV+=q^{N}m&kt;eAON?~N78AO!+uT^CVr%Ynu!Jx}4FxSe`0ErTqka3@j8VOMu;>z!X zolr7Sz|kZZ5#3s>I6@tuA@i$vSiD?**DBOIQE2DH)7V%{`4o^QW_N!TE~r8556hrL zmEV<^@u-#w*{G~@-MR5MRpl9C(sX5n^B>)Hs)Jhj&W5PZw^hxVgIi5BPf&L@M&nyNznz#UTmw$JKOSyxo&Z%efe@&k9c zikqfMetfDXRK$bfvv4|(PTFjb8{X4bzp9BdtJ&p9r^TUhI#33 z4O6&%5`5iakR{5$--}|kgK8-*zZ|D;%BbC~7SE-Gr5}&_5TWU1S{luXTu)kiYn?43 z-S*{M7q0nsK@`24k;F)$H_%UyV!4~9hvN5@(w69+dE4J^#{4?3M^(;c;}9%9mZ#v& z{kz0}*J&SkOJ-&ZX-xT>!Dy%9drB6Sb=BAdArXMy&(OO!MDbt2hn}bxsOPp1?Kjlo za-ks?(ex$Xd|yJn3j@e|8@l z$b85)6S&^&Jt#<&i&d$~MbOu57PyMOUe7m?Z~ydRl^;FJ zy*eq%26K=;|BQNnD*ATfV83l-jD1^cP!E|CH*Q%NC|5dPNYg$Mww;XG8rP_-sd8r7 zLx`z!$J}Y^30_;swGnc+dm^rH9zUwLKi%8N@KmuUkX%I6Id=dhF~$? z^ZkobI8OBJm8>e-@{bqi19vwA{m=Z6rjXrpvRZB_$O%?8jnPn{E z|4i~Qq>D=-B)$4YYdpl``7lAdvUGj)OVst@Npd8v+7M`%D@n|_sd^v!l#&< zhOPC)#`0fuX?&M8$gwJUDzlbzL@BhhvwCEv-+HC!L7u(Cj)i*7e2BSM&_)T#FVl4l ziC;pQCN3vAU3gG14!+1;Z&MdFC3WgyhrN3zAI}ZXenygqEa1W9Wlh9?fbC2MDOtTAPOXGb^XXfI5Ssc32-ZZb)PvEe zj=vp-Rhd*%&V#hC2P;u6|4y=k0%+QZK>7*!WcAO;0%K%Jd{l!{v-=8_K-oXy>Y?Qmzzt73u@S{TGysLfoj!N^=i*w}iu^f;7udH4v`#lsl>ff91)@sT6`qcs_MGc+J5bK*BTz*fpX%># z5S6yn=p+E&zeS@Fl~-l!)spVlwzMMC$#P^P{uAO3t{peiP&xnXCN{BN-=0Xpse|%( zH&!RPf)tGVj*kobA>Xh(s3_eta#pXANLu zOQLP)3=Hj|Hly-B1}G>+A92EIj{qJ?><|J#LZ5T&<2pNpnjag`s8<#<#PTVVJh zGj3FUcIh$+it&jQblrQaaT%M7Yt*{MR85;rO!AM?MHzgvq0Q`o<}bW;l9qX!oV#O) zpfY%p2W#RD2>na2telHy{iVQ*nbsIx=UlZwa)T9Dv>a z?~ximm0-M!cd`3X7W1|-y;-aP*V`R6qj|W0YFe=nf5%&8!zym)TJ-}-&`JDX^m{`h zM5Ns8B^CgsNSmk%r+4>nC{(R0v*!ir1b;EthXMew)?yD8MTN8umOWm6>)FLc4sedJ zq>yKwz1M<>R(yDYu(NK#Jz@EtFnhf*#XwV8ao38fVoCJ<SBPHYNIC)lcN<1Bw5ZH> zFlHiQ==IU~-HuZ#lrnHc-GeKL+vlYbQ}_8pMs50sHq~P%6?HL;kcg=4fzWegzPIXxrEg^DiC-=++0?)W_%HD9RaDoWaHBg-arZp* z_h32yhr09bXY>90aO}Ne?;t_!Rch~%P$WiU)o5u@Tg@W&s?u1oXY8t3l-8z*QLA=K z(NbzuDIMQm{<|N;{S>a(>m1kldB2Z?CZ##As=I9oOxG1$6ocS%wI%)jT_k2Iw{7oY zSoOVG5kANR(O|C){ArS7YGcrFu7o%728W=Qu9smgw<3^0tdjvTyIid*qp#SB zJ#%=b*sN6}bVi#L3XVQ=yvnGmR86`^Q*wY`X5*p1Bbrt%yY2o*>_0#j+5Ua_cuh^T zW(pza4cf_U6k_0ei$f)>E_=`8{x@`0_x2mvn#t?Dex8|IK+p3rUQMGCMUHO``zQeC z(dR%U;lneZMf!E^TRRiD93DprTv)I*Ov_H(wf`{(dD&OtByHT;laOa|W$aA_T6gIC zX-jBVX$02GtRm^glW1IKz8{mUs0msZs2bPJ=b6|InGMWH6$I&abNouaqz}mU?rr^t z=)YKg^LKcW{=ISC6>FnOc|` z^qNbhh++O7st#!q+{!R*7w>JfcwAD}?GpybJ0=go>bs;wI=G`g^BWz~ffNW|5ipE_ z18Axml6L+Iv@r~W{>q$}Vt~UIwPi(FxY<9+#w7UKJn&EgUg#!5sidC|4{ zJMRVX_9MQr-_2HwzKhRJo;Cq6ih7=`3aehU2N2_S>P+^hk3WZv(cxNn!gGO&>}s@- zNnOb^EI=q(&0#+`B%OV6Q4=&+Ujsu*D8BKz4IzImK3@wlP#1rjMbZk5mVNmkx?!is zLE6UU98y{J*ph%F`6HZuXxUX1I8ZX{EfC}EbT{S0Oyh{ybX`L+eBZ1PhD`UhI3PLR zoh-<8(jV}W^k{x^(2ZA`Xy>T3#&cIpsAHXRSN&*9DqH#B3MiRbzRQPGY;;!!FJoyv zd(ICmiFJJ{3dYDMcx3eP11!nU`m84*4vv>6SELc=GvGUSzu1hI$1E~8@*Xqb%Anpq z*yjf}CopoMizkeW1d{{+Y`5>W=fcDqtt+T;IymEP3mOdiO&n~)v;u=y{+8pJ`6VI; zfaAqlGs5oQUCg(~nUOhsmtn#K!h@Qha>^6L)9Ze$u~qp=$3GIG^YkH>MsxI0Lq7p9 zlde9~%?Y>g#3FSA#wzDzg!KL3I?3qwi~Z=e#U1WL>=Qg`t}B)eATwjz{GqMvH+nZA zAEZ*AF;y3qGS>Q3&0VQea(L(foCh&@4gr_-M&9&M_L!=<-_jE^vCCJpADQsxfLeBo z?7zR@kiC=Lj+bVP8E+*M%_}Sc2GA;j6Q}DehBEJxr~3lB-R>v3uEuaTqs#>pKUlN# ziwxBwOU9b1Tr66`F8%|&TkQm1qmFI1h1u{&H;UR~wSGE*qve znH`ze(t6A|dG1Y|#>Ow-MOwoQHG}cu(|PX6bQGx|<&XCP&#DNl?f=~GZKD!RsCvz; zy?)RLcx3u1TZ^_Ux-E&rOiJ!5yHFh0KlmY_v<#Ie1;W$w(OF@Pvvd+Yq@O>c19dh} zI@lHy*~KivvR;xkVG|2Z_2W- z98(MhbM9U}h5bSO-{a3&VOc!~%QO$C)hFQY-X z8gEYToh`nW&9H3zb07LS`wyQYU-q`C9b-YzCG0|loWDd^HCL0}>|BE;@asH`o zIR)ouiPp@OkHxNZrHloSqVAh&cBe>68z?xSr!f3Kjm*`o?+nK3hQ=(D%x`4(gsI~h zoA)V{wU9nQ{cd4eH#URC_`x`3a+c0*X*d0Dxq-)^!%&VmUg!`~ef@-q!?pJ^!Sprq zGA=NC@1zgKQ=_Nx0kOl@p@w5N4$BS4suCp&QVu15LFCM&7}>-3Z5J~Uo;UINVs}LJ zBzJq<9A>neyeTdS(t2NZ+U_84xEE zB5xcqc5_F@Bl_iNqnGE`a~GsH@!IM_g1%dTT!?fUZ>8VuK=UJJu*=ehWMnrL#hHy5^)Wi5de+VZ0{~LjvD#C%dV3_7c&f zm~Z{<;4`thpt-K_l_!hBBR$KB0bjFYzVOA>+kcrsA!Ob-^vk~q_I-U%uU^Pnrl+(8-U^HLiq#bU8Za*%ZDHMO zta*IkjAu~k2)8&?$k?jkdnq(<@p1s_n3TI~! z+u%aE9<3YWJ0w$@=VFEU4?wYK_D0o3)ock*@8kMKcJ`t#jO!R&+@7#=+ett>*=_!e zOe$NiQ(R0pMlorhU@;u^NeM#Lb)UT^Q~5}EvMH+K7AWH-8bhX>^W!Qmx3`ygcNHuO zbQ^LqUePtM6S8;O+Ir9PO6c!>iiwYZKl690s_Ur2LoLC*gP?H1WmfSO%%fR`oN{KS zFfRc%Iyo1{Q&W^#C%Q)+hy1g3+~F$pvICKjSjX^`2nfUgj>(Vv+p2S%yy0bycri4BcNJ$R0vZR=@03Rnd*k*8XD>Y zTW-8uUWe0KE*8N2Kl#!uoZlpsLE-SUEV%V|>Wzr;sG@>4Bkcs&>sdupCnJ?)lRFZp z$FTnZDTPINpGjQ_nMb23EmVcBe`RJKr<0nK*~uav7SDIb5RCm^bjcq`R;lhdc8Y)c zy0U&2=<^@o3!8WNHx)PTDLx`iE&;4upa6V=wPe7SEtZ-&fBpIJQ$#`jmtIYi97pwU z6nrt8e^a`&tFCvXq1|*gYTTzuiRsDzUST?Nr&?&$D==n;p&Pd+h2}&q*�eiDy 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�JhBP1(8nwOBIW+VaO1! zGw^S_Gg`g@1O03#1KkhSTXwHa$RkG~UKayTLGb9g2daj+m?xA}ek%-^9B>GG`Dk5T zUA@srUT(M#U1hMWGGl`lfM5a87e_5*l)ycsfbW@e$Aey3`6rfu{{b$1fI}k$E-^K? z;0oK^f+pASV-is2v&a64iC*RI%fF*pfgVjy2-#|#(Ry*`Uv)$!I=atVN0{Nw&ERE{ zj5m0(qxd0_@tpSRV}(*T?1;xhm-a^o)@2=;{+H^QE>7~y;BwF#k+VJGteEedD-vYs zoi}(xNps>@zyTL_I};!&?XIS6e4T8=fhAL3Si2XlfkGPYuJn#o0istprX97&v&trM z$el1PifJ$CjFp=KWlPDfAt5DK5ba{gFIm<6xM;a*)~m=aHKQAzQNw$OdqpN>0=&%csgd`vV?DK{dA-mPQx^JX}0G~=j}n`)fUt- zsgMk9$W*vHj1!<>_RuFF>@*XtB_#S+Ib0&a_6J+6OPl1g_`}I&`*0_QE>&Y%bThm) ze`%Z&!prLVu`Lk}f~Whtzy!uf!0M8QN6K9>TX^nt8pUn@4TKx)a0VM zdqql-hZnKw-OVZup4Z(-=$POOh08#DaI2zW;PC%29r|`Eq*);{iQ)D}-K{e~Ohpr{ zPrWl$S@0YjL1yabK|$Z&^6rb&8Y$iWXHu#v4ztExDq^1Nzub}tPR0I(BMSb6Z5?rl zQR)PBKbHp9d)R@IqwL;mSDkK~n;;FC1JLNPlnWWUDqqOP^>OG=8g8S3`{=;!!M;ex z8&yUQS+x!~K)tvCe-;d--glYO|IBtfDN1d4!2`mJE>|15+zxYdq7)13+{>DH@-{=W zR&_D*p_tQ(OKU)MtxjJx;;t%3!T$?wxLIwz>k`v733R9CYw1@^KnVcd%=V5B84A8H zzInvV^|E*MBkqnob5I4etY(Y=-zETwGO zXiCXxXx7LA;Om(R1j)8&$nh+q1urDrE1~TJ~SxX_R@y14j|3*2q zz$3mav4rn>-ebw(j~az`pqibd@98eh?=n|d%yxs>`fE68ABCQWLE_z%Q4H_jxuDXa zuToP9FY&LRTNsLBqZJ$(MFh|9+gICu6dFM zPRjLq?+2vP$6BwdGaL+s)U@*@hvLSrl;VrSmo5^tD>E3D0zv|4Tz#D9P5FZzE)(-A zWGi(nt9v7uk@zN;7TG^QboMUgg~GZ{yuCfMFzNfr---N9k)7-tXuSD}wka;^zE&1^ zL&=ADeFV;@+x^pCOVHAu(~oZ7R%>BEnO~x6MTWki2mZ>n3{ulk~JR&d6UV;ukd%0OcJ)5Wr=ah--T zz|+cGk&%#M%AC%04B&+~Y+B&0vtsG`7E_Fw^d_VM+NYqb_Ub==C zVuM@hGyn&!)3)VC(RAocROqN<>j_i5E#6(d)A#%zARy_GSu6$ATVbEO);2>H6`1kI zSSF}_n0JIT1{ok6l0xVzasTnsRgLeg(OE5@;9JKL6eeYa)ihQ+wVn_&-|(W>VuiYB zX#3>*Bc|>BOVUbQMOEMNx^IG;DS;_$z6_o9WLU`G_`E zCDuz|{8O9f0WoxD5&lwf!gLcGuk6Vpu=}oAi_2fuMCk~^#gSUn#~;rkXOVG+Q660n zdquibnQF7uqYWnOYAsXawfLo0mctuGILs>%1_Yi8rJr4RCr}tkIPAAb!-Z(em-RLq zGpj|Wv9mb)z1H;_6M1HS(EWT7Mz0ZHoc|fgiSwqU@t#`KhIUm(`o?gkpf*zdXSdZA zR`2x`hjImjwt|<FM^y3;6AlZmy)U0<4yw(I(DfJ5U`5@Re?!lQ@0>HLz5n#6-C;R!nO~qm zK2fLkv2d1#P#%EWlS?F13n#ebP<4i1A)skWyuYkcUsD`sG*Me$fTzS{m{e4+s;%UZ z(>HxjZq^^off9=3igu|#@g__^VDHRlsw5FUxGH@IF-m*h*_i_-%4BGI|9+kET|Ryx z-W6$rLZCiuBQwgfgqjrutv@u<$m%m#BOdKBi$Z!7)ei21`6-A7r}L6}TPcL-`0X+N zXqEHm)T}?6pPBC$s>Q_F0-)KuWQ3s8)T1SqBr?4$3j)un7OAbk|G*pK38S~Xska%ARZ!w^$FICRtorU00o zIBi4(h(*sGr)o(@bgA1`CTp2M9cRWcadbcF@4vfB>yW+~|4zt4&DW{2WgcHLfJ9>OC(DC zC@{zfGdAII5gh@{$YT9l_L988qcmtiW_LP#k+ZP;x2!0rOHwBzq6Ph&uwez-$dh_P zuVHou=K#roQdbB#eu`q77^vfCXN+wZ!gVs~{+#a3w`<>Vgei*>hh8M0dPJ>*LVJIp z2;;tlolEk=IUc?l zHHHX&Fil6MU|9OTUSkrMMdY1Rk++qEi5mRt`FmSm@rGx^;S6SXp(?|^Me?3pyDOiQw6WY*hY`7S9Ny9rueeOszxQW84@3)CM+XyM0MZ4tSjh~2J5 z!0n$|94f6ZBH*UP!MgA^zMdBX*-h=cH<1#7`gkm_J^^BtK7m*h! z5IYWL5@E{K-1Q2aMAO0%3v#fM=x z(FDRQl%}8}?lMTp{H5}{{vK_?VD>U)N*@rs9VJS1-5EJ%KYCwIJ>f3NrkL8L51*F6 zgLdy*N!U-?{pFx$<(fwqVRr+4-#%uIWEHmi)V4+&Pp8%A{-c^MYAns1JJ*pGRt?O2 z?3*8pl9EahxM8HjR|&U{?oFu#^`qHYrJY$+OI?3itwym}5vHqrettz=-K%7>lgV74YM{(hGd~g0Ii$XK zu2NauQ?)y`UNPg?p_UnMry}r5f06Vb0HtUez>CM(9-me|$W53UAAI_3xX=6WKChp{ zD05WkOa|84UCv^1l4#yxye2nRw?{0a{f~{!EIQQ)w2wWkbqjxAk7E~H9tbIMPI$Rt z$Zdhxnc9#DUIXWC7vHp>2C{@*RIR_;ECEE~sq6xs~;EpJeWoe=#Gd98P<3el2B z{q!lw)>K#6N2WTuR>??3U}%no%DZFm(u8qY6&x!)ztil$|l19NQ55to7g4w zzvzNe!&W`KQ#;0=dhodAzYsRS$G^)dW*+|wO&~5BAv@pw(B1PxGQ z%QBvy+w?p8(MT5GLN9Lo+F>!#4-$GTb-?*XQ_878TWqfCq*5nAMg?au!--Bq2>UDV z?(U3l4g5bqw(z{Bt;G+iq?TjCs%Bk;@!7e-v(~o_;%vfe$K=KbUw%dKpTXLk0|Cgc zTCxV)X|Yf9mdzv<<~F@V=6?CBWyFZ_Kb{Kfm@-t$5{0M25&`E&rvy=z-)?JMh4}Q= zXV;yi^78!Y&|Rg&a((akT2~x_r>0Z0Q>&a=(pIUxHP={1 zI?~N)Lu|P5%R?c6^+!=6NkLPgO(x&r>HA z>ucRl`~3~=ZMU{XUH62v4CeGPXMCwY<56-&cRengQXW=bQYHJ5otgNXm5v(S`Vz(% zTf_`&t>CV#EvdA=G~EW)k7w>;LN~n7-UjNbLR^p?Z3b0*Zjp?a>w&LAivwL8W+PgteukRWi>Av+Kau)+|M7sRTzVjKqN1WN ztSYnsEsPEEAU8lztetSZ@3%jvkV_n3vcmbh&=UnKrn&}0+4D{kcvJ~M2-S_tH4*Fh zW)hQ3VyC2U70I;aZ$`XmtTwpDBF?X5&ycwB(TR9@4Y$lu;7W*D^uEo#%lc5|v@7dx|E4cUF9I*JbIgmEl#UQ3Xn9PtDX zjg2-J5_GuFSsd5T=YyP0MVt$=1&iy^)_P0CUIS~>0#Mo|Q7B;vjxZFL^j8RsRz+~N z#+Ia3-pN@KJxEJ1tW54C>U+wv!uwXgnBSqpuLjdPC;SLFf&wY_i?R--ddV9JQXh95 zsx2^+fmX;Z+wZKz)#6Ag`j5GSx-GEhUw1g#M*n<5I`NS7qe;j>hJs1Utx6;CiJlXL z51FxwN&1Z!6Dolhb?Mgnuey>={T|P&am9h48Y`am8qjX*!xJDMHrA6%>U>IU>zNYZXP;z?1 z?8U)d&RhQE1UIBxUFBOhx17i8*eFk=%Jb|)^2x=YHZtlwce>%}(K{VVf_V-%hD;wS ziu4rLeg-57+oa~Ru7Hfe2*cM^>g>GKX|;PlXae&J@Vc zmAeH9yn6L3{#HPJJ$?RCz!4Il@P3_E-v|oB9$X|M3@hXY%&V+$OFff&3@UspY7un# zL{jlugo>?IxCcQ(4@G}K zU%~gB6f_yC_69|>rVIIYZgdRijfAZ+dwgOwyNW+F8Z!31tU|%t1EwHl`+36lO>$e$ zq3q3**=d8%JfdV0fmiw%BaCk$@7P__(xa2R8XndGnh}zyzLUA?b_cX@~cGy!3ntS)OOmcst&iET` zBeYK?DK9z20U&Q279fp}8uIrJ#>4oD=G(e>EYS@#F|JenCu0J>jkbEf!``NE%Mow< ziAN6Qhm)eiv=XY; z)B~11*-0DNEPh_Y5lo9=X?{PtnNf9bc3DJD z&6HUh)*i#Zp3L-@r}|!n1b;@0)}(n*AleViS0UxjIID5~i=)v`fur22@Zq=&8d}qqu z$gH`;6qEN`Q5+#>94b!Q&x<lvQZ?o32u_APfl z@%;|t!Y?dHDvudL0Pok?_6$4am4}`7FI7pr=A+RddaGKtR7pU6S(pmJwaT8iYQuBo zCWdA^`K07yWx+k@Q!oj7N-tN)>%g8aBqVvd{a5In^c?Y5dFZfcUTgWiPqZ}E^cBR~ zxClC6f;KC|BN`iFhaS_2S&Tsc$#=IMvIKNSir}L2Fa0)5Am^1jKzL)6gCP0xb4a;; z;7ynIJ>$_{oISispwIA+u2@!cy9Hg;&#i_9cw>@%s9D{#s8;RBWw7|p7=y$JI#mS} zxf?K>{q-rG_vcTXg^8?xH@mu#$?K1I<%n7Ph> zJCn>0$H2PuC;n@)HY%BhvA_BxcG>tV{df`D20BrC8NO*ki>QD$*&d!Y9Us*bi0Y6< zH`eiXLM9reLKFgzDufM_9w&mO2nr_y)jed~m3FQAW`g3|9>5fyRT1YnO=jha&AaW* zhA2QoEK}e&WoynX7D3~M5S-v9#6&+yHVOLH`0O)Y8020kI%eoS7|>UTErIgerejz} zGRlC_w;$FuOXn$CZ!IQsk_2n)t)V+1yvn4XB?PhE{=}Hj^X?!?c8`gsv=VzUSY0ty zF7*Y?hVk#a(k3vZzulXuvK>S&?#cJ*YN@}mG_zNz4EPP{;z#L}zMYl(f8O@@XVq9P z5L||Fnt6Jx-SHnaBeE#{ltUCafwsc)uXHWaWVq0Ec{QY#d*#yGrF_`$ohJoJwt(J%eKmm7~G}ZP9fmH^( zHyU{k!;%XjbnVSeQ|6cq@()#w4&czwIzCN)auAPZzvNVCc~zlSQB-P$KDQPETDzBv z=cru#il=|;JEOGAHk@@*z-d1-;gYJv7&`{fsxX2fSa7K&L(WILyjN!ULT;c4rVIJ@ zW%R^R$RcV)CJ5`#z1QT?aL7VsHL&kR;U7y^s;{dq(IYA!L9~7a)>2HH3Ob2ISB=vx ztb!T}l-R0t9o|Cv4DRUvmPV)iJwaCab;`ar*ZdPL-@+=3FsOOYrWtQOyRv2G#GD(^ zB731z0iw2D*2q(ze4tP=27ddZJXy3D|>-}cLjoJT3E8Z__gPG5i^@EhKiBLTTxK!neOBr^H zUaj1t+uP;H%QMe2>2M2!l*tbxSd{J8Tk8knlJ323%9O5cJDuG^?m(rwNwrx2hAv65 z&6LaiDvA`3ci1wMol$)#)jyuq&1;{$-Hyj z>uCHUg^zyn*@M6t^_d$fv(T&bxu(#H_GD+t1Iv(}5`C;uDgkVPfMDu$T!AQbf^I=q zkyIrrelM@yx5)_-kwH(*pIy04{BBU%2xIr2u^TpL#7y75|G{!3tBfweC?9_GZsWp$ zLhz*NmnFBpqoje!qeNnfc|4$7UB=*(!MLTh3pKQ!TZ{m+qTm!b1;BFv^CJq#&@cwhAE> zOmY#ENM;$K@Q9(xNud;)T8STDzY_akpaG3B{^`*yrj4+DY#~66Fm0AqCUWzf$DghJ zYC0Y^*MGMJzeLx6rtJx=<}T698?$9t2ZrL1aczP-o09iK%`!aM+5a!X&ikG1H~#lY z#E30str{_6Z$%Na5<6nnuDwT%QX155jY#ZG>^+KBTU*U4s;a8CQfhQj>T~iv=a=)t zxvujMzDJCq@x)S9UG-(tG zECjT+7EM+?iSFQM8^`b}i$4bR+Oo|Pogn-G2oJGC9UZ>qYzeZp@*cz3!!2`iKXf*o zAf%7k(@1H|JfE`+=K?X^v!LgT=RZT;;ab#q!^ckNe&0Ymujr$%Jyq*e;9tSJi#}w$Hu( zbUO5AB`V}_Rn;%ixR|~mg__(C7y{4=`GkE({5i~TI%E0zX|oM+YSFEwrNyVKnka$E z3K!~A0$pc+@REP2;Xd_oGl9JJN`oxPB5QO?ifLiUBQ`SJI28eer@8>}#i7xu&zgLoFo=LFA6 zAF9Lw_(Qb-odL+c@D(PLVcoTg`!fn%6Q&o*5>sbc^v?_RB_3x_+U94FG1ZOB-b%EC zpndkp+YD3Fz`)_L;dzNj_qB4Duue}1rm+k`M ztVApxA8knZ81TNW-2pb5jQH#^ZDCApxqS0YeoOp1V&WLK@k3 zKdHpNb4`dbr`_k4vpwQ4yvPBO-aXG) zK87JRQm~!YeC5Nhr++GXjryH5QP~O-H|{U&8p6-B<+UJY(yr8Tl)X$9q6c4kn3gm> zfmO<`zf^7B4DE@EawXKW1MEvRf(2f_$_``T92GyMefjiW&ZvWZ2}hVMl{rCON;}Zq z=P(+uu{AjIytVA)VB+U3f~)&`WFdGW)NAKcUfJQE_p*)K5M;Sjbr_tb|e5e|rRhEY_Dx6shVkk3R7&v3}cizUjWM z;(arIibYVjAS5Z5Mxgcd@@$^?C1jUKf(QpaOMkWc*N+G3^TCw4LlUQl?Y!k||-Tj2S zo4~3DIO!C;y})gi9*4T$M$2q+Sdv`shKlH4 zS!k=gj-o}`-?E!3v9=d0s(_6n4+pXu-7!6|*}Df3KcUHZB|z?-qB0pC~E{nX89v1Ay*Nvmp9K^3 zkAz2`tK$(CHrA!W{0V?%q1f+#RMEjG& zcqO*-f%%7Yo1)N^5(1Z$)$+yDSQ(4hg8XcaUeHWhB9^Xp!eo?r>8uZx>lWO#MWc=lJnvBR%V5}8_G8~}Xo1wmV`$vkWeVFC1+Tj|V znnFCg5nl7NKFNNGB{MY|ATR}Sby2U@ULZhO>*;?Y3pwkbNzjb2LjB)7ex4F^sbv`Q zfhz(V;_&CyX<^HUH8y-!=Nm5BcyQvUq5%@`3nNLbYB+2eAu(Ek05PyL>FiI&zdxdv zTB%?;_#yhmnSakYc21O~RVL;ML?SyjJR!d*k*)%qW$MMZr1ffPP4)HRDoDNtZUsu- zXq#A2{D=$VPknSJ!${(Bj_;q0mzPQlg-yfxS}+Y}Szs!=xZa^thBQ<>&n4`~Nxw-B zW3^*ip?7_WWb{Q`a1UEX(b5Xsa*u50{9(3~zOoTv7G=0!nguG`9{zxoovdLmtvnF4 zh&OFH{m_wJQt($WUnWdqrY!EQvA>)${7!xy@YR|~%!=sN51_c~$lviET*vzWziEYK zE8eo(pITg2?Cy+I?f>|h=xoEwpWpu2%!TEk#H^aop{0r@pXm9$sp{b}jsK@Q`EBxn z-{g(>5QWX|GZu_{omz$1wRjfE0bsYgu0oAbVo&|(Fql~0jeQj9%Eo+9@f&MD;w^OX zI1gAsbYJ`v3Y2L>X}R>ta8yqEs@-uJ^d>g`_8Jp`^EGqSR`N;2xdqKnBMF650i2sN5kE6V%jTO#=bP#ah)r+HbO|oM z8X^R)(UpN?r0_b^xK^*GX4m~!c#nQ*+MLv#=?~J5PNM*MtDWs;fk{rCJh!7D#F}W; z!fLuaFR^aC9e!3X_Y3q3V_M834&h7zFq#6B#pojGYxX{VeBNRy1f>%u(?@y3v5-ZZ zOeW{M<}ry$pC(KGq%wF zdtMli1)61Xe#s94q$+HQjeR$^Xh_;4YJaveLqkl>>b_ej)1Gopuj4~sU(IUTBotP3 zJ0yO0@;ao_?!2SL*d;pKGDUE!2S3l@*ZF|m@|9B;%vAfqwTF_wU)-8unwN%TKJha# z-S8E{t?696v|$CQF$1mn+@cQm9W2kiQ)MqW#1hoVu zcG}=)JG_tU4NNjeo0JQ5VwBpHm-Ey#>mFQZUMbpI3%)UCvqys5wtEZg?uZE=8tm@0 zuBc?xnz1WUcA0+&rITpCRB)fzk(O~~CfjFwkmGQ1lPW`|V;@GzcPsKBvXp=8`fa|0 zVA<-uG(>pY$)SO06N6C#c(*~m=ynW<(W+y8+L@UfK;R+M7%$;InFy7BF6ZbE4?Rt5#X zo;Dsh*U&OFMgP-iGNuS0XjmMC`noln*saX}Nl~Beo|IE9$uJZH7nI|MzGvyQvG@Bq zfEuqn_@C_9l?Z2YR@&JjLkd!n?(|Dy8teKmm-b&NTgromx)rvCR~5R*dOQM;w(O>E zbzM<%myg0B%=;q3J_w0*uG;0~o3|=B2nv%v^<4{Ddb;&#_^vY+8m3~AvVDO^UJ;GnL~rcxldp4RKHnVZaXmc_i3mXoAj7>na`RmK$oQs z9wLO6L544|ku?uy!ah#jEJ@qc1=-5E!!F#WLbNHvqf3!8JZe=Ovm!-*iBDZQ2->75 z8mo#18pK$}1cVz%P0({4I7|mquWQ~gn<1sVjAm93m%I+jI>UTKpFbffg{afu^ToM* zPsI>kckQD*GH086oZWKvehYvQ~Acg)}_Y*z%Mu_OX6#+#t25 z8*zNi!x?HrP~+q#6~1r5&STU9icUc)$$$ZNCK7TymZxIi!i=;sY%sR_>jceBz|J%G zy+9hmZLWMfWG!PyEVgQl5o@RErUaq8ML+=C`16;MDTPQI*Z>HJYB~lR?Q66VULxJtkoa5U(Lz3KW>Tr~)Z4M!s5p>)Sk=It-Vue~>vJO6ej(vR${1fo?dPD291x_8vD=q(Yr2 z1^L*Cs6V3QQctfdkAB?b!~onn^(N6Cu#>bDPa{Mz%}3#?!=r52C5|Ll>tfD!j#>9$ zv^ok<=aaoNT~1!r>8T|VJNr?hn(k4W9dIFL7dO62wOL^lk&&5h)lyZl3?`*!Y`T5| z0FSUtQ)Q`6%S;pr5?MO--!yx->g@M-Rum!$aTBZO1U5;p{NjTWd zE#_ca*9LcE%tBbJM+|mDu&wLC8>{00JF?d6MLYe_p@$09KEqkY`D?DDm&WLu@BhjZaDQ$i zZi!FQXyK?BPP!!dy!c+t>0S&%dc5HCQ@;MMR}nMdOnq$b{hT^B8J~5z-DN0XL%lM}9Z7Qn#JojMI#s!T`Hp;JTizcH%C*csTv2$cyT`vN85`Oy-jUDez zQ{`thM`9+NSrP-fg8T;P`LwatwjtbPMj^ky z&%>`e5mf^JG?Lpq{0B^Smd2VBQX^R0{^jHfE>&GVo!{93Rm7v9<%3&WFlOmE{HMfg($-0_J)~?09 zya=l(U-HZ}bo`eZNJ{_9E&7*)dZN+4;ve^SU~q#G?F1N95=KYDk;{(J+^kADc;Lji zFn4vV6OEZS0_;0m%JTg^l0G`-?hc66=WE>+#8SN&W2KWA+!XG$Ap6ER03w4%V53;8 z^ZxM-%c|w;Rz%QsDPXaUebHL_WcGQF_Gt^J%Yn`(Pwz6iOeqTuv&XY30}glJc~i_* zu^PADV?>v_^jS%e_b{8vtd@oj4HD=WZWpNGQU0 zjSb1(Il_}p-h}*D6#UoY-*!LBaVx%F{{ty*c6rq&;D`8iFILcZQ1%A;`P~B18s;tI zmTFA9$BK*ab*iRmWcJp z)6wihQFn72kRO&&=swgMZQ2*euT512jqP{rraO6zL*g z-&uNU7xTJ8$WzoxU2qfd?=D_H=yi&2t571_d%9Q9w9$?&5Zt+Y5pAVg@MWoM%!p*Q?jTJmZ2F|-SdPh_fFj?#{wG^D%CpUAT)RmPI@ z5u*NJgBSyN)>pp@_KT8`0+fdTNS#V>oBI!Rx zrh$e&DcJ7|xeyOb9Bix}7mo1deAG+%%gk%?|Ktl${F!^`QJQ|MSeX%JOs~j~VFpT! zYJaf`{P%8uXBLln^4T#|^h(NZ$WwZ}sZ<-hExbQ}nFzv}OdG7dn{YG2bPKo<;PPhL zS!kQ;^mwY>Ph%g8JFnFXPZ~J6(Y4o{1fUSaoL4USA2K7L^{6L`Yw`9c=^G`|Gy~@6 zw>$M)&%Gg>M(^b#8@cOFm)VZFJat5-2zb?uM3x%Qa~m7BaHGC~N+&M znzR%y9sd%c^{dr3 zTc&@gF6b>VbK2Oc>(#a!MKF=R+upd0>>9~iS7=YO?Ol`r`$N5WCg zAxUtQbX<7guTkn!{9e=pm(Y-ZNZPGoJ2tz|_`5N$t0{0SD4O)}R8xF54XR0(tHIe!_CNeHbl=oE)B<)GG(#NWmdTzqGdN!gh|fT9YzEO?wXT@ z6g}Ehj4!<#sUUrdIm}j!Di#Jgz(o zf-LPo+b(z94S2inwRJY2{5~Rj7>aU0!kAWOSb2;cn8rMsy$Koa`#C^Uf+8w}L1bd| za>z&ZW|3RhNU>aq(y2K{;q~>pim=L)9Z4iDMkifktql}{onz59**J8iYgCzOsu_~; zqv%Fo*-IFcLw75EEH#ehp{m=40CZ zQkT{oDZh%O)KCr~)CkORbIJVst;?|mk&-MYTv`^88;6Y`LT0>S?P zxH~Stn5Km#eR2aj9vB|~>z6RWycRWQa2+ir3!Py(&vl&q53o~=M1$Yi`zj!dQ>)hJ zzT<3`m)ijur2FTcm0Qd=g2z;(+B!9a@DkLzFeZm{BnSI>P>M_(V=AXVeo*&`Wo zpX?CLAK;C9YH6!_tPmRMJor8~u7OPtyKx4cTl#_77Kgh3OYh zSxz)5W!f(2Pf@XE)A+w5S7dDZI4Gt8`GnLOU)xl=@y~j;8v-xNFqVuq)OI|+K%`{9 zzdD5n6i0c_8@MC!<^NjpD_@(4iR`TIB!93*&*na^3_;}Mu(kN|Vb3q$Rh|lvur{Gw zxtfa)-{p($&Tr=T@8df);0D_66^cf{~CAV0`R#T{dU|o(`|2Nugl*ogqK4qOJ zu@rRqD_TgbH)+q8D=dkW+}+cpGmx@sRxBm(>7V#qlV+ji+{9*U)o@DB6s^g;Nz zyJGncJ3t#e&>T3;LU@q4F3I@)MBJKZ9oeDfJkpn9HSiiFinBHPEklH3g&{`MC%|XI z@5uYvUPd}f>p5b-)fA@=sHq^ytGouk$`R}c%52j%i3;y|^x(tYKDXOxeklv?jCjXY zoGmwcf~XJ9O&I1~BsDXqmnZRPvP%5ay$A`$R>a#c5#0^N($$>AuVd}+tFtVt0RXAZ zvoAZ`De|DDUz;#Lhcrhq)!K7Pq-Q+LW7V<1eQK77#woja$5xf9-y0gs&JBVqYVJIV zZDlU95W*}l%RQMi0DsnU%NSZQa1EgK(wn=nT4ubdoeJW!A43&|%M`5dgSm!x>sX-y z{M0ZIgG>b#t(GK1E_IqNA3PORKKS`~g*uYH91tSFO&&Fn2-Pb$Pzvvnp~LHB04Zd| zXMQB!BK>lBms^KD^m33L_g8C8-Z>g!XgPT3%G^D(JPJ09e}ENw^WxQMah@6iUb4mq zbsSQ^lIqB4rO8g*B!upJyJMsxG4%reON?}BxS6WpFC_fJEKNTr75$vGe)-_vaMUCA z!c#eFLiH>{pk+3A%INQS1VdZ>eT$b z<>Q};0fc+YS2^{L_awz&!CfyNj#xEMy_7d17*)*@wzKX}DbS$f`WSW${{{ZYxToM_ z?XjwK($?8n7Mbv2`WNn-*baob#-x^rFMMDjEU0D2+zt<|F4ZaOk97UcQ!!~i@072d zenoPJQZGQm;>Nw;;U0oJOe(r1I_YENx9$&`0<%~iqlErK`ufWF$d(ZE~Y4S%CmO@qa(tojJ#bAq93KFrVYpz+#>^i@yUOa{q$xe>#M5) z>J+loJ1?_k6W0r{mJ^NzNu>|*Iah}G>Kanrn4G$XPBpbzT>X1@py-w`wb7j;)OhsW|| zv};zJ_xIIj^+!m<+G%~P=n@XiS$BU*D?#+o9vhZM+%=Y#3tt`h)Zfmnsif5yp=p|< zS}%GYSNiP8f-4*oGrP=RZ*e$E;?91{C%7gRXl{BSAvtELh5d>?y+~NVjREp>f9`!N z)3ms0^J^IvO1qOY$6;B_H$<0KE4Mwtwu;GF|x}%-~Y*s#1)V#Am*xQ4_HzAeEA)b zlr@>PTGuXN^V%V~16GNTI&=tgPuGp~FDZNVBH@uyNCR5aCQ66w55)@tX!n{-N~3BS zl!Zwb7GwjMa^|P`R^yB67IWm1f#RmJ_Jcbq>DE+iB^U(r2|@!w)H+-P_b>kWj2@W( z2VjodndmIJvNXrVIJ{q0o>%#;k~TAX@Ik|`!B#q0tPtfkW!eGd@Ke9Y6pd$v8i3>a zL8-2LUb;V@BJ!gKA)IB%WxWS+bCBgDU@Cx@vPlu_?eb!XsjlV^mp?5;@@09=Ey&Iq zpzhF5D|KNo#Tlo9m1|?J{t--{t0nX_lgW6p$#3*o@mRu|0Blk@FS4v5Tq^6 zoK|`u4VJ|^#bJk^ATR5Org&g!)kYYGeRem_6BXTZPu+}-7H?fm@TIJ@`Sj;!H*yT0 ze#{{m*g`EO3E#hh>5l&FDy);B5D))12tvGZ!{!(TGek5_o`2TjgB5fGbp!S$yZV&= z(zX0tpCo0VLbHCW2{j?uyxA}FG8Tv)FnThGcJIQeH#|jc~nY2?UzbyTq)lV|O zh`yXfDU=Zs8J{!J!$=9Y1~1!l;2g+b45dPV%KcO@ic2lF9q~tAitv1jkJqP;`EZm~ zmTA}RN*5*VSK1)9hhM6xC;E;@pbLuE`q_|v0x zAJ_jDAX^}gwu?RRuv8MX7PfduRa~7}F8j4g(F69tB6D+sRtwbO-0r!1n}y$wWnbgj z3I~wSrk}D^P2pZ=YJvQX5lgx6RqDD&$NQ9mY90>&1rL%rOq$2Hgzcl&Zad@92Vh55 zS#aLG=_H6**!hv@-$4j-1mrU185J;nO%C0wL_t-xKJ)(M-v|)j(|b93wxcm5UibMT zzA@F%H?toqB$^92NWxyf1C*G41H^I;7zE;P7@7nATZkqWWzObw;n_NVxSs7xv=qp28fANA<7=Kn(rEsFmKShlDaFXdo(k_`PLaP9m^!mOn!?{ zhx;p{;&Q!vT4p0~nXV#X6jOlx$o*`S4Zf>#_F8ryMzTeWt_WP4k>w}#p9*-wFZvPq z@{!DbCtJ?+KPxP;P?}CHTQS`Me24z$^~b3-8=Apv)mcKd(Q)LGc9v7OZM^1?f6TW=7VTi9pbshJm=V;^e-k+q{ku9@FIybpbheJ9YH)ZcH zC_LAxzf9N5sYfD7QS?zsN0)XsnUM#R*~+MNkd{brm+o`Ohcmj%lwM&R0emQZOj+;4 zPf9YR`2x1{#WLf|rHWGc`5y`_ZR^kV_sBC;U70x7>GN)T5jzg7zB>&+5^a-uMr*I} zPQT$huW1?-urTSa5CS9E~WF?+(&Q8wWahpzE`!%WRZ}GwaWn4AKxd) zwdWoagb<3!vmB%+O1evn!lnCVwB83#T8CoY8qfC|36qwK0G?wi!Py(*OouI=#XjL| z0jIcK`;RGokE&%JAdzav6i_miOf*X5!~3ahQ+R0YG(VL#S-oy9Fu6ZUd|1fkYr(aM zS*j%;R&*q!QS0(lu~oNbJZC3fn|y0#-vI=P1Tmu3MVEaoHSL@^d9n5 zRu*o9GoOxk0ddOjf2YeEil)%Pum2>N) zH=WVmNjbapT7WLtJe?2dUhe1DYZ6%z4=bA-CY?wi}iR$d5i}Gs3okKin?LGSP}6G}V05NO7VgehLm3_o{!`35eIi2i=>vzGAs9&V$E!t4ZpdV*H zYwcp`)6&D2Q;{A5os|FUC%qz>9>!wU>u3R z6OkCn>#SW?MD=i+4t_VlSvw5YUC@&rCs43B8X>$-|+w2S6AaxWJa5dz=ZapK}YF3nP(}=qEeuf?VqpBQ& zy-rWDgr>q@D76^oxq8FIyI1b>w} z;3IY1cLT;##9ANstgM|1*hW9y_*AkGQJ}k% zB-)b5u^3Yp*srbGm@>kijdbCpb+rrf8D(e=|bpnjB$Mp7FO9)n>` z#Q9RSl#lyopHZSu!{t>SZx~oK^Q!Nefa5OvoS-E$hefhxX*rE>W=XlWzqTnTt@j#H z@gHQq_yXd-a{tZp8{CjWks5p7`F*lw>Ug9!s@@@gEX(-!?DBmSddZ=yXbig}HuwQjD>hcnDpa zliTb=cv$C>7O#VNrl%ub~1Lok{5l#*ymoH+%^`1;4A9PS+E<8l zrXZp5`qee#Z3&V@qq{{zJE(kODteCCtQU`f;eOK{Gs>KM>qLtF2iT(2Nj7bTp)DFb zvJNcd(2`-I%QWdl`bUD|&KD81d1;<@^+_$R4^e(>h<|6p4=fc;FRM#GWe3M}_XqMe zjJn0%fDrB^q0BLba0r#QA6c$zw+tD&`0s#aOGRxBdZ*@o!2;SAg`do2;1jiV-29vx zN!&2BxL|e(v;)30;|&D?$X> z+ZQ6}NIgnr5nr|TjzSw1A;A!NkC6x{BD>l|p&_C7uIC0IxTkDlt`ZL(U$Z%Ody9E^ zitMfM>M&0JiP>Q05nl#hw=+S%R{M~noYa{939t&-n9mvB4ds32IB+U4m@+kdVuvIs z%0t|Q2Sjklb41Kc*^>II4&7i#^@2cmNc*ZAj2PlJvtVq}k>d_m&JXNB+0=%T``~;1 zCR4@pv8@ntg3L#_NT1T>Da2Bg+Ld?BG!q}-xWA0K!!_wcWoM4n4q zS{zC8A>A8#hHda>?#wg&^+X{Bz=L9P`}nye^Bd?+mbdgiV@eguy*~O-`C=4XPRJe+ zBehuntqw{&HG(jq^IxYTBGP_H1^i9|g@4Yt&LSq8uk8Ym!hAVxo3z;%Xl_KAoIS;_ zl$u0BTwR$#6d4Wsy^vE&uH$~2+~JrnDdivPy_`SvJ+7`#FF8*p8X@`eZqBmctD(nr zY-z&Nyw`1wT%QZy!Iglmj9u|+R4I99eTQ>x3MIyEkqQ8dm=#!cy;L+p5~cQcpeV~J z_MPhgPc*NG$?M~3Wzf2*6lYI-(^w=BVks2@(Vi>U0<-aD(5wi7K`V(DOgfPJa5~?7 zuy>%S>)5~bg1GfVgFn~Vwy@-$kUNoe(T97%)aD(f*QeCs{4kjpV>?-L3e&?<;o?s) zPvj-ubu=_u(G6cg4=2sreg^kBJD6;e^aC8V1j_D3Y&OK=bkU5OQ^YOs1J z*rmXTwoksT%t*_Q%53bJWvna*a%)vYm9tSNU~Z=@#&X(fW7%>%1}U1p0VbL0g7fBYdoS3bn77o>9kxv&%e zVjEy`YPQD~x=#ZmQGfuwXUn27>Vj@>9!QhAo}FMqWgcBEvcTFw;U>K{AjHYFCItE? zfGj^@YO0q!)@3C7PU3`kp_U!qFV%pn+2{bI#+rh7H!mGwo1w_?FUSgXKK47mV2qWDs6PXX3t2@l9h5A3W^;@Cdkp8*Wq&1$Ew2m>--pLpL6o`kr z-~HQj(1QnbV_Vr4{RWm~1qHO&5PVLR=NO^%s` zY4YUR{G5T19DHs7fIpOw^7|fB-gv&|Q&mkp+@S}%l1h1CZPiU0&K6Z(5o_Iw9_Tgg zY?Z}PTbDt zl5)0qwsJl__Tr&P-r{8zCh zeNT@w=dr8gF13$)uK(|wqKmw!_cbnn@0vdtF%ZFMy~!pnX5A%69-Bn50WnmZ(4}&C znml8luiO%W|6U>mpRxceV>hUEpZAsJlufSB2&-Wd67^HAW;Xvm3YWQQ_Y`|sI_~xJ zd04h@=QF8#mhpk|tjYQYhaC9rr!6hR-+agWN<)d{!mP?7+kK!SFs@rT%&OT!Jg?o> ztQ~oo+i`;+*L_~(qj>i-{|a}jd(#F&!$n=RW6lrM zD^rCQJYSuNaXP&$pLc*o>5I~-eSXYXNfmI4RRKGU%K|QNti0b*gF9R2y+gS~fiP{9 z_wHjH-uSA*|HAU*H8`P7mUYYU+|oCtEfME;q+H_?+%Oyg7H&7Hg3iH zQ3)3~hX6nA@!ntIeEuL7cHn(BCt2^s7nQ7$B*uo}@@1k%_dk;)udtv(0maPSfD}q9dcw(=_3iT4uNFO+BNB6;&yId!(hAQyIVI9xj_4 z@Si#cy#1@xzRN;tS$?|JGb;=JYV|=mlGVtAtLJhk9U_I>+5uHk7iHLyi=7t-xMH|@ zPxN8m#|HW(k&Ctd(@>6EYFhiSETu?t0<-fHRs`)h92hCYh^t@((3sTDe-PNIgqV&{ zMn+MA$-YE(XSi5$C`Q2#ewG**eY7xhV0N!lWy^UZxYz5+WHn(1OXoesj5Hb#tSVi% zwLw|RKTyWbxx|52ZC+Pf-%U+^nNXxhZ{7#{0jS#jJVNuky;mdq(Cw-@ZTHZ_k>@dz zCg1c}SaL{6gdiH{@<({+lxc6n#=%iG#h`MyeivgoYlTPQxpU68K0N*1A2{(JfO3D6 z1z$@)eWL*BA6{grZ4m0>J{`e>`y$$1AWuP;BZDl6;9qPAyFYRsnITsuR#R#rX)h8@ zl^YbF+`r?Ly7^%0UN!jaD=LjXp&Mt zKMg->`C+A7V&Ai98SluAJ}sY<*tM!RQG^&1-=-L>WbbvdpZInGx*c5Y&O4iL?wmW zSmwa)5G}Z2tX}jserkh;wA~DOU4H*GhCOCSHAnNT^>noKWI+%AF2d1-CecU~gksXb zMryDxJT~>o{Y+4S?5!HslAuXHsczpZ0iU}%AZ?tm?+@8$s}X4}^3vZt=%#OimwPos zd5n34t1*k-v*#dO8-|d&|AQvz`0DN>s_(YHN(9Plk#;|pz<6A&5U{Uj_xk$u4zKok z_aQCvuJC2bXIbvNgm+U#wW{l(R%K%D)y6`16M_=<*KRl5u6W|AxE!X$C3xB}5?1Kl zQ7qt!zF%)rP6b%TfpsI#^>5yN>WY>*lqv~RBv3C%*-4qUI+IU`xWo!pZ+eyQ8|bNX zR&U-Q9V0DbSw$GwNR4J%V$ZI?=cMtaX~KCRvg7dZHP%$VGtmxOc&Nq3vft6gSJLE1 z{(UDMIGU#tnp>ZfYb{I4pXg19gqZfT1~N9*jR?;+wiNgMvM?de*oIV1q(#jOy%vCk zhE=Ys09P1~CE`e1nhlMXZiW5R;*=3l9^_Uenq@!4kY14a;xV`ml<82kPlt3Zzquwx zy4d!oyCd{{I1;;PszmpCmQNg=p3ud(?oi7YDljZgq zEWiCL`-Za&{YPYS!XsXdDNTGp8cqEojmTj(*umM6{{Xvlr}I~&!RAJ6;}4Lc zNM`7xJ!v{wkR@=wH)BXf?g;`|bO=;@`%l?iprCTJ1^Q{wEdm7dujCtzD z`}=;jyOU_dvsoQp7xO(y=j@(ePf6d52dgt%?{DC~21VU#Xk@AfqwwT!xa*zvDV9Q% zGp%bJG_AagG@+w4X|z3e_DCAz$S~nupk@{$X%cd6Q7A^s zYBmWB!m!9b* zdXu1-Pse%RP*K`k$*_p8x>{OinQ>!)YwVfNbKQ!11(i73as?d0haPhEuQ;&%ulW?# zo$}`jW938b2qm^%HKxX|%eb*j9yAUhQAvND=JlbS`%cX(xI!q z_d~b)+DYUWlk!+&Mgp6w%9D_sU?#@goBWKGvkXh*x^i)=`m7K7KOsP$UO0tz1=@1a z)znP(&iXYD@1HUyn`XmVek@fJb4`d345a9jR^>l=yvrOh=n8w-MXq^xT(~=>@!#khw>F-<_Pk&T5K4W)kQ|vV_;qhxAx6sZXm9G%Q()Y;w+o zZyWu>N=xka8&a8>>)oCgV#OkJ1L9*)2D1A90c;oTQ~Cl$YVnEgQ!TC*Grx^|mNS9| z4wz(FK8jH0G+Q}ks-Xi&e3qV{PuaeoKL6PYGntM2itiTFXGQ5wPA2<{UNc4tWn2c1 zQ0(DVEsXQqn%HHQObN-(IYg7X7L><|jrK=43XcR}k>*PG_BoF&Ll0!@k3?0%f$#+e}7UH>4+3|ll#F=%9SvsD+{gr_Fs6JEq+7D2|AYl_D98v6QueHY zvZ_Ng z2ESTj;JbB?P0Y;N=e7J*pQ*+526H|*U$s+xt&h#YMP*vW|1eKHM+QHuo{l~p|1F>w zU?c23An%A8X^(nRl7QLd5Hlm)HL)ocnq`4T)dPa!1ojn-;AOs_J88pUGeW_ z;DES`M~l%8>4z(tCY@quHGWkiROtQm$p*s6M^r}~Wh_-JBJ*zJMSzvWVoy$^^v8y%1W?}s z(xCgW{MOHm*mC<%Udtg;RxL9ZNmlI1@g;ZcKaQxn8}8is2JGK%Df|ym7WCj25$d*q zGvRw4^|;c-&Rj^8dQnjnS1U35fkLppZz+vrFgJOVW>idZ2Uk-;{=w=cVRuJ~{*f}T znP9{Lev-E8-P@Bx0=n9Kc0`lhLWM>QGfZ^k6WhjN=}Un;Tf6l+>JeC18cMc)lMa06 z{|u&h^20yjDdIw@7-RbP?kd+S5NZgLp^JYwCp1z)RFsY992;a^@{fMvje^V;XQhJn<4%I7IMu0IS^&1PKZJC1)lbi3oF>+RGqz8mjl{9NlwTn+atA*GAOd$2jhk_CyDth-d4~j^|F)UDuxfQg zOxtuvwO3oRPEutR#DYJAr=hz%q@p=!gwh!S9xpM^iSjqKlq(Df8iCiM>~MXG3TY%a z)el$+=;}`5Qrdo(gWITY&_!Cdxv`S=&NRh_cP@#-Cit6aKTJEVNM>hRS<|JO{6n^B zDv}yzScl?#8+1eX?Unjw`eK1j4Js>>OKo=Pe%(IKB1mc_>+;{L_U+AOO5^$gmW$U+ zrRBCebKf>D7XGE|TCj{T1eW|#IyoGYE9hWtp&&7(XURvX z6B5$(S&Ag@e=)X|-QjUFR|d9c{|_#VCsC_?u;O*-SJ3K`%g3c;MHCu)hu^XqiB=1{ zhuW_7_45TOwOQyeV`>vz=SO8p->lOq)tu5`+WmZCtM?L9L_8)9b-!`ye>HX24^6*e zzreRKVt~Zx95PTK=5O^4+qct=9x27d+ja?(&;LE#}p z9M<-*PyE9l9ZtTSLu!P2)b(yvL3oT4pYdNY;kH3`%A_b<{KB%^-_xSRP#IdYZ~3)C zPJ1it@wEr!2FH+ZlXSagPA$VHs?lj$h+>&(0Jn0Nt9x>e_YFjyatvNMKXGY3y(If} zY|6%}(V*O4m*M&v7ph0+tcHc{Ipz)aLY@^DOq46sqDfQdbYe#PFKPax)=TM?^{bXX z9@XtAVXrIbZ`pv3SRmu73Tr!hZV>GT+om17!i__op z-7?1LTSC<{d4}#=-Kci$b5b)CTbsfbM65^ih@!=O&NEU!hlFP^J#WaB`RQGB^LHuz ztRzfA$+;%<2kuMwZOQrF;?N@c0eIa#yF%A~v|$yL&qo#b^{^*WK*u(E(^gu3=TVcY zR_taLi@8;5y@&ST^IJ_78(j;2Lvg|CwO$T#iS%63!a!llXNX(3-z&KKuV_g6=JG~~*??acGZq`ML!6I2@z;UIk$yDd9D&~1b<1QuVCtpRy9=D8 zcJwhsbHtY2SsMCzPc$p-{c^`1Z)wL#r%(c%4Sbd!1=_pjL;Zk89<>MocaZ>v3 zbN~6>K4M;D;GUfPX$MWc1t324w_H>5XtMp>=owdVr)|zOm!-<^MUi3)T;S#;ZIN4O zoYlO2*>@4dw8L!~j~+}wBT*oJXggVMj^)0@w~g$#XB{7x?pHIkdubb5OuW+*CZpZ$=|nxZehoYIaselQ+#v_PkgT z7My{K*#|F_EkyMPcRuj`w)l^F|I>@PpA_=em``T;ft0W#@T%bQr%Fo- zSN~xZQNy6W0@*URzt4qaW0M~{$fvtHC|&|JD|YJIbrXuuLzyz@8&?GF%+S@lm=T&FRsTp&Gj%ev)Tlb zF`t-BHHAM~pfkX&3!R`hNXa%-ZI;x*-;>FW7f8vj_#6Ofusnqo==POD#zBYOgP>R5 ztp(-9`>k=nGBZDi#lnx~VqjEl+O?~{WyDQq_(>!mL0}qD&^Ih9+mBCn^xJp&ds$kF zLhf}zo-?dS!<=kbc2y?IwGq}rS(23I=&L`yr2Oh&CHu)Qd+d3RNuqIKrdDDIV@@gD zYIS;LJjRP&sExKP>%`L9G_s*f{uI z&jNLqH+{eL1-!o)P@5}EefrO1g}+T;_-^xqE#^erl|&!C!FQ zFIUw4M3=C%X%-@JZvuToqPWq<^2v0C;p_b>ohW}av zWIDcYS)!MyD&_65q%(z|gnJK3l+3mym-ku2i|jf!iu(CO00^?PVNq=@#C`zbMP+Ww zZ$V!qyPlv3tTjBxbi(8N*OHiN?N=g&jK6$^Hq`FexeF7OhAV<>#9x=ly$62ie>xi2 zaDlsz@i8lu;r?5;Wj+`;A@br{F%goZrXU75DUV=!EOmlY$yj{HImK}?=0GWtIg}-q z08TjgC7*r|zr`X`?32h`6Eap+oIC=W$CQc{>*iK6G}To$kYPCE4s?FTCKe5Tl$4_U zafN2TBy6CL!K?CJK=H zt+V9D!UIp)VDF~P6{$KE?KdiimpZ!R!{>{#wZ1FvqelOUaT>gRD znHKCFN*sP@zzrzxVWd-xk$|q;RzrP`iFNU*lGn);b-~T4qpxtD*!7Zx!FPG{v`dAT zIho=<^YR|rOTtRQBAFH)V;I_GsY4e}PxVY*LTkOB;bN9gjg;?R%5{gK5)3wNM3iuh z)AUReT;HR$>5a09DfbgBh21Tv#RI+La-fR;0Ar$Kv`+o>@NAW|Zj$^t-%11uVa%%a zD;_4tnObyzjF(^&!^;e$GXvaI*O*a@T4IrrIc9jqPJh40^f2jsP_mS~Fx=w?lI{VG z0O7}9!OW(XtNBNUl2*W>3RZ{fU60*pJ8oQPSKix0$oZV-B zJmm4TANLt~V>RS`eSe<=FJ4~}6Y_$~L(bG{iLlJt>MuoV59b2|9WDAWPuiL;ub)VSJ3UmQY?_==Is6 zxryxJphofwmZ0~hhcOr!f^2Xj(pkbj#;E@;46LIEC_8jIjyQ@AS4|muwqi zN6y?r4sUTAtu{iQsXtJ0pb1>;l)=O}y8Xn@XzKZuia2|SW(w+9!wz7Oh(Gu5KYwt0 z@vUL+_n#I&<5;IRzt?!n&xRzNbM9!o)|*mZKiH9SU+K%wf&pHfBoq0h?$~kCWjJZ; zH?oL$ia+TnM9TUEH)s!+iBYp4>PJyL4lLh6^8U>pOA;*n)i3(Y{{cP)`h?73n+3&a zIW*1!?D0h5(lLr*EJH3P$tyIycdK}I(xu7NGSsdPLPt5{E^!6u{0I2$D)#xSs9Dz^ zohS0?&zN6i>FFTN>%GK;9NMpqsA&8)C@@8gXzohdBVYu#mJRRz*XB9fZaJ;JY{d;# zS$YOHS^sh&`s@f7fe{26qheBEv+ujL8{g&1yIDy93wAdNHM5k67)~$jyBl zGph!twan@S2>rf7Ox30J!RdT+IPIN-`IIs2W!+E*IM2AGzKG|qTUIELyFUdX zj*-Y`^R&Lx(YKzw?~ zTHD{xhf6RwW)bt@lb$GX# z=vp*#)7SUp{{-@;I|pg7DMoGr-Yx~{L5$~RZ)DtP6+S2);BJntDr zkGKs&J}g2Dh@^@9_{k!O;B-dL{PiyAwmCSF`~PCy#VOD3u`^}zat8BXYN)VpZjt|~ zPEB1f!9H%D#T&pL-9cS5VBVFFDz=bxj?WsRRyq#pelwQvN zx+;{}wJN-a?@>5 z&l#Wf=qq8R8X~z>82k!u+zhdo`rL-iv2?kSE}-TtE4Kj7T6cdm6Y-9(_XXuKpFzd% zS=!!Vxl*OueFL&1>Z~1^dwI9r%d`Sei&H{r6x4kUr#<>+n--&GYI^P`5P(1(#TPz< zo4fQ1&lVrpnv2N{*F&?n@?-RGAQmVj#RZDO0Id#qbZ1MN8_ooMl5)_SQ_p5yD2~^8 zEVO19xhnRP!6v|HG=bMlX9lgH0MEmJRZ7tO2e^{osrz8-;&@9Co~k-hWcv?*T=>rG z?*`5y@#;AZ-Eg;cNP8WAYt%{5$WtmT z+Ahic<)=J=%6<}mnuNohHH5Hcm(qpgq$MjEXDdR%jIO~z1!B@pm zHYqSnbDAy#uT{ESyvA+oB(bE+*cqcdKJ@NpEvGh}FsOogNMRDIg&adC_ zUP-X&9m|$2%D53{|EN9TtFdrL#WBo>KLhkbcYa}O;*6;aFy9(>fuh?&X^*RwIhm`{ zY0@sL3*=&XjGR;ouCt)?PIfK69n`&SX5Vi18*T0$K27^k=rDG5C)H|Z`qMV4YQCw$ zodlYEAy-{<)}7(XHR3&~Tv>wfRQ{B>5j4(kUuAoOoZ=ZW&nqln>2eO`-=(P8rvGkT z{IJMQY&+$Vcp_cdUjcM;?gvfH_YAb@%j}ei@XI~B-1<_A=6oVhdSvC-pGTEVf5);0 zOUoxTSHHM?H%*0pv15BzK_0X(4x+8TSEl5m{rac>h^UPRNgr$|Et=3LvYQOqz)m!X1W(e^QwphT#z^|xy8FM(=q7=2o z?wDE~(i7~QCB|TkyZ6ZRwgGQC+s)iprN^`f_w`k2@Wi7A#RXj>TX6&5Yl+_Ax}S?kRP*clq}d;Q7EVWRojAukqpAVssj*G(TpViG1K;Ez^WB&)+aHKamj6jSSTx=7|anL%po#$Z}LQ zHhwB^g#dY#GrDrXAF7m5Srh)y-<;$*T97ZQG~$=Af9EnFl?l zR`T-$`Eym#^M&diE*1!sAe9hz2s}$r$@k(i z*mwDU-gLlZ`1&Cy^{(j7cNrMCOXQG=%^B4zV@Vhz@xAyF2M?c$U~*|XxA^R=K1mbl zoK}RZeMyk#;M>w0)IFv{w*h}9h&WS>o-!DMYD5jvU91BH3qR%xuH>_o$pD%Ka5QEf zV!?c=O49~;7pmJ`OSPD9iOFP*j8bpC6yjJ^U=F826;R=x#yCLL)7+vTLZ} zHeJlGP|2tb+4oOR@xPXj`MhchMx`qyCPu?|i$4w^zi{tcQo{{7P8Pxt4zKcK*Tkx) z56Yxc>7SOgsSfIs>T1|VbE|wTHKT!o$OytSuUFYwUTODhxju9&e+JdpLRbj|k14Fs ze_4`4`0Y%o@E`OP=kcXq+u}pA#j#?|^(F`eeOU}PGRH{6 zxtFT+fC1a)fABc`Uk`Z1qA>0=%e82Vpkd`Qr&E^X&*7o91j8|gLKj4F4qM%!zjn}G z^v^rEXlt;K|2QXI{TQd_QN;ruluD-WvW6;hO2FR_tOuGS8)7|gh9|Z0ME z>fTq2D1iB`u@Y})buQ2V1pra^-Z@+qyEjz|jCpIOp!!9g1ppD=UIBX4>28_OO{DNK z-@UVdK{KtwQ<)BgP$<`}lvG*c9N(dXtiPwQore2-8cZ6N`8I3HXDo95-v7x9lc6_9 zU57fuJG7Y6KN;oSFw0A+r59i()Y~-^F*cXtro1Y# z|5dWywP?5GjDLu%h1ZAtTtynK#%6u<{@NzX&5-30h}IIMov@sWijw!tDTL>pL(fbk zSZkUUzIuB6eIoRHGe=-+v9;AMF245hQ7$GU2lG=HEu6c-b8^04H~TZIet);g&qy;z z&V48kn{7adG^Dl}eP3pNp|-o9Je}J?Z$4<2PkaDHfY7j&9E#P?skKyRjK{}(VdAb3 z6Y?!&No|R8maUL8U?slEX0dnj{_)afvXv(X1HDFxdv$E4P9+`dk;9-NLfuqI`q|j= zyDE)6-<@}B5F`M4K>qhds5tVY2cj{_3!L>ZsqGtwKN801S^2Lvm0w1tZH}%QOE}S3 z9WVytUdC8H7AkK6_ri73VtYS`1);Ff?8kdR8IT2Z?qU$(9wX0J?m0XcbONesMJ+rW z7JI!YvCKIVN1*eggVU|!V%y0rpp3;lUJc9;8C6ZS=*J`=M30k)O&efeLEBwKaAj^_-p#s*{oEw~@=7&-?Gj*-j zLH%D`{{j91(&?^^F5EYgd-i+HuR81Yjajkc+YGj}G~aTu@0^V*A43(=9}wF75m~3% zTZ+oXPs@XHO%iVgumTwwUgFZ*rYI7+3&0D=xQ({B1YUUMmQh?!%f~0x1hhox*lleKg}}YqaY98!^PkNStMz z?MzV0ZSeHYN}FslY;Ek(j?rrY)Ly=+v>pw=&N8Mn=CS?3p)mOdn^c`|$OsKwjIcVu zu$?r2Vu-1v9;`j(=89&!*=8frIALwGjB-Ux%BFd9@!1id0YrD~^SrehY|d$uSh*_2tzV zNlvSXptqjS?*Hwdkl%vM3Uqa=yfgkUR1K|_F}u+@ zGbl?dXY9$g`NU#Il#dZs_d5^Fvaho<0teq*pme!p;ol3 z}qr0m+l7&0}ZhkiXFD zHX*b*Y*##b())z9Q4np1WdDFZusc#8mYjZ|pp4dJkrt?@(ZRcC&aVz_eS6qlilzJ9 z#+n!LJ|{xqWJoXGj1a6pQ<*1==Y_w4kG@^cCHIHQRRF_xcRO@hk}zhX?_YaVb*nho z4eEYqs{ew<%sqLC6BqoLo!928rS%W+Geeb2o%tQB-sxj*T<8KJm7g zQ@KSe)zTC5eh26$(AuQAX;fV97NRV`k10OfwkR(-faUudg(6r{njSI9DC>2Ri2g@#bo;{fjnvV>YiV@wLhH-oa@GcKT@gQ9IZoDG-1 znCM_>{{Txebvb}m4dK`&*bqy^@Z!@J3zy%zemOF?w@%uS!sg*m_S|+q{+$@~q3w!l zS5J9#uAFULUD$QQJfx8_=}d+l`Cq-)(~aqmwh~LG2CLlbq6;AUglmfrz9wota0U4G?m7TlNP>QIvvH5&m1J|BdeNhU+f;=;ypIVz$tGLc(<@ ziQasMoy`l3E>}*;LCe_ZwVT$C;#P%7^?j$}!Oqint+E#U)#!bLl^cuupKPXoq`j9J zx1c^YKH>f7ZOfN943{2yh`e^MD$Gu9RncbfR6oH!`E-Cb|I}CTLzY9fR_go-3MB_&l3SLQlYXf zF1A_Ma+KXK-6XND!8L^Hu>vz?yWEQHtZJjK4Vq<{rYesm@au=+u@W(Qg)TO(T8nqB z@8|J61y);msye-rnsV;Am1@PM{|4(G0NH-}_AJaxlAkkFxX8Zum-hjiqc!P^!QDtT zQl{>CwShf#TR64ALL7gsN5NO?t@FT`#*fupJ{Z=}h2NnI_UA*~zjVJ=Z~plk6RABQ zZk?zq>GUmk?v><^GzK?HevS4J`Y44BHY^P(C{2XLG>@j%$YQ8b`b{jnGcZ7l#d}gT z!7`QtNBccKepB#H4qgqPN(V%{yxqNyxVDaqyM?^BSUS0CxZ*G9?PZY5NFR~V>bOx$ zk;ow>NSOJIy01lxZ7)3td!pGhvCxOC%C(jWvhdX#oGj6ZZ#j!0G^7#v!{CoTv){o% z8$`Ph8&*ci2SPC_ZJNpKDyq=3$1V>aP{FkMn@k21m0+{+|eBkY^+bM&FayTFaa*3AC;d#*;r&ZU?aOFuKe&d zTOlo^y1sA!m7rXGddIxbYjb4DA z!t`g`hN%HbTt2^1i^m{V4El)P41T0d`-**yY^6kyIH;DjL6vQ~tBDg0!*Y@AKpi=Q z_b-afYkapc{O;w**YClXp?`;~t5Xk7f`;>C$mGVJ2ssl#oeMc-am)Gx{6HsK@#mkbCPuPq(2+pyZ=WK_qmk>knP;!Bw+dA>5}(!^#1qem)<|{cH(gBkJ)Mu zxJaFqze(0|QiJ^`!)4ugBdpBM=#;)q14a{nSoaE~RP|F*+Y!UD(ya~ISGQIUn>wR{ zEd=j`oPYQLVY{wbzaA3eA{zpQgkwT)eje9zcafr?z8)s6WpiVX6thOhfq4l zBfgNzx)_3cn1gvs8q4XPG$i0xM-R@}~^fi`v6GxbE@QXj+@aY+<2V z@rKp;K=#hSeQ6$vqXsW@%4xciqS)>JxGIJNz>frTa-GCb{{u83UF_qh(enQQo8A@a zYJ0~t>n#N#_SNXAR>!wdfqUGj^>y50u!#Tgckyc8U}%}`=3D$rh{4ea z0C1rGv~i5uOz6Osc6x!M^W_PRd7fJLF+=eOt=xD-KGQhmEwXY+zH<1*-z+x+gi~+P zF!3D&@l=9oakA7{ee)%z>W2$S*)K46cEH@DaGkhI_?xYY-2V&$PF+OLN%DQB++|VO zS~0EYE;z~d$XTY2w7PcEK}H$GVq7IO)+na?uWye$RZoBEu$LX2o1AKx9AlS~$;uMM zNT(XQq`)iR`wVJWX&$@FH7KBLWbsBcLI&`1CFAP^mPps8HEBZ)=wVy98!Vvz2`T1; z9kOW06FNS^i~Q~Uj~5toKRMcFRO?_^(B1ZHtknRZBo0AGw)?zVgkV49)`S9{^d)TZyZp9G4o(e4 zgEOjpJ3Z~HhDFBf>c??>;&ZJX$zKaux7UU^6gFPHkgWzN$@~CVsHvbF@6hhOv@MM* z{8+I;u&7FaE~3a-0(X`t=MRmLCxS#BC)!G6GMa$IH!S~y!#wC$4&G<50 z?S9m`W1bgP#lAeKBTtY87&f^W+k1C?zpO=+5iS zt4^RLMxT}UA&4=Xto8b#zy%3k0P~1P5la9rp{r$^^dS5ybUce=tHy~pQ{V%fAip!d zsn^80^5@~bfJbjE5~jc(bi(zXmEB_`rrS|fEl^3jLGp9uTO(mW(J)+zHp|foBy=Gf z>R8+PI_C4#LBWd4xFv)3giwaIQS<(|2^A%&)m+_0d{ ze|V&rB&DGcp$bgxef$aLx3I*ThxyaFqDu`~NNlHE)FBA8v$VuxB7uGEJ$xUJy@fD6 z{gd!A{g{ikL8X}!C`3vT2Yv{#s%c?7$qu#Ue{l`bq#h7gl@O9ilT2y%BRdx# zz>w+K*e>EqhFG|Sy?SA%0Nf9J43Xw&k_Cz_(IuU(ai&Vb4-e_`A4uesp5dE%;|QFp z;P(2>@y=LVZqujTE9BiT3)Z_jL_F^+CYpeazFfvv?O=A}Nm@~CbZC+(0#0}4&2n;t zX1^fWWcs`8%X{r?Jx}#{95Iyma=8y$aq}!`hSQGRe1u3y0;PZq!oVMEu+u~4#+6hJ zb=wCFDg%Uyh6AVT2+Zm+{d{F)+6J%Gw*Ru`uBbt#ltEDs)F6@kmSM_fG(eA8BD<1o z;b{u0b~3A!7fui_O@RJ)o~$0&^UB?|FVQrwt?%{Nc$&Q(5n(z4f{spL1iH>cdZ1MV z|4DNzBhF*|xQC4rzBo^dGF_mmQGwNcl$?(N$OZ0U-tvGqGk z6tTU>bkWW6WD2_{15l zQ^0KY%D0mQpwU8~r2RgndIEUubD51embH}#Z0q-8BD|MgIUhhnXxW2lu(3@1G!}iW z5vhYP__c-aoYnX8D?+_?zTV(fkxli<(1lU4|A&JSrRt+5OgPUi+r(!&y&)|I376Db-Cb3 ztag*^%qIk$v6~!#H=7VlEFU7R!V!g1HsGXwHdeB<%XU+VI(g2l#Irkdv*I7H!-MJbHF7n6r@V|-=_7waEWjvz zu%dctb13gAkQTx)Oo>tM2Ei;x?=(ky8aNKG>x0Cn+wIUl&UIj=7JrAO_w<3?ec}+F zXm7wok4)TN2hq5Q6987i;fd5eOkO-^NkBDX9j0kcW}6T>wBb=LZVFHcOGF?QApWpu zI=bdqYeS(Ih4{08NFA)Vdrd^BM#{W^@=na z$T5hbKgf7jC%ZzTIfv09&g$tm9<6P`;R8M*r@nAlwB-N=82W4J6Qz_17zK(9PZA#c zgisqpnKYeP#0Yf$NEn6LsBNNiIoEg*X}4MUj$pBx2?;X#f~jdACp$ifcbfB0`UG&w z3RD4%U@`XIY>MowTMsDNlkRblk-ed6F@w64qy-(fNF5WGU$~IjeXE+_bDi)8L2WsPLuJD8o;$v z&p>4?-j{N#H;24@XH=ALVg0{|4_Ue>`Gx1uPX@=jQQm}81#@bKwGC6LCgLdXyGjF7 z5IsHY(eOKeNY`d|N)AC4G?o{nfu!K$SI;#^;cCC*_ajw0pTNtqDSvQe7pX);E&WQ_ zkVcXcIB$f?Lll%XAI21m+9vV#?W|q&XE{gQh$7+NJ5CD3ZdZ$w7V4&(=8T3QZy-6T=0`*_Dz= z&_gnD=PTggwQov@5`r;0(UFJXs_FtX_;F?x!IxY{uG2{Z`tkj99Yf`azPAHOKFQN> zx7eS}Tn$pOgVS9M#05uQQE|t`%H&Y<6!q8CHtmx9fKG1N-DNm16w4ba*ydNRo&g|e zy___QE3%ukL(~6?#-t=JK};e_HU#q%;}@*}RPC-KZ8mR*1+^EJ4xACuRC5W_R0YnI z6|Y$}kKga|7*{y(EKRce^HYu>$T5Oz;LeC8nk@2h4cxiV4zSOUeS2aK)x+V;vsZ^d zIfA&yuJuuE^j#C#B<7k2kM_gAx_-y*MQ+%SO?pU?j|7XNl|Eaqx08$R6mG!~WVZ^VKD>ssuhPeEs+*LZiJEOAi8!sf zUSw)}B~%bKCu;G@uWc_h2w2JPzFL{v5MGj^nGo$zC5GQEk)7G*Pf96?@e8{#Df4hZ zvqnrwVxy1EhK-!piDg*>`Q+dAWH=skRtR{UX|DBuOXZ|+yX^9;0aMvm*S#@X;@6~@ zXy5f#p^zLumjD7b?itzQ0h1ellHx1eS5bn#3@2CMW*F32{G$O>iLU}n&V-0qn)4!`U}-Hc$DI2|0Af9qGg(1ONp>k6I$F1MNwtAXu%`R8 zpYxHs|EX6dA($m0Fg~wdX4CO}+a!u5?s!Sm%$M0tJfEnoN+u*uv9&8K)pxsdJr_So zHNt)f7L!aL*rEE8Rw_eO?5Lzs@H)>Iv#5~xJNZU?pZ-^X%N|u*1J-m=*1b5c;TTv z^7>@D>FwF)cJ69H?b?-#J~k}LaYakZ2LCJOqm;^G4?26 zLW6WaA+scnTybllwz$s6r^%**CWB5lza{@RNaY!(McX9E3i0Kkxykx&HyxVKk002%aF%wUPIy%i{SGTM#+ zfZ6xI2QGmDlLP=L09gq!b+@JCcG)Ps!3LV!@{mQ(OqwSu`rAB-(CPN+_LjSL*UZ)u z5Gq|4G}y$=uq(rDqZr}~*INsEh8Mi;lTxr!=0;^zE^=rt(Y?`Mm1>M}qL!oT7jBK) z`@JgBcS(`ju4JmZQuv1|PS7_%RAt!vUti=L}hh~x%S+_&`h&C|>lYLE(}V(=Uz>n0~6f!LeDUYVU(oh}04D#VoRz>JXNT zm7pWosVGC((iD`(Ot(=?Ui+837sS(PGg zKYtQ`Gz;$^bw_mTzonc>LQTT2`p+dP$5e2XE3b-vHhvl6w7xje*W5b57@-w9>Tx97 zP>hv1uKscvW)KCPuWD|MVGs@$NL*C0@>Sm7D0G$W)-n9I-R1Gx$%QwnuX--~hGDC^ zlkc)Xw5XAtmZ5F<%gTDE`|ZK4268-pqJNWI9?RckO}Bm(N^g4=xE^#@ra3gV;nk{a>QWjN%y*?u8$Y6Gx#Gt)l^L)N(NSoSEvx>4F=3mv a zgo^q0g0@goy9GTJX8&^H*2v1`Xl9p2BO^oCh~ex8rdY#oEXd8+Ds2~)8pcKkB7ILX z?r)5XQ|crSdf3s7r(WkDH~MlW>FVCmF&VcTi?sfjujx;Q$}`+HM$KhWC4on{P$O*o z`oxFkpX%tw`h-*LP3B&&a}@WYr~kVk&!8VEBFq;nenI(7=MyMzsIG7JZNPGv{AbGF zSldK{~PjF>sUXH!bYpWczKTi?inOuj*yn_})k;x5M54^Y! zA5&K+PI`UqT0{qb_f<#mp>5QKo3d6-Rf|9-u4n$fvuquNy&^d)M_UxJK4}z)k@zX6 zAvE4fyO^05c#J1t%*i5md6<$%+o4W3e>r{M;8aOeHJ?ONqWl~SBGKz?ZI4+^cd#9+ zpCdRDLNToE#bfx`pqMc#Y| zM^>>Gd%4l3x}K{QA0TDh%4&Bb#et>Aq;65MuxC6D=wsn3p<9TTLp%=-?uT8#KK$n# z4O?9|fIVv-}b_wLe^{Pz>=Qv=Qhus|YLT(B#MhCC&e5PN5 zA?;ED3Gr%w0zbM&$R&YmH}D_RzIf{hRHOKwc?ACn{&GtWEA_7=Pdeeb&4jGMSbUm6 zS)80D$l1(C>>Sd#>c{f;&X&o(h?EYxOP&*-IU){U&)e(bRNHV?imaC@9BgX{I0Jzu z!}WZSWEW#RVlXuy&sI;#k8>m8{s-@uCMVFO5?{>Z()v4cV=cQdb>iUBmYK5gQpBp5 z>Jf3mk^t0$4Tat>q)Nm=I}P*+l`-P`<^uu^5VW*J{#kG ztC`7@H_fjKn@1ik^YG6OEHZL*DE6_i6EM^u_R77@>~#UsATUSrocUTB#?uhDRsGK$ z?ynN*W9#pB%WX54fmbC>?l~zX@X_+&-b9pqp{;7D{=fQqw5<~5_m*1#+k&J*k}_?N zi%dxyFK3iXX)(9U_Fi|`rQva9zed!mv2&OIz6DOmk_U>ba0{Lm#_8Y;e7k4S@|k7% zmn5Dx;CSap*}{vgn=}iqOvbfw)DC~a1K*y!0t2%m5DVzm2Kb8;a>q_i^d%@b!yfSP zy=;qIgCBcJKvh$ld$}7_T4z*?`0T@{%!oH<)0=*lV~9 zt^XjZhxqHXO|LpHEu2f(>!9ePfX+|i97?=xuD4x~0hfUu%|J;vv6);U?GH7Vx%8ZT2;;HC98p+<_dp!#Izd=ra^9Pc?;tv-Ut zws#MFxaNdKI#L7G!E#?Pv0{*^P7%hy1*uD*-;P#n&gi1&_}nP?^X#_{s0ZQ?Ye7Hb zQT1Q0Zj39n1yI{Ln>Zt6F6K~_g;TYYKxuksHJya}gRML2-}o=~`m;Q0H2dospUiQ8 zC^bSaITk8XQTT?(HVTCVCOv~&UVONp{>guKaWO)86>cl3Zl~ywsJTox_@29#t!G50# z>%w;RrZ0=6Pv9a=*zi$-w#WN5`WKNe9~t>mi{-g-k*mVR#Kzg~?{$!7_M_Ws)?TsEHTf0C!b!ml4McdR zPHLD)u}JNwdJqAiuo^=cLBxq`o$(6JwH@xU-$t0-^!^a)ej)J^N;b&QavG1lK~YBZ zK)DmQxEvwB9M;m025%?4(ERw!Bk_FVnKwz@Kp zq-U_>y`CCdR7V!-6#s9AOC0WZjH2dU^55YFM8QZnh*{v(=HFACr`zp?fi56XyKbu! z9dOf5)HX!Uzr^>|*^bneucJw%N1PHsB-FXeB;PwdHU0I3!c&UH3~pqp2AEY@oUP$- zRHVD^4Dd`iU6_1q^kfpKT$P{P&mrQ5+ytdv0{Gxj&nazUXYV9UK7v8s&S+0?sr4;yO&>G7|xep%w<;nbp*Mr>Y} z$3QSOmyyh_NzLK*kD8XoX`}bxo3)wg1{d0Zth@D*JPIHoUre@xmO&lZ-JEq3xPCm) z0mzZ!Tz!8GakmjZ6|Od~$nH2*Y$ru|c{NYn*i@sz`vlN+YXb|qs@zyu-?}UBxBM^Y zc*0PDm5FxR{;DcPgr81KQ&W~*p$oJFpB2j1EDx(a5+6p$rB3dmkyqN6E>I@fnl)5q zl`)X9#++eEY@%tFqK22DM2-G_zu~KVp z59kb7bj?F<%*)Dm0_C6$wgY=uJYiIX2;A>G49t`T3-mX!j<@}ETAX6?AQ-wY zs&=ooM85t3R3%p9*}aFdoJQT_&6ATyt|~r;zT-~C9Q>0acLnA+$#g zvmBl*!D=1~8HZ6Aw0iAgp^on4ZsON%=Gs}vwbJ$2&Z=1LiPd!q05dNx#7@rQFsHK-B8x912R%UThfyL(v&;dx=j&b!S}G> z6r@kyu;xLDGUuO^yg0jaMqM$_@01!ga|RAE(5^Nlr(S0kTK^p;4*ot1Pv7N3RZKa0 zR5}7(v|7S}X-eV1lppZbi2xbg7I(I^3W4J(i4R_Vjl!y!wPW~IcBKuy<;3A*jYj!?)z#TjC8%F{fb6U&J7FTB_YV zbA40ar6*Pg4;iKwXt;uYRRMVQnnxD>T0r&Dqk`;UUParrve_r#|DUzq+8>#l%^di{DUV>dHc0V|Qh$~C^1pUZEurN~?kChPkD(m_ zd{S#8KMzU}h(Wd%i9PPa)#uJ9AM+)9lCrz)^%tivvNhqI4<6@l9k|<=ai*zsTare= z0W9nJa29=9<;Nvv{-;u0J~Xl%>%Q&giWN~kJGr%zU|>Y<+{wO*oMft~6U&H)*43v}&)wVe zmnF3Rd2dh{bDWAF726K-!%bopzSs(~C4LUcIMIx{(+NxfdpYHo1{BuYLu{_XmKN8& zd7Q`>V)nTkJn}x^)7O;@ynaGFeaKqkD_bI;nj3R!FBAu_h>G%R_*6sL)YOo=&uq}K zO5mJd0s)GJculY^*}rDodAzpN!9_3<`dE(?-5C~sg_&nJdY za3;8*mtdq`xkglgTXQx7P5XI4k@8gks3A&=>=e(Y^qZb%9uLK3MgIUIt`v&Pduf`J zO0uXz%Z2A_)Abjl&T~dlXUAA*Uo7IkaD)vR60$(D2l1vx4MJ%JtxW%)MspGR!$n*4l+W7vV{Jr>c`RhZBQjm=ctq1&v zlUi#HfEe3Ov%CK@(BWmb_-e=fcsbot2^Bwl*94f!S%NJ*F^Dr$F+<0 zRM#!(3)XBoi5oUC>45C^LiPRB74xtoeCz84IS=%B;H!@R=wJNo{HmjeBiueEa_PJV zsjQ9t!`)d?|H2OSW9OS9!3tFxtzJ_!Ei;{e_=WW8#k|oR!t1(rWEv`3SN);J4pi*c zFHuE*l{LcOr7lI`=l208-A*~04alN;TlXZb{);wweViI{O>F+*-t;w6Wimp@dYau6 z*@?wvJ~Bv>;m3}p(puAo7}pYfh@Ew7ZFP>$>21_IOa=2F-zM&PC5s94zHL3E60N;0 zFGZ}@Vo81E`X|&SQ{XSo1dGpDS)a7CsEM6gq}|vHSd~@2eTaYkL#}^7?eI(K;V?MH zv53@yMtZlm%S=QC<|i%sW4ciH_1;fvxzl#A=JEj3UFM!=FDlCs zhWRP?EZg@H5~!!rfcxLXAPtnqZJqVi^Ilcn*>XGPXtb`x(`Tz%fDLz{Na7T6U`0T) zB|TuNHGTqfI30f7tz*7=s?kMuLhecAKGb7=%+-)u7wCd*F8e2vU%&ARwarfnv)>4M z@$!5tC$F@q_0<3J^QypwA~BqBq> zhB-DqG)X&)2uEy|0^Ql375f&gQmwVP?M2@#)e`!0IEBS#FwS+^j6Ng%3ySG5u8Iz1 zoFqlLE`0@%MLOG2E0x*k`m(MU_HOe}b(HEAf_y}rPM%)^TH!j?C3The%~-`Wo{DWX z4bPKix^8TA_np*2rDDhUGC$vogryGDgL}tR-zD}$c|>6p_r{{0tPic0z7$VV8whZrXEvbQpa@``>r1v2k9DGm7HIP+Ateg#;~CsWz1*^lr)Tei8Ujk|x%m z+uLQcm9Um5BaBb(61EjS^}iD+|BVWQxHxjD$IFm_*ZHe-bPvj%mmPE=7cX5e8X}wO z_EgmS=MaGMW+_ z=$hSqPwpGbe%fHJkKv*10raW z`&VB~)Smy766I>S{6JU4>a?$&dGWV=u2_9G3GV%Z0#HYbPaP8(CV6z zvz&Qi*4WlgoET5y;NO)q(X2&au8ou|gm;^j6^&kwj_IdnZ^E#=UwZ4Y(|uKKB}fo3 zH~LgSL&0!T4Qw3fU-X8xQG*|Azm?k5L^mu>9JQ(uqsw8R>B^g5uNmFwL2dgAwq?%r z5vqc4idwtK$Qn2`M%(IRb=Pkjwz9AiF6xscD8F{xH#H#}BI?BF0|7sZ9R5s7seTtx zPvU<1lQ2f_0k4lfY=+dq$1tuO>ge* z38^6^<}GRLG;B=Y@buJm_Tk_n)IEhfn#)J;t&cOwK1kIssnCA7mfIcdV%=JYxmPlQ zALlKP>Ho4s`n>e!FdIA{&+EtsHaRK_dI<+*lGw4gndN36Xb6iW`yuVjL{|~V`(R0p z^5i+}iV=pDf?51G=-@!goW8$m!~HjYK)kH`SKAU9P(|k_ugB_FZ?4`$7XM_P)?1ot z#`;{mwp7wt@H0l8nw41)zFuw}F<7%G)wxF zY|0RW$G#OVB8V(dgzxAyA%y5F=Y^XN=x?@X&FMQ__27}lzB35F`|{y~t&(o6rA@Jj z5Fz?t%NU1d>p1QeYCV5w73HJk0*e!mh7Q7MLPJFr0>%3qua2e~DT2j?sGtNJ61a7r z%7rsJvz=RqZb32mGJI|LNH*3)vGZg5s_^D!(W&YrG*_is2`|pKWHwoIU4)eEg8bhh z5*Q5M346_&EijSVS-O>nhiVbo868<_dOOvxJ2V=2HJ;H3epX5H+_H4>eYtoQd;0Q* z(ax<@LlCx8UXJW`8>47gh;I_ePL+$o3hIX`KRs|a1hH#YDu3s$o9wA%32OKxgg`ZugOlqnnVuJSsNB*4gA3!5~%JEy-@7x!=G*gRMG+PF<@tr{42H)!k5{gNtE_yO`tNQ$At53mZ^s65HRiKO zLjDl@Eqm^DEpEzaEs;(ZO(sAr{^EhKGqmCiq1uDAqmV z{@8@^;-0ErEuPNxFBigI-25M&c-`7R0;NWn4cxa2>!kxP;WS&mUlkZ`jSj?81E*tnKme!Y4^;l?PmmXJ+j0Sf&!=gF!ICCkTv^6&blS8_ zkgF4YdF8+PH^-JT(3Vj~E)cXroVNIrJfh1N_PV6vsLIB2BiQY-5T?$0_P@yuv4_j< zEN=wE_DUQ5!+vDK2LC#NKKb(_y%q zkW>(>B*MmR{;MSqoFU^a8unkF_yE9oK$`v`hR9y5N1tfuK$gWUGkdT zTD{0!wMT{*{3bz3D4KtgNk&!&`*B!2+6+!n&uHIcFJ z$!JK~&yMM^ytfuMcUAU!x@Qf7E8bqL=3~5WrwwN6bmoa6H!ui;6U9a}azkTXpgY#o zg_+=+v#~2HDZj@8_wpix?2O7CEk0Sn>b|g85&b_cfXK>Sx&BMQLQN{dW7yfiN*ym}-pwQj>s>y2Nt&Q@z>!rXoPNS~uv}`5>=E}uLGv7DF z9ZHVSdij{m+{|js=c?&RyXQoI7WpAb`ezN~@AoUZ{pESwbcA)|ty#ad3St)oxLtg` zukKk{oJTMWIwzNPZur0R5D3@gP~=>4>SdXrR~4~}{#nYU=n*>|F-lioYv-1dtWUlGDPF{%jHb;ujGK!)d`(=ut3!mvn^Oo}06KjUEsOq&-N~ii)uh%}CNF&W&-{A!E7=16Lzc12P7 zspq!zEs~(Qes7O_JYTYW((NT!KjX@@O@ZA`u;{#=7B=$eg6i0vlmnV|KAsWL%A@%? zc?S*4BWyHzKbt)DOQ$iZQFT7J3@WaL)3$CEKDm!zKppFm5Ie;f ziQ+7NlgtXQNHeZb#71246eQ%?#SmE@>GX71R1~=|@Y%=4>P2<`8h6MmhameZEZJF< zg7z7`zrpuiWm!c-sgZ~mn@0;z%((?Q9Q^a2hZSm%Y`*PPeP?u<4*+pq&s~psV5fSu(PNI<<@@ ze$EJkCTnVS`*f~8G4rX_)002EbN4MieQ>#mE0fLFXZ-?hY)UTE!3_N z-7G>4?o#7+|CQ1tkr&rG*^>-oP267S`8H~ z`9z#;ImV0g$^atoe5!;zOa;E#o9l&N_ycTgUTwZs{d@=`*Vl6Y;cI z<{FMKnV$1Az0bf-OVxxf;XjGYRUO!=?DF=8$iu=qEDrMWQs;R;w~E#c%bw1WEt&bx zRgtPP)gH}|u+`YX`}B=XM=sk%l1f|cv`7N}J=G;%Pj0NErHOc!Y+|}Z#EI&S(9lp6 z^+Hl`r+|gBbBU7coh+9nkG~&kggU5;<@6^k6Sm%4Oe3jO&-~^ogWEiQ%+&)_eax4{ zklc_|Q;sjke0BH&#`cb@I7azNUazn7u&1ZO%IE!@I@pQGO`zwwqF>;z7P7~H3ZN9;s}A7$@()hbukPZVViD^ z@fh>a-X&HV>{s5!ru#p~3%}GZr!i-m6FzNiDE2kq(%!`P9b4mvL#v0lFywX4Sh0QV z&Awq-SScA#XmYnE@w6iMqS8>}??@YEmZ0G|W}&gc35#aAeg!7Cf8j>J#Wu74q%z7+ zTWPL&9Lx2exi_cp+avEIlTp#e$;mLeWtUKJyfw#&Fcl;Zy@|B{Q>sXbxb%A<1LEM= z>)cHM7ld~u^2d*Mvz-z-!yx+S{YS*~>h!GpcQ8eRG$6~@$cYFYcepfC@PY>vJs|ZT^pWZv4XvF4#Q{)oU|{QNvV-}$_{r52L3e%fF2tS4FHj!Sg1rv~&_ayJHfe1rh!CkzW+!$ma0M$VWp;6jEn7BWY`wjmP$(O5WH5Y9^ zAJBqc68$7wf8n_Jhd;TDCgxEeG;%n{S#mO0*B(1zF5O05`4Qv6n%?iauCM^QR8av~ z$Yc)NiBwSM3K5!d1fJFb&D-F1u5?DJ-1!C9B_ps&c4SN-FM9WMif28*#sLKos^G4e+BmZeU^|1YBbQmWT@Qq|LjC|Ql zc#sg+W0s-H^W_@i3QAx>fk|iTJ{H1mTMPi3WPva@N*y+%Mk`5NtUq?KSbGx+@g zHi&c(wH;NNq6ky(_fs}J-4IGwda0Gwi_=+^)@;U^uJ*UH{8QA^Rxv^l`zW09<>IEuUow-p_w1jzuKf>Zrvw;B!|Eq;^o zH(I2s<*{dJ!6{c_0)BW1Vc552OfFLY4oeX1+)G`LR3WxiRXA(3JUlHancyus!!d3X z_ebig2Zb(A@77gMwNnxJ3F+&8-1oROfxZo__^@=}wwl&)_a)kOzgx-aDb5%|JK_pA|ph26x20hL>sFkYOAx zc5F&EV0tnO6@ApTL9(@Y)_Q(IWbxDl6%(Y#iG>y6vBF@|^)*ZIC$nfSnu7o2{Bx#{ zhr0?1P0xglEe~!MvMf5}`#*F?dNr4T7Z%vPEo?PuVr}&Iv7F^<7Jn}*{y>jIv~R7G zFJ+zz0;#Sxw3-K>Bn9sL@5zaTihN?Jrq8^AmlM5ITte|6Ds}a53AJOsl;fjymLYrZ zwf2UNd3j7JG?}1|#=q?z*=Nt0FDtH0(h@=ZN2py++F55AAD$;*%bgE9(ggh^YI*m; zkh}4TwRK^fq|UNH?eug6EN3z4=*-7m!CwhB4o~fK>ySnPn3RP)w@|!2ZKD^uoLpk(at>sAGhvRGHVNE9Ik@$^kJ$t$zb3vlXMN@ za=%OW+WESDsRC#IeAQGu_;(0uxzXZfD=KSmH(&sHUi%NDSQ_HvVP#dHHeeq6caJj; zF{j`qBdW$E`!xEv;4sORd2)p$5d^u|?2m5h%A(-0*Uqp8T_+>(o{5ylX_`?1+gGe9 zWu=c>zvSR~n73x`8r7(O4Uaz1cQ!eGY`{C^oTl#pR`}C#{vyxik*k^$$Jb36Q9kz% zCiRKrFFpw@>zw41>)pMFzWCLZTa?x!Ds82#`VfFN(zOpq8KDaW*myq-88IEI6N!Mv z^>2D7G!I@yykgf$2To3qv(3!Neay_lH@dx^&m*cIYj(k>1 zipUAZG;WtDV4C-2fd0j%M7X_OXfH#grK5W3%AXa{Bm|l!L&dm2&unj+sU8eh!X{dS zm4E1x^b+BjP?eO=- z2oKSSGtCe@4hM3g2h`|hf8ea@o0l{riL`M}j3oNK9#-5zP)|OJjwcPxhSxn}j${nC ze3=ept}cI|ZERSf$km32ZqxF#{UL45YT`TU%pai*6(#@@{92g1a)BXKeenXmszh}Ka zsNbU>J@(I?)GJ~^C*ei8P*$c9JW6#b3S-|6)Z4WeDv=G1sl7#@i>6wg&*$HHC@C)a zQ=Qytxun@u6!~`msjp+iFXXW6;Ze`muy-t<>4ETGEHO(=8y)?L#!-;6v7OYl-++-1 z8TQL%uYBzi{^Jpr0wx1-zqx~|`-EwI>ZEy6f^Cfy#KG9gv7Tc&gGBZlyGzbx^lvzf zeTVdVfHT>Ta$rPDBH-_yD>H8d?@3G5+QWwBzVB!;`|O8lHb(%L zWmsGasG;ef4W99O`S7hTKk2L0@1U7JjYD4lt=(?SCmm+U@fS#pi#o6^9oX+&~JWH==2N~RXx;9Qv7f8Uk z`)5cr^(Fape}sF6Eu)JRo^9-zWTK;FTt=pHcCH`WTlhwbm%+^ZPswbR<7B79R&B}x zA6kq^GmLnmsY~*5O#Zwx*7)C`XJi&@^_@)NrFN?*a#As0mW@auucX`fjT4=md^PU6 zCa`-%XDiHv#?&8laj!h8bUYG1_{E4Q#Mp4wq8eyka{JL0$$^y-;f4Il%{1c1iB-X~ z?zw8F6z1@Kbo20;H1I!4f)14vTk>LoG#Bc$k^9q#gET#hzmEJr6wO#bO8A9x{GXIj zwEWL*^EW*lj(#3J$apm}q&2V!C?G5Ak)T8-axD^tt_yXiGhNO1t)L4Mg#`N#ba(WX z3R=O1fHBg+tKjA!Q)rCUDIIba>?mEo)CN$aUstE~5B>lMW^gobF;R>kq4TdllVa(m9O~NMiT+pxR~wC)(BG zgj@FGmFB&#Y|!3}wFK7rJA<*wiAh=gZV;#BK4lF3buC4msEOg;7IL97MN)vGpU*F! z!*)h{%Y~litQWk%AR|^~qU1&^R;l05DQ_z5Yc(geyhE^oXnW$AtZ!tGCE^HHSCP1d z*GR!tiTf}TfB%^dIY*$+uX^-SB7O&>yx~*MCwkq;&)7p;b%FJ)wWoXoxy2HiM)m#) z=qZ<9bd)C0oDKQ{d{LUEcE-u~^wITQh&*J4YG0hu05MQ;pB$>ge3pa-@tNV{@@!s= z!}wPbNgi#g+(<|2R@4<=oPDY{NqXc@|H3uL>v>&Ss)T*2ecJs(BMI{Ke-8|yn82lG zpEtHGlHIr&tmBxyD9+!|LvQuncyPrBvoI-(Y>e=96?iUz^_ebM9OEMmpAVe1vNqll zS~&+uFfQlUY^H2nh~jSqjKu0m`~)$h9s)L28};XhJ4mE%5a(rS2OkJ3N@dFw9%!U> zo3TGitTR1D#giVnzl1mzgkrNi`muPqymra#FF<6b)pz#Yw=tLW^4`QPMKOC}4Tf)l zY-;aAgFzmserGXL<8bbNNcPib=I*(M1*a2p!tvEMgJAa3@F2nxNOxqZq>k_=yYnJ8 z*p7(vb3u|n!$atDU|LWyvv3EG)Z;;SbSm-z#svo^1jFV;D|%q0URNIO?VgAZ>i?># z0*O}5iJ&Uo|DUSP8HpZ_4Ly#y#+zkkatrC*d*%~`7m{mrbFYSTX?6#srG6L~OB_CX zqVU*6T}sZ&+}yEO-*5k`%w-YI5aA)-UPpv8_a>S>YJUl`66ZBGTlj+4QEMJ+^t;FV zdr)AZL#QA#$0D<38!gc*J$RdF7)mQ)q@-y4z*G%C%d0iPe|0%>z69<{@7OY(#!n?F z(-I8pgMS6}r?~u5r1+A4cl%AtFBnw1;nD^ImfyKPRj+;#sdntR{JPuDMvB+>u{@8+RLo$yNBWSPFvX|m zo1VsZ=4`I;_76yW_EiyARVNLNUGv3ch|vGOmyWD%pRj>P_Y2*Gb96neHoUU8*3Zir zx>rU!0tuq!PRG2T(mC2=t7Bg~9zR%Z4Uc=Jj+Gu{P@$9_v^A1Lsq_`fsm|bA>`4j- zr$=Mrm-z<4Adk?KqQ+qiN^weeF5|(5v6XxGc4p}<}m1V2v zmGUX5qvhs(r>*Ukm6 z<1$pXVnG8bghdn!XY=d9Yt^WH`Pl43g1?G8D>IKK2aw{2=j^_I*|v3u1(L?i_!1C# z*?48AdqTQ9ckQ`Gj+d)@ClFfeo4cQBsGnJ$A2-=Xe$j4jYPITA8{=zqO`417;7BTMHy9pL6~Wt6OtEmg=jTuzm@ z2J1G|3P*bjWbT6dZ#|^3@?!&wt}do5Gv`h!kp^(F)AxR0wf|tA9Qm=q1@6Y}03QNb zWuvb~2o-~#buizZbg^tEc;&uv0i*>rsHuyUao=}hf8B%tGzinxxR_Cj*$L%eBygsqM`5$jngN>)d}{^Y|MwpmE6~o8T?gsNr9x3WyQE4(-%Utw^!h z|Ky&xTf>`3)RsUv=ofS_AUsDH{ z1ceyYcr8M5a*Y(lKvWCfKdvIkERnXDOwTUn;9Suphln7{=$a9Bj-~C_?*qoBlMY~X zX%cZ!nwL&=DaR9c-in~+$P{2wBK6X`ho-Ii=osA9u84bG?b#?NN`HoRhUsACZdz)_ zbcXJQ`W01C@_v#v&_N0=WMfa6^J{GS-1mVsBb9j5T_J)5B6Ru2YAQD|=j1*!zj2XW zj!r#hE-2q5Q9@1>rC}hc?|7y9iQ&bEGD<9UGQ_XX-Njb|_xb~t>9xbz z-7(mZlK!AJR3>kIsc3dfe8T-aw~cuBYYSy)^yh+K$s`GOK?vJ*hnYu@Nd!6;fdF8A ze3}JZjV_z+z8F3>X-|PmPIvbWoOx2b<|JowNIcX zeVYQc&MSQ!8J7{^a8->cwqnHBV9kAuA&_L2{55qvwD*Md%23N<)c8|KyrZQm*`DbE$i+XzW}Vs#_n75Dj4HN*Qc;Xd49z75Ht;r6?WvG`KGle_;gYkcefCCqHB|pP zz$|g0HE^1>sDFf|^7&&3#tb-~>AI#4Z;6IV3=2mVjL7=o$m=w#zr4HY^XJmMqF&%m zPucZzMyz5%oL&gMaQXj^CvA0?_1zsugW`K22tBf)6$?k8Ks0K2M9k5r-09T*ri{@= zlEfvulA(Hn)UGAvyP8Qn5*rOBs*f1RA*;PTOZ~@x3D<;1J^#wp>9E0B>fYYgwFPdu zeM*%B^0HbNxNY=x$`ejWYIV0h5_|DU&@MLCz6rX+6h~oAPPNs7go&eQSI)s(>@3bD zs+4)3`?@hC{LS}N`e>4AK;mxTfK~EDt;vj(lkTdPmNBp@PS00(HymGA%g2PsW~r0T zcuF0EKmSf3xrg)l-Bmc_o_H5~^;*yZ7%6M8GR`S`!T{#L9O&Xr6N&EFKCc9q=NCvp zqL(#~nn2>DVzU?7+$XE3pK4S=(6raxG&@ibN=5N8`4cbZZ$(18w*UC4@k7A{%rhV^lFYaa1jizccqG zRqO*}V?t=y6q8*PELWB>p28nA^BXKE5k7wH2qGP^^BR_*AEORgg0NW5j@9K)kx&6# zOG?N^qC8S&ws3B-UuK$dL1iEfGl&-P2%1+r`Wkt{Kn<2K`ecDo*yKFOF4t@K5q~9v z)Y7=xM%LokPPciN(9G<6PgjPY)y39%GJOXD>4hVTG}EwE>uUyyIt6wH&3V1bC$fnG zfgXMW1&?|vWRe&@`W%f4=>uUKL1n8 zm*18`$(HSZUrR|JMChYel(%UM7L1X+mT0khWDi0e41y6hPy~sH6}p&HKO}i<=1W`I zvz9Q{QpEPjMYBnF%ZT70a#rdcf0IejoI~0wnRVIp97j`Ar};X4!_kzeC{_Nd^y$5m zATIw`VyAzwJe`}*q3Yt2jZdT0UH1MhLu1sCeJ;PvCNYtyXhnO7$f$GviN&n4uK7nQ z6u($qot!_sd0!`SY(l??0>?3Eg`)c3)M;2oEeVhb#mMci=sCD&P{w=J*VL~Jh%(G1 zWA54v7QoTF;o%EG2qmF5O)82w$k?ppkqpA5=U-J)?2TeGD8xUrRnYu?A`d2VocZCt zLM9NO%I3w(?Avd<#ENL#-i|4z43ZOFPNnK$UIHs;{g;(ny`_$yW#8Bo<0&@O8n&rFxl~*nZ{tpCst7V}T7Bo^mg16LgTOnla#~W`@jL#g zhP`m$r4K3h67`NMhC)NaWHbOVMUd_7$QW^KU?;kcH2kyi3H^z??tTut+-I!#4V+o3 zDc*b!)=fHA8##h~@UH~NI>o;ryL9zXZTR;k)V<=Gj#~$i9~h2m77S_&A48`f1=OWM zgB5t0oDcFSnJ~5i%%HFGVbot%1DSrduC2*kHL<=#Qc_rOGTW6Qv7CCSaM(MJqNGwH z7lcY2HpltSya&Q^j}cWxQ9;Qm(#;JkiLtE@Iy!5GZL{tc9~6F2ioF-0^eNn!lfAQ` zRKR1G42@Vwz5xKDPyhQbKmY}zGLbN&QyRvbBDSe!{)`IH|FC_RiLreASBq@teWe*# z=8tN0gEOkp<`^4oR)B>inyBObx@|MSs?_wsSM}Gb340>aCxsO`1FWj%SiPlVtfJ}L z?l2?$w$$)>oi_*^y~HES)!_YK0Ld6O=fd5mAE_=KXH#^23(-X>t5#KuUY2aFcDk9* zc0Jk4Te9aK=9RUsMcBu|N6zq62t(<}{hzT8a30sGXMy5BV_n+4zslIWGA#oxL3Cj) zyRy{-pp8AutEW}oW&;P~>ay#QBq+g#YmCi_tUD7$YY!yk5CHICTrs!efLQ6ORC(xO zH&dw+m2O42c{tWi0G!a0nJ9q<9?mqRJh z&s58+pe;wLtR{SA$&hvaEWi=xhX6tBa-fzm$~D`z`NUL-UZ!GEW387cL2WpUi^}As zUg;1>C&K4NJW#1`TWGerx^nrrLUJPDZ0I&W}h$e+Eff-}-Qn%0ltaMcKq@|?QZc2g#G*TFZlR#dvkjiU_OSL{7E{b`p`La@>wZbd(!y ztaSW#0n2S0)@dP=W1=~Y5n7$tnUzF-nFc*(0U9cnfT+at%4yS9&yfQCj%tQmf@rOm z>>Rclrc_iWugr>64jCFjW_--IuJJz&5)`*(!#r}X`K$IUh*rDF5icT3WG4}M+3Ne| zl_|8%5(!s=5VhOpp+WN{f+mW$$Ety|V}WSG1$x&7fR7>196$;I1IOGpiIysv1=fvt zzWt6$q%7^Ly&$bIE_@iE1QSs+KN~R-CnP`t6>?VN!j`%f1UiwT+mQk5K`!P|lDe7N zAGR7j#1^AmuXYkQ=-inooI-O9Eg}s{lbM6pg8h6W1%QuIic9hx<(7F|nDzz7ER|Wv z;AkdAOh7l2om8|L=ELA&ZnoE05J^DNWNE3P^^b?_jQ~Jv%Waxv(?s@C0Ifj~ih(~U;z*IfKaUVpJWo3GY1%M~hEdG;1KC#j^ zuWb8NLno1m5>98A8bU!5q!9g1GAc})qVTZLE({O?N$fRg7y*alWuBk`Rf1$-t{&zZ z{OVRICz*-#VMb+q`jrU1Oi4*-H%oF{+DdblkZ6>{UN-1+X7zt!x`t*2!!^ag|MNTu z!0FTh_-G6e@nir>ZA)!iA2A#@#tA@W$XfsR$6f80h5@DjMDNu!u27U8=E)uaK#>o5 zE%Pup+)A_3N>@1zY8i&fjr{_UO3`UepH^13to#wh(jnUQGfZs?5LyoA&*GfJy6|aP z0DCL|h|?cNrD4mWZ3Il+OgeAL_!0BuunC=1WQoXIKnyJNFxS1zhm{jVDb4~+>V5!3 z98K1^G}^S9^m zLg7daJpovl9uM6dIQh!wMpd|G> zYB17muGFYBhS?E&DuQk%Y9nXXME-|`Ab_pqVQE@|R=bHzL*gcMwXEpln|ktN-2T` z2v9b|kN^=@I!a11E&TJcvXy4ni=!ly_Du=3y_&3A#85$7&dXL^J3d^A-dZ5RGt3D< zKrfX_plYYRwPx2G6U!Hk@M^JM&q${zLeDK_~|i*!R&|M zXiE$xKq*R7sqG#PVwarNL6R65Ni07T0|7$2pSk|6@N%z0^;+LpkC zpl<4mN~YoY>gZW1(aTg(waO`tOT)}WB!tJ<)cilT-mr@TrH@!JDm1t2+s{I}7J#u7 z+s%yyGZC>M^iq`r~ovl9WcW(3jz^!Gbx7D^7@Wlm4iS6LNEmsQ$0a!&I=cz5REx1Q`Z6L zmYew+L;$3^fKXgVj0cA+)Hl|>Go9mrD3fZHH?bu_==C7^XpsZI~965Y0w4}x&ySh zW6Dd*+_nPXs;7bkj`f2R!&q_?PqS2}N)%NaXf)@gaYQMOD6iSD7GI_yNa;6RYHnEp zkb-(K0=A&Z(W#c5szoOeO1L(!R!2FmY?iw61w=qG#cIH8cP}zVO=@fZ*J8>#Ks{j+ zO*8VYLLnFvBR&BL@;9Ctd1bgY50;da1(({5YMjuifi6=D^fFoLR^4TF7(kE$#+>|8 z>zZ9*(2fK`DUmAa#7y&1mT==y*N{@AiD+2}w~V4R+)Lt!2Q$f9H?tE&$6s61t)Gn* zVloJRM;l`J7^F-Jie2zUH3g%N5|pMgT%e=rKNC1k$b|vps!f8>p)fkBB!UuLnG^Wz zRcHY-mYrc~v*&K~)f}*nR-&mrAN0k95P7rq0003Dgl@yh3!8>A*SJn}wh?nI1V#c=A zR-0B=Ob~jRbO8W^PN?${S+ES14FO@=CG^Y6IspKn2o)VY>IJy)bgU9Q-GG4eNr-hS z7T_3MvDpMuXk%@A79e!eaHV*{|7k{+ESZ3glBp8fm^K?MO2a)xL^9IwPhRD?;j*&2 zUCBa9>O^B@*)pAA7_HAZBuXenR=2vq#&V^tgI7pm^7ONfKx@`|aO4I((JV~ykdp-B z9W8_%;Z&m<0z}NnOVgeOR1kF%8Nir0(PefdKaR%0*nNWpqCAdMj) zGv`&?03eYfw(J;7C!UO9Uk*zoB}%lX3{CSmsJ|`!b=Dl!h)ym-VBQKaPml`4xkawk`YuZLBd?2}yvYIvE-R0JllY za0m!$ahvt$#R|;yK)$jB#(+iK<~|y#*Q6~baX~uTj3N` z58m>V0M1(>V4-yYK9W&@q?%Nx(#_L;#ZMwBQH@IKOFk~P!*mjns8&z5md2%RUiS&X z3;D~Uvc{s#1Bs}cHjEWh)3>H$UOU14|80+=)U1uK2FT+yL8np*pw>UvMz<{(u>WACGN1O+LkPoq%rfjVvZ%Cge|jky;3D4p=D2yv8+u914O-Ho@5-GI`D0}`*ea6IKBGxnF=~r=^(X0 z^?x)1Cd4|f>u+l5ToNHmy=k?Lh$1>9HQp|S0a8$wigD4(co#ZgYzV4R4Txi;EOVi4 z%l#%wB#n9n2XD;@STim%)?@5k7*OiA+Pl!)<9ggVH#oO>dI@+{TPMv(k3RYE^MZ<7 zv;}AYDrGEz0fj46wN3%0$~zStK%h;9ZLN1;nPHg(F%de6q;~Blm9g5e3o)c%GX5h} z7~N+r3fIru##kjnx*kZtC5`;N^&kL|c)|n_zm$|FqBN%E!4{Q`haicW>o8z0&U8P9 z$pb5Bwk+tee|s0vpv@^@+%TrpFyigcB@q-bpfyY3M2d}S%2C6tyw^mD5@nkPF-B3D zED;p6zQe=Cl0{|M7;eYfi%t?;2AvhmcIZ#YF)R9{J`5npvHE+Kgl6io z^wD_{0dVwRn)n6h8Me;?9fw1|qb-qqY_3*({gq)0ZhtO`B1v@(xRIZRAb?;j2q52f zu-5pXUL*Pa_UDtbzi zl2o@j1i9smN;SOI91 zz-k$o6b7JHy@{ljV0&gRbsSvgR5b^On@zh7yPix$S>>Y}qe-uTCV7XNeAg{}Hm>XI zG%=fW_9?ro~iD(%Nw{+N)6j6zL{vVpifi_d3P5KEDL!EX*1A-y79SGETRgV;$sS*|}(#cNf zGy-t^U+LW|gf%TYg7S0pIF;e4Y9fgw*J;WQJJY;R$1H#+odRqq;--p}q&CBs*jjCE zNoMQ-_Z*V-i8Q8P{R+mI;CZBvJ@L+`6w(yAELw(b=oEUh;_h&;BmQ$;3b*esk z{VKyPCzm-V{)n+U(e*!~5o@vxNopEZSs8YC*Z6#X$y->i*OmqHS(*ScGX~mBk)jZS zk`Tt)BbR9syV7L#L*Vv7@U6AscF9nCB!g0EZke!^0uiofZK$Lk^P&I$_O5M7P8^2n z$aMbyU-u!qX|VRe*p>}FZDDs326#(NRmT|JjxI;r-Z2r8=q?@)elD#(U|D4GYjs8Q zOouGc!Jxm~>=F-raMl>tRDg=FShK4>4J@ERs#g0gMB<^l%D9|83L%gnlf@Z+a)Z!4>o0(U{&32?9oy31kOcTSmN!7d*;??NKK@>*- z_8`iZE2=)K_IT7R|0|bu$gl;Hh@NL1h{2olx2HjOfAE`wVA+H~9D53uMP_d)fgx7_ zNOhH>0svKPowF;M+5AT{x_r+5-(UXM)BpGMp+Tq(tB{LFmA#{GMXhM(mW+AwE4O)o zZnxKD|F_uI4VIxaS${)L+^nEj#P!UWV4LN6#Q&U0rpmiVps`h zQ>iWm*lz3zG+0OgKnzzgFY?C0}}tSQurm%(p` z0s8^23xXZzA(+KmYzb(J_Fu{r5Z+oK1-pY43ctql@(KD_W||_#@RH@=i=(0}!G`pJ zTU&-OxmL(dMBdZlCCp1Wh@M-ML*l;8pB^0uJKvJvE(Op-*~)k%9=J%g+MGK|eKjI= zp#L(iD4jrPKXz+q0qBYMflBV+_SS>)I~`!?WM#}eGC5{&s|ygK#Y^bH!)7=pIL|kR zITjFQFO>Loj8K^R)+no;jpQ8oi3?CtScI zwj+xsLHSzxu}<(5Q9A)Er3H94aGftcR|E7f9;k)Aa;ir>fco!o5prDF@`}X(e(^>C zkeb}3K+bR17NVT(D5IcubZPkHlf$-OmGI`Mnv}e%9!?yxPwnNf{F{zwE`_jjgS@G8 z5t~uq)3qe^c>vjmM6r*pec2#6S8zzRe+-m44Iv6RI7sSq#&8XAZb$!H!rw|0dhcpsSp7Z3|;5P zor51#5@yb!npByS<~CA|J|zY#%;dP4FE_f%aLJ6klf3b+(j|*jA5bi1fb@Z|1pwbX z4JuU=Db5y?nnC$%R%d^A44`{;!UyCk6>S6n*{qf+po3^w ziV%eX$LOyHfEBPQ`RYvOUs8xzmk|L*yAuc{zf?nJ6ai#oNqBE|$B4%jN!=NzB8Tq@ zwp9Qs<)K4TrNe94bRFx#k*@eh>9v#rv{hg<1}*l96wTchOJukGDQEC1rZ<0jrP3Fo zN3RB;Oe~0NT)}GbOG&KRgGrP@Bjy23LC(brF4cRGI?pMJK+YSZpk%Tu>Y;XzR0%!~ zES-#>?~Ik;;{h^uA%S%N%Ak3oBE06_A1oF}Z0y{@O}#tO;PH3V?x>|6_wFMcf1PtN z!%dyGPo@C50aInNC*!L-)R3-Is%Wp*GJIEbR*ImS9@@vsjJ!s+28$F zb1U>|LX}&@SV`RkT0NyY8Hf9=cewK|7-knvM2m7n0rcC-h?q|XoS07?lX3Z?6FKv8wx!i~Btt0RwWifBFtTae!-agI+gwyxZ_Eivd1lLOvl< zr?7B;)g78!DsXKDIMMYTe$n6jjZNpg9ns&V0QYl0=X!1oa6h*K+|T{o3UEL7b1T68 d+|RY1{{aLnOZ_({f!qK9002ovPDHLkV1jM9>#G0& literal 0 HcmV?d00001 diff --git a/service.watchedlist/lib/__init__.py b/service.watchedlist/lib/__init__.py new file mode 100644 index 0000000000..b93054b3ec --- /dev/null +++ b/service.watchedlist/lib/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/service.watchedlist/lib/watchedlist/__init__.py b/service.watchedlist/lib/watchedlist/__init__.py new file mode 100644 index 0000000000..b93054b3ec --- /dev/null +++ b/service.watchedlist/lib/watchedlist/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/service.watchedlist/lib/watchedlist/api.py b/service.watchedlist/lib/watchedlist/api.py new file mode 100644 index 0000000000..070c8f567e --- /dev/null +++ b/service.watchedlist/lib/watchedlist/api.py @@ -0,0 +1,105 @@ +""" +This file contains API functions for the usage of WatchedList with other Kodi addons +""" + +import xbmc +import time +from watchedlist import WatchedList +import utils + +def change_watched_movie(imdb_id, playCount=1, name=''): + """ + Change the watched state of one movie in the WL database + + Args: + imdb_id: ID of the movie in the imdb. Format: Integer, numbers after 'tt0012345' + playCount: Unsigned Integer + name: Title of the movie (optional) + + Returns: + Error Code (0=No error) + """ + + if len(name) == 0: + name = 'tt%d' % imdb_id + if playCount == 0: + lastPlayed=0 + else: + lastPlayed=int(time.time()) + row_xbmc = [imdb_id, 0, 0, lastPlayed, playCount, name, 0] # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + saveanyway = True + commit = True + lastChange = lastPlayed + with WatchedList(True) as WL: + if WL.get_watched_wl(1): # Read the WL database + utils.showNotification(utils.getString(32102), utils.getString(32602), xbmc.LOGERROR) + return 1 + WL.wl_update_media('movie', row_xbmc, saveanyway, commit, lastChange) + return 0 + +def change_watched_episode(tvdb_id, season, episode, playCount=1, name=''): + """ + Change the watched state of one episode in the WL database + + Args: + tvdb_id: ID of the tv show in tvdb. Format: Integer + season: Number of the season (Integer) within the show + episode: Number of the episode within the season (Integer) + playCount: Unsigned Integer + name: Name of the Episode (optional) + + Returns: + Error Code (0=No error) + """ + + if len(name) == 0: + name = 'tvdb%d S%02dE%02d' % (tvdb_id, season, episode) + if playCount == 0: + lastPlayed=0 + else: + lastPlayed=int(time.time()) + row_xbmc = [tvdb_id, season, episode, lastPlayed, playCount, name, 0] # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + saveanyway = True + commit = True + lastChange = lastPlayed + with WatchedList(True) as WL: + if WL.get_watched_wl(1): # Read the WL database + utils.showNotification(utils.getString(32102), utils.getString(32602), xbmc.LOGERROR) + return 1 + WL.wl_update_media('episode', row_xbmc, saveanyway, commit, lastChange) + return 0 + +def change_watched_episodes(tvdb_id, seasons, episodes, playCount, names=[]): + """ + Change the watched state of multiple episodes of one tv show in the WL database + + Args: + tvdb_id: ID of the tv show in tvdb. Format: Integer + seasons: Number of the seasons for the affected episodes (list of Integers) + episodes: Number of the episodes within the seasons (list of Integers, same length as seasons list). + playCount: Unsigned Integer + names: Names of the Episodes (optional, list of strings, same length as lists above) + + Returns: + Error Code (0=No error) + """ + + if playCount == 0: + lastPlayed=0 + else: + lastPlayed=int(time.time()) + with WatchedList(True) as WL: + if WL.get_watched_wl(1): # Read the WL database + utils.showNotification(utils.getString(32102), utils.getString(32602), xbmc.LOGERROR) + return 1 + for i in range(len(seasons)): + row_xbmc_i = [tvdb_id, seasons[i], episodes[i], lastPlayed, playCount, names[i], 0] # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + saveanyway = True + if i == len(seasons)-1: + commit = True # Only commit the last row to reduce file access to the database + else: + commit = False + lastChange = lastPlayed + + WL.wl_update_media('episode', row_xbmc_i, saveanyway, commit, lastChange) + return 0 \ No newline at end of file diff --git a/service.watchedlist/lib/watchedlist/utils.py b/service.watchedlist/lib/watchedlist/utils.py new file mode 100644 index 0000000000..6da514ec82 --- /dev/null +++ b/service.watchedlist/lib/watchedlist/utils.py @@ -0,0 +1,240 @@ +""" +This file contains additional utility functions +""" +import xbmc, xbmcgui, xbmcvfs, xbmcaddon +import os +import time +import sys +import buggalo +import re +import random + +_addon_id = u'service.watchedlist' +_Addon = xbmcaddon.Addon(_addon_id) + +# XBMC-JSON +if sys.version_info < (2, 7): + import simplejson +else: + import json as simplejson + + +def data_dir(): + """"get user data directory of this addon. + according to http://wiki.xbmc.org/index.php?title=Add-on_Rules#Requirements_for_scripts_and_plugins + """ + _datapath = xbmc.translatePath( _Addon.getAddonInfo('profile') ).decode('utf-8') + if not xbmcvfs.exists(_datapath): + xbmcvfs.mkdir(_datapath) + return _datapath + +def addon_dir(): + """"get source directory of this addon. + according to http://wiki.xbmc.org/index.php?title=Add-on_Rules#Requirements_for_scripts_and_plugins + """ + return _Addon.getAddonInfo('path').decode('utf-8') + +def log(message,loglevel=xbmc.LOGNOTICE): + """"save message to xbmc.log. + + Args: + message: has to be unicode, http://wiki.xbmc.org/index.php?title=Add-on_unicode_paths#Logging + loglevel: xbmc.LOGDEBUG, xbmc.LOGINFO, xbmc.LOGNOTICE, xbmc.LOGWARNING, xbmc.LOGERROR, xbmc.LOGFATAL + """ + xbmc.log(encode(_addon_id + u": " + message), level=loglevel) + + +def showNotification(title,message,loglevel, time=4000): + """Show Notification + + Args: + title: has to be unicode + message: has to be unicode + loglevel: Log-level of the message (equivalent to xbmc.LOGDEBUG ... xbmc.LOGFATAL) + time: Time that the message is beeing displayed + """ + # Check log level + if getSetting('verbosity') == '1' and loglevel < xbmc.LOGINFO: + return # setting "only infos" + elif getSetting('verbosity') == '2' and loglevel < xbmc.LOGWARNING: + return # setting "only warnings" + elif getSetting('verbosity') == '3' and loglevel < xbmc.LOGERROR: + return # setting "only errors" + elif getSetting('verbosity') == '4': + return # setting "None" + _addoniconpath = os.path.join(addon_dir(),"icon.png") + log(u'Notification. %s: %s' % (title, message) ) + if xbmc.Player().isPlaying() == False: # do not show the notification, if a video is being played. + xbmcgui.Dialog().notification(title, message, _addoniconpath, time) + +def setSetting(name,value): + _Addon.setSetting(name,value) + +def getSetting(name): + return _Addon.getSetting(name) + +def getString(string_id): + # return a localized string from resources/language/*.po + # The returned string is unicode + return _Addon.getLocalizedString(string_id) + +def encode(string): + return string.encode('UTF-8','replace') + +def decode(string): + return string.decode('UTF-8') + + +def footprint(): + """Print settings to log file""" + log(u'data_dir() = %s' % data_dir(), xbmc.LOGDEBUG) + log(u'addon_dir() = %s' % addon_dir(), xbmc.LOGDEBUG) + log(u'verbosity = %s' % getSetting('verbosity'), xbmc.LOGDEBUG) + log(u'w_movies = %s' % getSetting('w_movies'), xbmc.LOGDEBUG) + log(u'w_episodes = %s' % getSetting('w_episodes'), xbmc.LOGDEBUG) + log(u'autostart = %s' % getSetting('autostart'), xbmc.LOGDEBUG) + log(u'delay = %s' % getSetting('delay'), xbmc.LOGDEBUG) + log(u'starttype = %s' % getSetting('starttype'), xbmc.LOGDEBUG) + log(u'interval = %s' % getSetting('interval'), xbmc.LOGDEBUG) + log(u'watch_user = %s' % getSetting('watch_user'), xbmc.LOGDEBUG) + log(u'progressdialog = %s' % getSetting('progressdialog'), xbmc.LOGDEBUG) + log(u'db_format = %s' % getSetting('db_format'), xbmc.LOGDEBUG) + log(u'extdb = %s' % getSetting('extdb'), xbmc.LOGDEBUG) + log(u'dbpath = %s' % getSetting('dbpath'), xbmc.LOGDEBUG) + log(u'dbfilename = %s' % getSetting('dbfilename'), xbmc.LOGDEBUG) + log(u'dbbackup = %s' % getSetting('dbbackup'), xbmc.LOGDEBUG) + log(u'dropbox_enabled = %s' % getSetting('dropbox_enabled'), xbmc.LOGDEBUG) + log(u'dropbox_apikey = %s' % getSetting('dropbox_apikey'), xbmc.LOGDEBUG) + log(u'mysql_server = %s' % getSetting('mysql_server'), xbmc.LOGDEBUG) + log(u'mysql_port = %s' % getSetting('mysql_port'), xbmc.LOGDEBUG) + log(u'mysql_user = %s' % getSetting('mysql_user'), xbmc.LOGDEBUG) + log(u'mysql_pass = %s' % getSetting('mysql_pass'), xbmc.LOGDEBUG) + +def sqlDateTimeToTimeStamp(sqlDateTime): + """Convert SQLite DateTime to Unix Timestamp + + Args: + sqlDateTime: E.g. "2013-05-10 21:23:24" + Returns: + timestamp: E.g. 1368213804 + """ + # sqlDateTime is a string (only from SQLite db. Mysql returns object) + if sqlDateTime == '': + return 0 # NULL timestamp + else: + # the usage of strptime produces the error "Failed to import _strptime because the import lock is held by another thread." + # to solve this, in case of error try again after random time + try: + for i in range(5): + try: + return int(time.mktime(time.strptime(sqlDateTime,"%Y-%m-%d %H:%M:%S"))) + except: + xbmc.wait( random.randint(200, 500) ) + except: + return 0 # error, but timestamp=0 works in the addon + +def TimeStamptosqlDateTime(TimeStamp): + """Convert Unix Timestamp to SQLite DateTime + + Args: + timestamp: E.g. 1368213804 + + Returns: + sqlDateTime: E.g. "2013-05-10 21:23:24" + """ + if TimeStamp == 0: + return "" + else: + return time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(TimeStamp)) + +def executeJSON(request): + """Execute JSON-RPC Command + + Args: + request: Dictionary with JSON-RPC Commands + """ + rpccmd = simplejson.dumps(request) # create string from dict + json_query = xbmc.executeJSONRPC(rpccmd) + json_query = unicode(json_query, 'utf-8', errors='ignore') + json_response = simplejson.loads(json_query) + # in case of exception this will be sent + buggalo.addExtraData('len(json_query)',len(json_query)); + buggalo.addExtraData('len(json_response)', len(json_response)); + return json_response + +def buggalo_extradata_settings(): + """"add extradata to buggalo""" + + buggalo.addExtraData('data_dir', data_dir()); + buggalo.addExtraData('addon_dir', addon_dir()); + buggalo.addExtraData('setting_verbosity', getSetting("verbosity")); + buggalo.addExtraData('setting_w_movies', getSetting("w_movies")); + buggalo.addExtraData('setting_w_episodes', getSetting("w_episodes")); + buggalo.addExtraData('setting_autostart', getSetting("autostart")); + buggalo.addExtraData('setting_delay', getSetting("delay")); + buggalo.addExtraData('setting_starttype', getSetting("starttype")); + buggalo.addExtraData('setting_interval', getSetting("interval")); + buggalo.addExtraData('setting_progressdialog', getSetting("progressdialog")); + buggalo.addExtraData('setting_watch_user', getSetting("watch_user")); + buggalo.addExtraData('setting_extdb', getSetting("extdb")); + buggalo.addExtraData('setting_dbpath', getSetting("dbpath")); + buggalo.addExtraData('setting_dbfilename', getSetting("dbfilename")); + buggalo.addExtraData('setting_dbbackup', getSetting("dbbackup")); + buggalo.addExtraData('setting_db_format', getSetting("db_format")); + buggalo.addExtraData('setting_mysql_server', getSetting("mysql_server")); + buggalo.addExtraData('setting_mysql_port', getSetting("mysql_port")); + buggalo.addExtraData('setting_mysql_db', getSetting("mysql_db")); + + +def translateSMB(path): + """"translate "smb://..." to "\\..." in windows. Don't change other paths + + Args: + path: Path to File in Linux notation + + Returns: + path: Path to File in Windows notation + """ + if os.sep == '\\': # windows os + res_smb = re.compile('smb://(\w+)/(.+)').findall(path) + if len(res_smb) == 0: + # path is not smb://... + return path + else: + # create smb path + return '\\\\'+res_smb[0][0]+'\\'+res_smb[0][1].replace('/', '\\') + else: + # linux os. Path with smb:// is correct, but can not be accessed with normal python file access + return path + +def fileaccessmode(path): + """"determine file access mode in case of smb share no direct access possible + + Args: + path: Path to File + + Returns: + copy_mode: Mode of file access: 'copy' or 'normal' + """ + + res_smb = re.compile('smb://(\w+)/(.+)').findall(path) + res_nw = re.compile('(\w+)://(.*?)').findall(path) + if os.sep == '\\': # windows os + if len(res_smb) != 0: + # smb accessable in windows + return 'normal' + elif len(res_nw) != 0: + # path is ftp or nfs network + return 'copy' + else: + # path is not smb://... + return 'normal' + else: + # linux os. + if len(res_nw) != 0: + # Path with smb:// or ftp:// is correct, but can not be accessed with normal python file access + # use virtual file system + return 'copy' + else: + # "normal" path + return 'normal' diff --git a/service.watchedlist/lib/watchedlist/watchedlist.py b/service.watchedlist/lib/watchedlist/watchedlist.py new file mode 100644 index 0000000000..fe4d1997dd --- /dev/null +++ b/service.watchedlist/lib/watchedlist/watchedlist.py @@ -0,0 +1,1695 @@ +""" +This file contains the class of the addon + +Settings for this addon: +w_movies + 'true', 'false': save watched state of movies +w_episodes + 'true', 'false': save watched state of movies +autostart +delay + delay after startup in minutes: '0', '5', '10', ... +starttype + '0' = No autostart + '1' = One Execution after xbmc start + '2' = Periodic start of the addon +interval +watch_user +progressdialog +verbosity + '0' = All messages (debug) + '1' = Only info / notification + '2' = Only warnings + '3' = Only errors + '4' = No Messages +db_format + '0' = SQLite File + '1' = MYSQL Server +drobox_enabled +extdb + 'true', 'false': Use external database file +dbpath + String: Specify path to external database file +dbfilename +dbbackupcount +mysql_server +mysql_port +mysql_db +mysql_user +mysql_pass +""" + +import xbmc, xbmcgui, xbmcaddon, xbmcvfs +import re +import sys, os +import unicodedata +import time +import sqlite3 +import mysql.connector + +import buggalo +buggalo.GMAIL_RECIPIENT = "msahadl60@gmail.com" +# buggalo.SUBMIT_URL = 'http://msahadl.ms.funpic.de/buggalo-web/submit.php' + +import utils + +try: + import dropbox + from dropbox.exceptions import ApiError as DropboxApiError + DROPBOX_APP_KEY = 'bhd2v8hgsmqwcgt' + DROPBOX_APP_SECRET = 't2cepoevjqyubnd' + DROPBOX_ENABLED = True +except: + DROPBOX_ENABLED = False + if utils.getSetting("dropbox_enabled") == 'true': + utils.showNotification(utils.getString(32708), utils.getString(32720), xbmc.LOGWARNING) +if utils.getSetting('dbbackupcount') != '0': + import zipfile + import datetime + + +QUERY_MV_INSERT_SQLITE = 'INSERT OR IGNORE INTO movie_watched (idMovieImdb,playCount,lastChange,lastPlayed,title) VALUES (?, ?, ?, ?, ?)' +QUERY_MV_INSERT_MYSQL = 'INSERT IGNORE INTO movie_watched (idMovieImdb,playCount,lastChange,lastPlayed,title) VALUES (%s, %s, FROM_UNIXTIME(%s), FROM_UNIXTIME(%s), %s)' +QUERY_EP_INSERT_SQLITE = 'INSERT OR IGNORE INTO episode_watched (idShow,season,episode,playCount,lastChange,lastPlayed) VALUES (?, ?, ?, ?, ?, ?)' +QUERY_EP_INSERT_MYSQL = 'INSERT IGNORE INTO episode_watched (idShow,season,episode,playCount,lastChange,lastPlayed) VALUES (%s, %s, %s, %s, FROM_UNIXTIME(%s), FROM_UNIXTIME(%s))' + +QUERY_MV_UPDATE_SQLITE = 'UPDATE movie_watched SET playCount = ?, lastplayed = ?, lastChange = ? WHERE idMovieImdb LIKE ?' +QUERY_MV_UPDATE_MYSQL = 'UPDATE movie_watched SET playCount = %s, lastplayed = FROM_UNIXTIME(%s), lastChange = FROM_UNIXTIME(%s) WHERE idMovieImdb LIKE %s' +QUERY_EP_UPDATE_SQLITE = 'UPDATE episode_watched SET playCount = ?, lastPlayed = ?, lastChange = ? WHERE idShow LIKE ? AND season LIKE ? AND episode LIKE ?' +QUERY_EP_UPDATE_MYSQL = 'UPDATE episode_watched SET playCount = %s, lastPlayed = FROM_UNIXTIME(%s), lastChange = FROM_UNIXTIME(%s) WHERE idShow LIKE %s AND season LIKE %s AND episode LIKE %s' + +# Queries to create tables for movies ("mv"), episodes ("ep") and series ("ss") for sqlite and mysql +QUERY_CREATE_MV_SQLITE = "CREATE TABLE IF NOT EXISTS movie_watched (idMovieImdb INTEGER PRIMARY KEY,playCount INTEGER,lastChange INTEGER,lastPlayed INTEGER,title TEXT)" +QUERY_CREATE_EP_SQLITE = "CREATE TABLE IF NOT EXISTS episode_watched (idShow INTEGER, season INTEGER, episode INTEGER, playCount INTEGER,lastChange INTEGER,lastPlayed INTEGER, PRIMARY KEY (idShow, season, episode))" +QUERY_CREATE_SS_SQLITE = "CREATE TABLE IF NOT EXISTS tvshows (idShow INTEGER, title TEXT, PRIMARY KEY (idShow))" + +QUERY_CREATE_MV_MYSQL = ("CREATE TABLE IF NOT EXISTS `movie_watched` (" + "`idMovieImdb` int unsigned NOT NULL," + "`playCount` tinyint unsigned DEFAULT NULL," + "`lastChange` timestamp NULL DEFAULT NULL," + "`lastPlayed` timestamp NULL DEFAULT NULL," + "`title` text," + "PRIMARY KEY (`idMovieImdb`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;") +QUERY_CREATE_EP_MYSQL = ("CREATE TABLE IF NOT EXISTS `episode_watched` (" + "`idShow` int unsigned NOT NULL DEFAULT '0'," + "`season` smallint unsigned NOT NULL DEFAULT '0'," + "`episode` smallint unsigned NOT NULL DEFAULT '0'," + "`playCount` tinyint unsigned DEFAULT NULL," + "`lastChange` timestamp NULL DEFAULT NULL," + "`lastPlayed` timestamp NULL DEFAULT NULL," + "PRIMARY KEY (`idShow`,`season`,`episode`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;") +QUERY_CREATE_SS_MYSQL = ("CREATE TABLE IF NOT EXISTS `tvshows` (" + "`idShow` int unsigned NOT NULL," + "`title` text," + "PRIMARY KEY (`idShow`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;") + +# Queries for clearing data from the tables +QUERY_CLEAR_MV_SQLITE = "DELETE FROM movie_watched;" +QUERY_CLEAR_EP_SQLITE = "DELETE FROM episode_watched;" + +# Queries for selecting all table entries +QUERY_SELECT_MV_SQLITE = "SELECT idMovieImdb, lastPlayed, playCount, title, lastChange FROM movie_watched ORDER BY title" +QUERY_SELECT_MV_MYSQL = "SELECT `idMovieImdb`, UNIX_TIMESTAMP(`lastPlayed`), `playCount`, `title`, UNIX_TIMESTAMP(`lastChange`) FROM `movie_watched` ORDER BY `title`" +QUERY_SELECT_EP_SQLITE = "SELECT idShow, season, episode, lastPlayed, playCount, lastChange FROM episode_watched ORDER BY idShow, season, episode" +QUERY_SELECT_EP_MYSQL = "SELECT `idShow`, `season`, `episode`, UNIX_TIMESTAMP(`lastPlayed`), `playCount`, UNIX_TIMESTAMP(`lastChange`) FROM `episode_watched` ORDER BY `idShow`, `season`, `episode`" + +# Queries for inserting tv series +QUERY_INSERT_SS_SQLITE = "INSERT OR IGNORE INTO tvshows (idShow,title) VALUES (?, ?)" +QUERY_INSERT_SS_MYSQL = "INSERT IGNORE INTO tvshows (idShow,title) VALUES (%s, %s)" + +class WatchedList: + """ + Main class of the add-on + """ + def __init__(self, externalcall=False): + """ + Initialize Class, default values for all class variables + + Args: + externalcall: Flag if the class was created for use with another Addon (via the API). + In this case some features will be deactivated for speed purpose + """ + self.watchedmovielist_wl = list([]) # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6lastChange + self.watchedepisodelist_wl = list([]) # 0tvdbnumber, 1season, 2episode, 3lastplayed, 4playcount, 5empty, 6lastChange + + self.watchedmovielist_xbmc = list([]) # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + self.watchedepisodelist_xbmc = list([]) # 0tvdbnumber, 1season, 2episode, 3lastplayed, 4playcount, 5name, 6empty, 7episodeid + + self.tvshows = {} # dict: key=xbmcid, value=[imdbnumber, showname] + self.tvshownames = {} #dict: key=imdbnumber, value=showname + + self.sqlcon_wl = 0 + self.sqlcursor_wl = 0 + self.sqlcon_db = 0 + self.sqlcursor_db = 0 + + self.db_method = 'file' # either 'file' or 'mysql' + + # flag to remember copying the databasefile if requested + self.dbbackupdone = False + + self.watch_user_changes_count = 0 + + # normal access of files or access over the xbmc virtual file system (on unix) + self.dbfileaccess = 'normal' + + self.dbpath = '' + self.dbdirectory = '' + self.dropbox_path = None + self.downloaded_dropbox_timestamp = 0 + + # monitor for shutdown detection + self.monitor = xbmc.Monitor() + + if externalcall: + # No Dropbox for external API calls + self.downloaded_dropbox_timestamp = float('inf') + # No database backup + self.dbbackupdone = True + + def __del__(self): + ''' Cleanup db when object destroyed ''' + self.close_db(3) # changes have to be committed before + + def __enter__(self): # for using the class in a with-statement + return self + + def __exit__(self, exc_type, exc_value, traceback): # for using the class in a with-statement + self.close_db(3) # changes have to be committed before + + def runProgram(self): + """ + entry point for automatic start. + Main function to call other functions + infinite loop for periodic database update + + Returns: + return codes: + 0 success + 3 error/exit + """ + try: + # workaround to disable autostart, if requested + if utils.getSetting("autostart") == 'false': + return 0 + + utils.buggalo_extradata_settings() + utils.footprint() + + # wait the delay time after startup + delaytime = float(utils.getSetting("delay")) * 60.0 # in seconds + utils.log(u'Delay time before execution: %d seconds' % delaytime, xbmc.LOGDEBUG) + utils.showNotification(utils.getString(32101), utils.getString(32004)%float(utils.getSetting("delay")), xbmc.LOGINFO) + if self.monitor.waitForAbort(1.0+delaytime): # wait at least one second (zero waiting time waits infinitely) + return 0 + + # load all databases + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + utils.showNotification(utils.getString(32102), utils.getString(32601), xbmc.LOGERROR) + return 3 + if len(self.tvshownames) == 0: self.sync_tvshows() + if len(self.watchedmovielist_wl) == 0: self.get_watched_wl(1) + if len(self.watchedmovielist_xbmc) == 0: self.get_watched_xbmc(1) + executioncount = 0 + idletime = 0 + + if utils.getSetting("watch_user") == 'true': utils.showNotification(utils.getString(32101), utils.getString(32005), xbmc.LOGINFO) + + # handle the periodic execution + while float(utils.getSetting("starttype")) > 0 or utils.getSetting("watch_user") == 'true': + starttime = time.time() + # determine sleeptime before next full watched-database update + if utils.getSetting("starttype") == '1' and executioncount == 0: # one execution after startup + sleeptime = 0 + elif utils.getSetting("starttype") == '2': # periodic execution + if executioncount == 0: # with periodic execution, one update after startup and then periodic + sleeptime = 0 + else: + sleeptime = float(utils.getSetting("interval")) * 3600 # wait interval until next startup in [seconds] + # wait and then update again + utils.log(u'wait %d seconds until next update' % sleeptime) + utils.showNotification(utils.getString(32101), utils.getString(32003)%(sleeptime/3600), xbmc.LOGINFO) + else: # no autostart, only watch user + sleeptime = 3600 # arbitrary time for infinite loop + + # sleep the requested time and watch user changes + while 1: + if self.monitor.abortRequested(): return 1 + # check if user changes arrived + if utils.getSetting("watch_user") == 'true': + idletime_old = idletime + idletime = xbmc.getGlobalIdleTime() # xbmc idletime in seconds + # check if user could have made changes and process these changes to the wl database + self.watch_user_changes(idletime_old, idletime) + # check if time for update arrived + if time.time() > starttime + sleeptime: + break + xbmc.sleep(1000) # wait 1 second until next check if xbmc terminates and user made changes + # perform full update + if utils.getSetting("starttype") == '1' and executioncount == 0 or utils.getSetting("starttype") == '2': + self.runUpdate(False) + executioncount += 1 + + # check for exiting program + if float(utils.getSetting("starttype")) < 2 and utils.getSetting("watch_user") == 'false': + return 0 # the program may exit. No purpose for background process + + return 0 + except: + buggalo.onExceptionRaised() + + + def runUpdate(self, manualstart): + """ + entry point for manual start. + perform the update step by step + + Args: + manualstart: True if called manually + + Returns: + return code: + 0 success + 3 Error opening database + 4 Error getting watched state from addon database + 5 Error getting watched state from xbmc database + 6 Error writing WL Database + 7 Error writing XBMC database + 8 Error merging dropbox database into local watched list + 9 Error merging local database into dropbox + 10 Error performing database backup + """ + + try: + utils.buggalo_extradata_settings() + + # check if player is running before doing the update. Only do this check for automatic start + while xbmc.Player().isPlaying() == True and not manualstart: + if self.monitor.waitForAbort(60*1000): return 1 # wait one minute until next check for active playback + if xbmc.Player().isPlaying() == False: + if self.monitor.waitForAbort(180*1000): return 1 # wait 3 minutes so the dialogue does not pop up directly after the playback ends + + # load the addon-database + if self.load_db(True): # True: Manual start + utils.showNotification(utils.getString(32102), utils.getString(32601), xbmc.LOGERROR) + return 3 + + if self.sync_tvshows(): + utils.showNotification(utils.getString(32102), utils.getString(32604), xbmc.LOGERROR) + return 5 + + # get the watched state from the addon + if self.get_watched_wl(0): + utils.showNotification(utils.getString(32102), utils.getString(32602), xbmc.LOGERROR) + return 4 + + # get watched state from xbmc + if self.get_watched_xbmc(0): + utils.showNotification(utils.getString(32102), utils.getString(32603), xbmc.LOGERROR) + return 5 + + if self.sync_tvshows(): + utils.showNotification(utils.getString(32102), utils.getString(32604), xbmc.LOGERROR) + return 5 + + # attempt to merge the database from dropbox + if DROPBOX_ENABLED and utils.getSetting("dropbox_enabled") == 'true' and self.merge_dropbox_local(): + utils.showNotification(utils.getString(32102), utils.getString(32607), xbmc.LOGERROR) + # do not abort execution of the whole addon if dropbox fails (e.g. due to network issues) + # return 8 + + # import from xbmc into addon database + res = self.write_wl_wdata() + if res == 2: # user exit + return 0 + elif res == 1: # error + utils.showNotification(utils.getString(32102), utils.getString(32604), xbmc.LOGERROR) + return 6 + + # close the sqlite database (addon) + self.close_db(1) # should be closed by the functions directly accessing the database + + # export from addon database into xbmc database + res = self.write_xbmc_wdata((utils.getSetting("progressdialog") == 'true'), 2) + if res == 2: # user exit + return 0 + elif res == 1: # error + utils.showNotification(utils.getString(32102), utils.getString(32605), xbmc.LOGERROR) + return 7 + + utils.showNotification(utils.getString(32101), utils.getString(32107), xbmc.LOGINFO) + utils.log(u'runUpdate exited with success', xbmc.LOGDEBUG) + + # sync with dropbox + if DROPBOX_ENABLED and utils.getSetting("dropbox_enabled") == 'true': + if self.merge_local_dropbox() == 0: + self.close_db(2) # save the database to file. + self.pushToDropbox() + else: + return 9 + + # delete old backup files of the database + # do this at the end for not destroying valuable backup in case something went wrong before + if self.database_backup_delete(): + return 10 + return 0 + except: + buggalo.onExceptionRaised() + + def load_db(self, manualstart=False): + """Load WL database + + Args: + manualstart: True if called manually; only retry opening db once + + Returns: + return code: + 0 successfully opened database + 1 error + 2 shutdown (serious error in subfunction) + """ + + try: + if int(utils.getSetting("db_format")) != 1: + # SQlite3 database in a file + # load the db path + if utils.getSetting("extdb") == 'false': + # use the default file + self.dbdirectory = xbmc.translatePath( utils.data_dir() ).decode('utf-8') + buggalo.addExtraData('dbdirectory', self.dbdirectory); + self.dbpath = os.path.join( self.dbdirectory , "watchedlist.db" ) + else: + wait_minutes = 1 # retry waittime if db path does not exist/ is offline + + while not self.monitor.abortRequested(): + # use a user specified file, for example to synchronize multiple clients + self.dbdirectory = xbmc.translatePath( utils.getSetting("dbpath") ).decode('utf-8') + self.dbfileaccess = utils.fileaccessmode(self.dbdirectory) + self.dbdirectory = utils.translateSMB(self.dbdirectory) + + self.dbpath = os.path.join( self.dbdirectory , utils.getSetting("dbfilename").decode('utf-8') ) + # xbmc.validatePath(self.dbdirectory) # does not work for smb + if not xbmcvfs.exists(self.dbdirectory): # do not use os.path.exists to access smb:// paths + if manualstart: + utils.log(u'db path does not exist: %s' % self.dbdirectory, xbmc.LOGWARNING) + return 1 # raise error on manual start if directory not accessible (we do not want to wait in that case) + else: + utils.log(u'db path does not exist, wait %d minutes: %s' % (wait_minutes, self.dbdirectory), xbmc.LOGWARNING) + + utils.showNotification(utils.getString(32102), utils.getString(32002) % self.dbdirectory, xbmc.LOGWARNING) + # Wait "wait_minutes" minutes until next check for file path (necessary on network shares, that are offline) + wait_minutes += wait_minutes # increase waittime until next check + if self.monitor.waitForAbort(wait_minutes*60): return 2 + else: + break # directory exists, continue below + + # on unix, smb-shares can not be accessed with sqlite3 --> copy the db with xbmc file system operations and work in mirror directory + buggalo.addExtraData('dbfileaccess', self.dbfileaccess); + buggalo.addExtraData('dbdirectory', self.dbdirectory); + buggalo.addExtraData('dbpath', self.dbpath); + if self.dbfileaccess == 'copy': + self.dbdirectory_copy = self.dbdirectory + self.dbpath_copy = self.dbpath # path to db file as in the settings (but not accessable) + buggalo.addExtraData('dbdirectory_copy', self.dbdirectory_copy); + buggalo.addExtraData('dbpath_copy', self.dbpath_copy); + # work in copy directory in the xbmc profile folder + self.dbdirectory = os.path.join( xbmc.translatePath( utils.data_dir() ).decode('utf-8'), 'dbcopy') + if not xbmcvfs.exists(self.dbdirectory): + xbmcvfs.mkdir(self.dbdirectory) + utils.log(u'created directory %s' % str(self.dbdirectory)) + self.dbpath = os.path.join( self.dbdirectory , "watchedlist.db" ) + if xbmcvfs.exists(self.dbpath_copy): + success = xbmcvfs.copy(self.dbpath_copy, self.dbpath) # copy the external db file to local mirror directory + utils.log(u'copied db file %s -> %s. Success: %d' % (self.dbpath_copy, self.dbpath, success), xbmc.LOGDEBUG) + + buggalo.addExtraData('dbdirectory', self.dbdirectory); + buggalo.addExtraData('dbpath', self.dbpath); + + + # connect to the local wl database. create database if it does not exist + self.sqlcon_wl = sqlite3.connect(self.dbpath); + self.sqlcursor_wl = self.sqlcon_wl.cursor() + else: + # MySQL Database on a server + self.sqlcon_wl = mysql.connector.connect(user=utils.getSetting("mysql_user"), password=utils.getSetting("mysql_pass"), database=utils.getSetting("mysql_db"), host=utils.getSetting("mysql_server"), port=utils.getSetting("mysql_port")) + self.sqlcursor_wl = self.sqlcon_wl.cursor() + + # create tables if they don't exist + if int(utils.getSetting("db_format")) != 1: # sqlite file + self.sqlcursor_wl.execute(QUERY_CREATE_MV_SQLITE) + self.sqlcursor_wl.execute(QUERY_CREATE_EP_SQLITE) + self.sqlcursor_wl.execute(QUERY_CREATE_SS_SQLITE) + else: # mysql network database + self.sqlcursor_wl.execute(QUERY_CREATE_MV_MYSQL) + self.sqlcursor_wl.execute(QUERY_CREATE_EP_MYSQL) + self.sqlcursor_wl.execute(QUERY_CREATE_SS_MYSQL) + + # check for dropbox + if DROPBOX_ENABLED and utils.getSetting("dropbox_enabled") == 'true': + # Download Dropbox database only once a day to reduce traffic. + if time.time() > self.downloaded_dropbox_timestamp + 3600*24: + if self.pullFromDropbox(): + return + self.downloaded_dropbox_timestamp = time.time() + # connect to the dropbox wl database. + if self.dropbox_path != None: + self.sqlcon_db = sqlite3.connect(self.dropbox_path); + self.sqlcursor_db = self.sqlcon_db.cursor() + # create tables in dropbox file, if they don't exist + self.sqlcursor_db.execute(QUERY_CREATE_MV_SQLITE) + self.sqlcursor_db.execute(QUERY_CREATE_EP_SQLITE) + self.sqlcursor_db.execute(QUERY_CREATE_SS_SQLITE) + + buggalo.addExtraData('db_connstatus', 'connected') + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u"Database error while opening %s. '%s'" % (self.dbpath, errstring), xbmc.LOGERROR) + utils.showNotification(utils.getString(32109), errstring, xbmc.LOGERROR) + self.close_db(3) + buggalo.addExtraData('db_connstatus', 'sqlite3 error, closed') + return 1 + except mysql.connector.Error as err: + # Catch common mysql errors and show them to guide the user + utils.log(u"Database error while opening mySQL DB %s [%s:%s@%s]. %s" % (utils.getSetting("mysql_db"), utils.getSetting("mysql_user"), utils.getSetting("mysql_pass"), utils.getSetting("mysql_db"), err.msg), xbmc.LOGERROR) + if err.errno == mysql.connector.errorcode.ER_DBACCESS_DENIED_ERROR: + utils.showNotification(utils.getString(32108), utils.getString(32210) % (utils.getSetting("mysql_user"), utils.getSetting("mysql_db")), xbmc.LOGERROR) + elif err.errno == mysql.connector.errorcode.ER_ACCESS_DENIED_ERROR: + utils.showNotification(utils.getString(32108), utils.getString(32208), xbmc.LOGERROR) + elif err.errno == mysql.connector.errorcode.ER_BAD_DB_ERROR: + utils.showNotification(utils.getString(32108), utils.getString(32209) % utils.getSetting("mysql_db"), xbmc.LOGERROR) + else: + utils.showNotification(utils.getString(32108), err.msg, xbmc.LOGERROR) + buggalo.addExtraData('db_connstatus', 'mysql error, closed') + self.close_db(3) + return 1 + except: + utils.log(u"Error while opening %s: %s" % (self.dbpath, sys.exc_info()[2]), xbmc.LOGERROR) + self.close_db(3) + buggalo.addExtraData('dbpath', self.dbpath) + buggalo.addExtraData('db_connstatus', 'error, closed') + buggalo.onExceptionRaised() + return 1 + # only commit the changes if no error occured to ensure database persistence + self.sqlcon_wl.commit() + return 0 + + + + def close_db(self, flag): + """Close WL database + Argument: + flag + 1 for closing the WL database + 2 for closing the DB database + 3 for closing both + + Returns: + return code: + 0 successfully closed database + 1 error + """ + if flag == 1 or flag == 3: + if self.sqlcon_wl: + self.sqlcon_wl.close() + self.sqlcon_wl = 0 + # copy the db file back to the shared directory, if needed + if utils.getSetting("db_format") == '0' and self.dbfileaccess == 'copy': + if xbmcvfs.exists(self.dbpath): + success = xbmcvfs.copy(self.dbpath, self.dbpath_copy) + utils.log(u'copied db file %s -> %s. Success: %d' % (self.dbpath, self.dbpath_copy, success), xbmc.LOGDEBUG) + if not success: + utils.showNotification(utils.getString(32102), utils.getString(32606) % self.dbpath, xbmc.LOGERROR) + return 1 + buggalo.addExtraData('db_connstatus', 'closed') + if flag == 2 or flag == 3: + if self.sqlcon_db: + self.sqlcon_db.close() + self.sqlcon_db = 0 + return 0 + # cursor is not changed -> error + + + def get_watched_xbmc(self, silent): + """Get Watched States of XBMC Database + + Args: + silent: Do not show notifications if True + + Returns: + return code: + 0 success + 1 error + """ + try: + + ############################################ + # first tv shows with TheTVDB-ID, then tv episodes + if utils.getSetting("w_episodes") == 'true': + ############################################ + # get imdb tv-show id from xbmc database + utils.log(u'get_watched_xbmc: Get all TV shows from xbmc database', xbmc.LOGDEBUG) + json_response = utils.executeJSON({ + "jsonrpc": "2.0", + "method": "VideoLibrary.GetTVShows", + "params": { + "properties": ["title", "imdbnumber"], + "sort": { "order": "ascending", "method": "title" } + }, + "id": 1}) + if json_response.has_key('result') and json_response['result'] != None and json_response['result'].has_key('tvshows'): + for item in json_response['result']['tvshows']: + tvshowId_xbmc = int(item['tvshowid']) + try: + # check if series number is in imdb-format (scraper=imdb?) + res = re.compile('tt(\d+)').findall(item['imdbnumber']) + if len(res) == 0: + # number in thetvdb-format + tvshowId_imdb = int(item['imdbnumber']) + else: + # number in imdb-format + tvshowId_imdb = int(res[0]) + except: + utils.log(u'get_watched_xbmc: tv show "%s" has no imdb-number in database. tvshowid=%d Try rescraping.' % (item['title'], tvshowId_xbmc), xbmc.LOGINFO) + if not silent: utils.showNotification( utils.getString(32101), utils.getString(32297)%(item['title'], tvshowId_xbmc), xbmc.LOGINFO) + tvshowId_imdb = int(0) + self.tvshows[tvshowId_xbmc] = list([tvshowId_imdb, item['title']]) + if tvshowId_imdb > 0: + self.tvshownames[tvshowId_imdb] = item['title'] + + # Get all watched movies and episodes by unique id from xbmc-database via JSONRPC + self.watchedmovielist_xbmc = list([]) + self.watchedepisodelist_xbmc = list([]) + for modus in ['movie', 'episode']: + buggalo.addExtraData('modus', modus); + if modus == 'movie' and utils.getSetting("w_movies") != 'true': + continue + if modus == 'episode' and utils.getSetting("w_episodes") != 'true': + continue + utils.log(u'get_watched_xbmc: Get all %ss from xbmc database' % modus, xbmc.LOGDEBUG) + if modus == 'movie': + # use the JSON-RPC to access the xbmc-database. + json_response = utils.executeJSON({ + "jsonrpc": "2.0", + "method": "VideoLibrary.GetMovies", + "params": { + "properties": ["title", "year", "imdbnumber", "lastplayed", "playcount"], + "sort": { "order": "ascending", "method": "title" } + }, + "id": 1 + }) + else: + json_response = utils.executeJSON({ + "jsonrpc": "2.0", + "method": "VideoLibrary.GetEpisodes", + "params": { + "properties": ["tvshowid", "season", "episode", "playcount", "showtitle", "lastplayed"] + }, + "id": 1 + }) + if modus == 'movie': searchkey = 'movies' + else: searchkey = 'episodes' + if json_response.has_key('result') and json_response['result'] != None and json_response['result'].has_key(searchkey): + + # go through all watched movies and save them in the class-variable self.watchedmovielist_xbmc + for item in json_response['result'][searchkey]: + if modus == 'movie': + name = item['title'] + ' (' + str(item['year']) + ')' + res = re.compile('tt(\d+)').findall(item['imdbnumber']) + if len(res) == 0: + # no imdb-number for this movie in database. Skip + utils.log(u'get_watched_xbmc: Movie %s has no imdb-number in database. movieid=%d Try rescraping' % (name, int(item['movieid'])), xbmc.LOGDEBUG) + continue + imdbId = int(res[0]) + else: # episodes + tvshowId_xbmc = item['tvshowid'] + name = '%s S%02dE%02d' % (item['showtitle'], item['season'], item['episode']) + try: + tvshowId_imdb = self.tvshows[tvshowId_xbmc][0] + except: + utils.log(u'get_watched_xbmc: xbmc tv showid %d is not in table xbmc-tvshows. Skipping %s' % (item['tvshowid'], name), xbmc.LOGWARNING) + continue + if tvshowId_imdb == 0: + utils.log(u'get_watched_xbmc: tvshow %d has no imdb-number. Skipping %s' % (item['tvshowid'], name), xbmc.LOGDEBUG) + continue + lastplayed = utils.sqlDateTimeToTimeStamp(item['lastplayed']) # convert to integer-timestamp + playcount = int(item['playcount']) + # add data to the class-variables + if modus == 'movie': + self.watchedmovielist_xbmc.append(list([imdbId, 0, 0, lastplayed, playcount, name, 0, int(item['movieid'])]))# 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + else: + self.watchedepisodelist_xbmc.append(list([tvshowId_imdb, int(item['season']), int(item['episode']), lastplayed, playcount, name, 0, int(item['episodeid'])])) + if not silent: utils.showNotification( utils.getString(32101), utils.getString(32299)%(len(self.watchedmovielist_xbmc), len(self.watchedepisodelist_xbmc)), xbmc.LOGINFO) + return 0 + except: + utils.log(u'get_watched_xbmc: error getting the xbmc database : %s' % sys.exc_info()[2], xbmc.LOGERROR) + self.close_db(3) + buggalo.onExceptionRaised() + return 1 + + + def get_watched_wl(self, silent): + """Get Watched States of WL Database + + Args: + silent: Do not show notifications if True + + Returns: + return code: + 0 successfully got watched states from WL-database + 1 unknown error (programming related) + 2 shutdown (error in subfunction) + 3 error related to opening the database + """ + + try: + buggalo.addExtraData('self_sqlcursor', self.sqlcursor_wl); buggalo.addExtraData('self_sqlcon', self.sqlcon_wl); + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + return 2 + + # get watched movies from addon database + self.watchedmovielist_wl = list([]) + if utils.getSetting("w_movies") == 'true': + utils.log(u'get_watched_wl: Get watched movies from WL database', xbmc.LOGDEBUG) + if int(utils.getSetting("db_format")) != 1: # SQLite3 File. Timestamp stored as integer + self.sqlcursor_wl.execute(QUERY_SELECT_MV_SQLITE) + else: # mySQL: Create integer timestamp with the request + self.sqlcursor_wl.execute(QUERY_SELECT_MV_MYSQL) + rows = self.sqlcursor_wl.fetchall() + for row in rows: + self.watchedmovielist_wl.append(list([int(row[0]), 0, 0, int(row[1]), int(row[2]), row[3], int(row[4])])) # 0tvdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6lastChange + + # get watched episodes from addon database + self.watchedepisodelist_wl = list([]) + if utils.getSetting("w_episodes") == 'true': + utils.log(u'get_watched_wl: Get watched episodes from WL database', xbmc.LOGDEBUG) + if int(utils.getSetting("db_format")) != 1: # SQLite3 File. Timestamp stored as integer + self.sqlcursor_wl.execute(QUERY_SELECT_EP_SQLITE) + else: # mySQL: Create integer timestamp with the request + self.sqlcursor_wl.execute(QUERY_SELECT_EP_MYSQL) + + rows = self.sqlcursor_wl.fetchall() + for row in rows: + try: + name = '%s S%02dE%02d' % (self.tvshownames[int(row[0])], int(row[1]), int(row[2])) + except: + name = 'tvdb-id %d S%02dE%02d' % (int(row[0]), int(row[1]), int(row[2])) + self.watchedepisodelist_wl.append(list([int(row[0]), int(row[1]), int(row[2]), int(row[3]), int(row[4]), name, int(row[5])]))# 0tvdbnumber, 1season, 2episode, 3lastplayed, 4playcount, 5name, 6lastChange + + if not silent: utils.showNotification(utils.getString(32101), utils.getString(32298)%(len(self.watchedmovielist_wl), len(self.watchedepisodelist_wl)), xbmc.LOGINFO) + self.close_db(1) + return 0 + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'get_watched_wl: SQLite Database error getting the wl database. %s' % errstring, xbmc.LOGERROR) + self.close_db(1) + # error could be that the database is locked (for tv show strings). This is not an error to disturb the other functions + return 3 + except mysql.connector.Error as err: + utils.log(u'get_watched_wl: MySQL Database error getting the wl database. %s' % err, xbmc.LOGERROR) + return 3 + except: + utils.log(u'get_watched_wl: Error getting the wl database : %s' % sys.exc_info()[2], xbmc.LOGERROR) + self.close_db(1) + buggalo.onExceptionRaised() + return 1 + + + def sync_tvshows(self): + """Sync List of TV Shows from XBMC to WL Database + + Returns: + return code: + 0 successfully synched tv shows + 1 database access error + 2 database loading error + """ + + try: + utils.log(u'sync_tvshows: sync tvshows with wl database : %s' % sys.exc_info()[2], xbmc.LOGDEBUG) + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + return 2 + # write eventually new tv shows to wl database + for xbmcid in self.tvshows: + values = self.tvshows[xbmcid] + if values[0] == 0: + # no imdb-id saved for this tv show. Nothing to process. + continue + if int(utils.getSetting("db_format")) != 1: # sqlite3 + self.sqlcursor_wl.execute(QUERY_INSERT_SS_SQLITE, values) + else: # mysql + self.sqlcursor_wl.execute(QUERY_INSERT_SS_MYSQL, values) + self.database_backup() + self.sqlcon_wl.commit() + # get all known tv shows from wl database + self.sqlcursor_wl.execute("SELECT idShow, title FROM tvshows") + rows = self.sqlcursor_wl.fetchall() + for i in range(len(rows)): + self.tvshownames[int(rows[i][0])] = rows[i][1] + self.close_db(1) + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'sync_tvshows: SQLite Database error accessing the wl database: ''%s''' % errstring, xbmc.LOGERROR) + self.close_db(1) + # error could be that the database is locked (for tv show strings). + return 1 + except mysql.connector.Error as err: + utils.log(u"sync_tvshows: MySQL Database error accessing the wl database: ''%s''" % (err), xbmc.LOGERROR) + self.close_db(1) + return 1 + except: + utils.log(u'sync_tvshows: Error getting the wl database: ''%s''' % sys.exc_info()[2], xbmc.LOGERROR) + self.close_db(1) + buggalo.onExceptionRaised() + return 1 + return 0 + + + + def write_wl_wdata(self): + """Go through all watched movies from xbmc and check whether they are up to date in the addon database + + Returns: + return code: + 0 successfully written WL + 1 program exception + 2 database loading error + """ + + buggalo.addExtraData('self_sqlcursor', self.sqlcursor_wl); buggalo.addExtraData('self_sqlcon', self.sqlcon_wl); + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + return 2 + for modus in ['movie', 'episode']: + buggalo.addExtraData('modus', modus); + if modus == 'movie' and utils.getSetting("w_movies") != 'true': + continue + if modus == 'episode' and utils.getSetting("w_episodes") != 'true': + continue + utils.log(u'write_wl_wdata: Write watched %ss to WL database' % modus, xbmc.LOGDEBUG) + count_insert = 0 + count_update = 0 + if utils.getSetting("progressdialog") == 'true': + DIALOG_PROGRESS = xbmcgui.DialogProgress() + DIALOG_PROGRESS.create( utils.getString(32101) , utils.getString(32105)) + if modus == 'movie': + list_length = len(self.watchedmovielist_xbmc) + else: + list_length = len(self.watchedepisodelist_xbmc) + + for i in range(list_length): + if self.monitor.abortRequested(): break # this loop can take some time in debug mode and prevents xbmc exit + if utils.getSetting("progressdialog") == 'true' and DIALOG_PROGRESS.iscanceled(): + if modus == 'movie': strno = 32202 + else: strno = 32203; + utils.showNotification(utils.getString(strno), utils.getString(32301)%(count_insert, count_update), xbmc.LOGINFO) + return 2 + if modus == 'movie': + row_xbmc = self.watchedmovielist_xbmc[i] + else: + row_xbmc = self.watchedepisodelist_xbmc[i] + + if utils.getSetting("progressdialog") == 'true': + DIALOG_PROGRESS.update(100*(i+1)/list_length, utils.getString(32105), utils.getString(32610) % (i+1, list_length, row_xbmc[5]) ) + + try: + res = self._wl_update_media(modus, row_xbmc, 0, 0, 0) + count_insert += res['num_new']; count_update += res['num_update']; + + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'write_wl_wdata: SQLite Database error ''%s'' while updating %s %s' % (errstring, modus, row_xbmc[5]), xbmc.LOGERROR) + # error at this place is the result of duplicate movies, which produces a DUPLICATE PRIMARY KEY ERROR + return 1 + except mysql.connector.Error as err: + utils.log(u'write_wl_wdata: MySQL Database error ''%s'' while updating %s %s' % (err, modus, row_xbmc[5]), xbmc.LOGERROR) + self.close_db(1) + return 1 # error while writing. Do not continue with episodes, if movies raised an exception + except: + utils.log(u'write_wl_wdata: Error while updating %s %s: %s' % (modus, row_xbmc[5], sys.exc_info()[2]), xbmc.LOGERROR) + self.close_db(1) + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + buggalo.addExtraData('count_update', count_update); buggalo.addExtraData('count_insert', count_insert); + buggalo.onExceptionRaised() + return 1 + + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + # only commit the changes if no error occured to ensure database persistence + if count_insert > 0 or count_update > 0: + self.database_backup() + self.sqlcon_wl.commit() + if modus == 'movie': strno = [32202, 32301] + else: strno = [32203, 32301]; + utils.showNotification(utils.getString(strno[0]), utils.getString(strno[1])%(count_insert, count_update), xbmc.LOGINFO) + self.close_db(1) + return 0 + + + def write_xbmc_wdata(self, progressdialogue, notifications): + """Go through all watched movies/episodes from the wl-database and check, + if the xbmc-database is up to date + + Args: + progressdialogue: Show Progress Bar if True + notifications: 0= no, 1=only changed info, 2=all + + Returns: + return code: + 0 successfully written XBMC database + 1 program exception + 2 cancel by user interaction + """ + + for modus in ['movie', 'episode']: + buggalo.addExtraData('modus', modus); + if modus == 'movie' and utils.getSetting("w_movies") != 'true': + continue + if modus == 'episode' and utils.getSetting("w_episodes") != 'true': + continue + + utils.log(u'write_xbmc_wdata: Write watched %ss to xbmc database (pd=%d, noti=%d)' % (modus, progressdialogue, notifications), xbmc.LOGDEBUG) + count_update = 0 + if progressdialogue: + DIALOG_PROGRESS = xbmcgui.DialogProgress() + DIALOG_PROGRESS.create( utils.getString(32101), utils.getString(32106)) + + # list to iterate over + if modus == 'movie': + list_length = len(self.watchedmovielist_wl) + else: + list_length = len(self.watchedepisodelist_wl) + # iterate over wl-list + for j in range(list_length): + if self.monitor.abortRequested(): break # this loop can take some time in debug mode and prevents xbmc exit + if progressdialogue and DIALOG_PROGRESS.iscanceled(): + if notifications > 0: utils.showNotification(utils.getString(32204), utils.getString(32302)%(count_update), xbmc.LOGINFO) + return 2 + # get media-specific list items + if modus == 'movie': + row_wl = self.watchedmovielist_wl[j] + else: + row_wl = self.watchedepisodelist_wl[j] + season = row_wl[1] + episode = row_wl[2] + imdbId = row_wl[0] + name = row_wl[5] + + if progressdialogue: + DIALOG_PROGRESS.update(100*(j+1)/list_length, utils.getString(32106), utils.getString(32610) % (j+1, list_length, name) ) + try: + # search the unique movie/episode id in the xbmc-list + if modus == 'movie': + indices = [i for i, x in enumerate(self.watchedmovielist_xbmc) if x[0] == imdbId] # the movie can have multiple entries in xbmc + else: + indices = [i for i, x in enumerate(self.watchedepisodelist_xbmc) if x[0] == imdbId and x[1] == season and x[2] == episode] + lastplayed_wl = row_wl[3] + playcount_wl = row_wl[4] + lastchange_wl = row_wl[6] + if len(indices) > 0: + # the movie/episode is already in the xbmc-list + for i in indices: + if modus == 'movie': + row_xbmc = self.watchedmovielist_xbmc[i] + else: + row_xbmc = self.watchedepisodelist_xbmc[i] + + lastplayed_xbmc = row_xbmc[3] + playcount_xbmc = row_xbmc[4] + + change_xbmc_db = False + # check if movie/episode is set as unwatched in the wl database + if playcount_wl != playcount_xbmc and lastchange_wl > lastplayed_xbmc: + change_xbmc_db = True + # compare playcount and lastplayed (update if xbmc data is older) + if playcount_xbmc < playcount_wl or (lastplayed_xbmc < lastplayed_wl and playcount_wl > 0): + change_xbmc_db = True + if not change_xbmc_db: + # utils.log(u'write_xbmc_wdata: xbmc database up-to-date for tt%d, %s' % (imdbId, row_xbmc[2]), xbmc.LOGDEBUG) + continue + # check if the lastplayed-timestamp in wl is useful + if playcount_wl == 0: + lastplayed_new = 0 + else: + if lastplayed_wl == 0: + lastplayed_new = lastplayed_xbmc + else: + lastplayed_new = lastplayed_wl + # update database + mediaid = row_xbmc[7] + if modus == 'movie': jsonmethod = "VideoLibrary.SetMovieDetails"; idfieldname = "movieid" + else: jsonmethod = "VideoLibrary.SetEpisodeDetails"; idfieldname = "episodeid" + jsondict = { + "jsonrpc": "2.0", + "method": jsonmethod, + "params": {idfieldname: mediaid, "playcount": playcount_wl, "lastplayed": utils.TimeStamptosqlDateTime(lastplayed_new)}, + "id": 1 + } + json_response = utils.executeJSON(jsondict) + if (json_response.has_key('result') and json_response['result'] == 'OK'): + utils.log(u'write_xbmc_wdata: xbmc database updated for %s. playcount: {%d -> %d}, lastplayed: {"%s" -> "%s"} (%sid=%d)' % (name, playcount_xbmc, playcount_wl, utils.TimeStamptosqlDateTime(lastplayed_xbmc), utils.TimeStamptosqlDateTime(lastplayed_new), modus, mediaid), xbmc.LOGINFO) + if playcount_wl == 0: + if notifications > 0: utils.showNotification(utils.getString(32404), name, xbmc.LOGDEBUG) + else: + if notifications > 0: utils.showNotification(utils.getString(32401), name, xbmc.LOGDEBUG) + count_update += 1 + # update the xbmc-db-mirror-variable + if modus == 'movie': + self.watchedmovielist_xbmc[i][3] = lastplayed_new + self.watchedmovielist_xbmc[i][4] = playcount_wl + else: + self.watchedepisodelist_xbmc[i][3] = lastplayed_new + self.watchedepisodelist_xbmc[i][4] = playcount_wl + else: + utils.log(u'write_xbmc_wdata: error updating xbmc database. %s. json_response=%s' % (name, str(json_response)), xbmc.LOGERROR) + + else: + # the movie is in the watched-list but not in the xbmc-list -> no action + # utils.log(u'write_xbmc_wdata: movie not in xbmc database: tt%d, %s' % (imdbId, row_xbmc[2]), xbmc.LOGDEBUG) + continue + except: + utils.log(u"write_xbmc_wdata: Error while updating %s %s: %s" % (modus, name, sys.exc_info()[2]), xbmc.LOGERROR) + if progressdialogue: DIALOG_PROGRESS.close() + buggalo.addExtraData('count_update', count_update); + buggalo.onExceptionRaised() + return 1 + + if progressdialogue: DIALOG_PROGRESS.close() + if notifications > 1: + if modus == 'movie': strno = [32204, 32302] + else: strno = [32205, 32303]; + utils.showNotification(utils.getString(strno[0]), utils.getString(strno[1])%(count_update), xbmc.LOGINFO) + return 0 + + + def database_backup(self): + """create a copy of the database, in case something goes wrong (only if database file is used) + + Returns: + return code: + 0 successfully copied database + 1 file writing error + 2 program exception + + """ + + if utils.getSetting("db_format") != '0': + return 0 # no backup needed since we are using mysql database + + if utils.getSetting('dbbackupcount') == '0': + return 0 # no backup requested in the addon settings + + if not self.dbbackupdone: + if not xbmcvfs.exists(self.dbpath): + utils.log(u'database_backup: directory %s does not exist. No backup possible.' % self.dbpath, xbmc.LOGERROR) + return 1 + now = datetime.datetime.now() + timestr = u'%04d%02d%02d_%02d%02d%02d' % (now.year, now.month, now.day, now.hour, now.minute, now.second) + zipfilename = os.path.join(self.dbdirectory, utils.decode(timestr + u'-watchedlist.db.zip')) + zf = False + try: + zf = zipfile.ZipFile(zipfilename, 'w') + zf.write(self.dbpath, arcname='watchedlist.db', compress_type=zipfile.ZIP_DEFLATED) + zf.close() + self.dbbackupdone = True + utils.log(u'database_backup: database backup copy created to %s' % zipfilename, xbmc.LOGINFO) + # copy the zip file with xbmc file system, if needed + if self.dbfileaccess == 'copy': + xbmcvfs.copy(zipfilename, os.path.join(self.dbdirectory_copy, utils.decode(timestr + u'-watchedlist.db.zip'))) + xbmcvfs.delete(zipfilename) + except: + if zf: + zf.close() + buggalo.addExtraData('zipfilename', zipfilename); + buggalo.onExceptionRaised() + return 2 + return 0 + + def database_backup_delete(self): + if self.dbbackupdone == False: + return 0 + # Limit number of backup files to the specified value + backupsize = int(utils.getSetting('dbbackupcount')) + if backupsize == -1: + return 0 # do not delete old backup files + dirs,files = xbmcvfs.listdir(self.dbdirectory) + # find database copy files among all files in that directory + files_match=[] + for i, f in enumerate(files): + if re.match('\d+_\d+-watchedlist\.db\.zip', f) != None: # match the filename string from database_backup() + files_match.append(f) + files_match = sorted(files_match, reverse=True) + # Iterate over backup files starting with newest. Delete oldest + for i, f in enumerate(files_match): + if i >= int( utils.getSetting('dbbackupcount') ): + # delete file + utils.log(u'database_backup: Delete old backup file %d/%d (%s)' % (i+1,len(files_match)+1,f), xbmc.LOGINFO) + try: + xbmcvfs.delete(os.path.join(self.dbdirectory,f)) + except: + utils.log(u'database_backup: Error deleting old backup file %d (%s)' % (i,os.path.join(self.dbdirectory,f)), xbmc.LOGERROR) + return 1 + + def watch_user_changes(self, idletime_old, idletime): + """check if the user made changes in the watched states. Especially setting movies as "not watched". + This can not be recognized by the other functions + + Args: + idletime_old: Old Idle Time + idletime: New Idle Time + """ + + if xbmc.Player().isPlaying() == True: + return + if idletime > idletime_old: + # the idle time increased. No user interaction probably happened + return + self.watch_user_changes_count = self.watch_user_changes_count + 1 + utils.log(u'watch_user_changes: Check for user changes (no. %d)' % self.watch_user_changes_count, xbmc.LOGDEBUG) + + # save previous state + old_watchedmovielist_xbmc = self.watchedmovielist_xbmc + old_watchedepisodelist_xbmc = self.watchedepisodelist_xbmc + # get new state + self.get_watched_xbmc(1) + #save exception information + buggalo.addExtraData('len_old_watchedmovielist_xbmc', len(old_watchedmovielist_xbmc)) + buggalo.addExtraData('len_old_watchedepisodelist_xbmc', len(old_watchedepisodelist_xbmc)) + buggalo.addExtraData('len_self_watchedmovielist_xbmc', len(self.watchedmovielist_xbmc)) + buggalo.addExtraData('len_self_watchedepisodelist_xbmc', len(self.watchedepisodelist_xbmc)) + # separate the change detection and the change in the database to prevent circle reference + indices_changed = list([]) + # compare states of movies/episodes + for modus in ['movie', 'episode']: + buggalo.addExtraData('modus', modus); + if modus == 'movie' and utils.getSetting("w_movies") != 'true': + continue + if modus == 'episode' and utils.getSetting("w_episodes") != 'true': + continue + if modus == 'movie': + list_new = self.watchedmovielist_xbmc + list_old = old_watchedmovielist_xbmc + else: + list_new = self.watchedepisodelist_xbmc + list_old = old_watchedepisodelist_xbmc + if len(list_old) == 0 or len(list_new) == 0: + # one of the lists is empty: nothing to compare. No user changes noticable + continue + for i_n, row_xbmc in enumerate(list_new): + if self.monitor.abortRequested(): return + mediaid = row_xbmc[7] + lastplayed_new = row_xbmc[3] + playcount_new = row_xbmc[4] + # index of this movie/episode in the old database (before the change by the user) + if (len(list_old) > i_n) and (list_old[i_n][7] == mediaid): i_o = i_n # db did not change + else: # search the movieid + i_o = [i for i, x in enumerate(list_old) if x[7] == mediaid] + if len(i_o) == 0: continue #movie is not in old array + i_o = i_o[0] # convert list to int + lastplayed_old = list_old[i_o][3] + playcount_old = list_old[i_o][4] + + + if playcount_new != playcount_old or lastplayed_new != lastplayed_old: + if playcount_new == playcount_old and playcount_new == 0: + continue # do not add lastplayed to database, when placount = 0 + # The user changed the playcount or lastplayed. + # update wl with new watched state + indices_changed.append([i_n, i_o, row_xbmc]) + + # go through all movies changed by the user + for icx in indices_changed: + if self.monitor.abortRequested(): return 1 + i_o = icx[1]; row_xbmc = icx[2] + i_n = icx[0]; + lastplayed_old = list_old[i_o][3]; playcount_old = list_old[i_o][4]; + lastplayed_new = row_xbmc[3]; playcount_new = row_xbmc[4]; mediaid = row_xbmc[7] + utils.log(u'watch_user_changes: %s "%s" changed playcount {%d -> %d} lastplayed {"%s" -> "%s"}. %sid=%d' % (modus, row_xbmc[5], playcount_old, playcount_new, utils.TimeStamptosqlDateTime(lastplayed_old), utils.TimeStamptosqlDateTime(lastplayed_new), modus, mediaid)) + try: + self._wl_update_media(modus, row_xbmc, 1, 1, 0) + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'write_wl_wdata: SQLite Database error (%s) while updating %s %s' % (errstring, modus, row_xbmc[5])) + utils.showNotification(utils.getString(32102), utils.getString(32606) % ('(%s)' % errstring), xbmc.LOGERROR) + # error because of db locked or similar error + self.close_db(1) + break + except mysql.connector.Error as err: + # Catch common mysql errors and show them to guide the user + utils.log(u'write_wl_wdata: MySQL Database error (%s) while updating %s %s' % (err, modus, row_xbmc[5])) + utils.showNotification(utils.getString(32102), utils.getString(32606) % ('(%s)' % err), xbmc.LOGERROR) + self.close_db(1) + break + + # update xbmc watched status, e.g. to set duplicate movies also as watched + if len(indices_changed) > 0: + self.write_xbmc_wdata(0, 1) # this changes self.watchedmovielist_xbmc + self.close_db(1) # keep the db closed most of the time (no access problems) + + + def _wl_update_media(self, mediatype, row_xbmc, saveanyway, commit, lastChange): + """update the wl database for one movie/episode with the information in row_xbmc. + + This function is intended to be private and should not be called externally + + Args: + mediatype: 'episode' or 'movie' + row_xbmc: One row of the xbmc media table self.watchedmovielist_xbmc. + saveanyway: Skip checks whether not to save the changes + commit: The db change is committed directly (slow with many movies, but safe) + lastChange: Last change timestamp of the given data. + If 0: Data from kodi. + If >0: Data from other watchedlist database for merging + + Returns: Dict with fields + errcode: + 0 No errors + 2 error loading the database + num_new, num_update: + Number of new and updated entries + """ + retval={'errcode':0, 'num_new':0, 'num_update':0} + buggalo.addExtraData('self_sqlcursor', self.sqlcursor_wl); buggalo.addExtraData('self_sqlcon', self.sqlcon_wl); + buggalo.addExtraData('len_self_watchedmovielist_wl', len(self.watchedmovielist_wl)) + buggalo.addExtraData('len_self_watchedepisodelist_wl', len(self.watchedepisodelist_wl)) + buggalo.addExtraData('len_self_tvshownames', len(self.tvshownames)) + buggalo.addExtraData('row_xbmc', row_xbmc) + buggalo.addExtraData('saveanyway', saveanyway) + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + retval['errcode'] = 2 + return retval + + buggalo.addExtraData('modus', mediatype) + # row_xbmc: 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + imdbId = row_xbmc[0] + lastplayed_xbmc = row_xbmc[3] + playcount_xbmc = row_xbmc[4] + name = row_xbmc[5] + if mediatype == 'episode': + season = row_xbmc[1] + episode = row_xbmc[2] + + self.database_backup() + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + retval['errcode'] = 2 + return retval + if not saveanyway and playcount_xbmc == 0 and lastChange == 0: + # playcount in xbmc-list is empty. Nothing to save + # utils.log(u'wl_update_%s: not watched in xbmc: tt%d, %s' % (modus, imdbId, name), xbmc.LOGDEBUG) + return retval + if lastChange == 0: + lastchange_new = int(time.time()) + else: # data from WL database with given last change timestamp + lastchange_new = lastChange + if lastplayed_xbmc == -1: # used for external calls + lastplayed_xbmc = int(time.time()) + if mediatype == 'movie': + j = [ii for ii, x in enumerate(self.watchedmovielist_wl) if x[0] == imdbId] + if mediatype == 'episode': + j = [ii for ii, x in enumerate(self.watchedepisodelist_wl) if x[0] == imdbId and x[1] == season and x[2] == episode] + if len(j) > 0: # j is the list of indexes of the occurences of the given row + j = j[0] # there can only be one valid index j, since only one entry in wl per imdbId + # the movie is already in the watched-list + if mediatype == 'movie': + row_wl = self.watchedmovielist_wl[j] + else: + row_wl = self.watchedepisodelist_wl[j] + lastplayed_wl = row_wl[3] + playcount_wl = row_wl[4] + lastchange_wl = row_wl[6] + + if not saveanyway: + # check if an update of the wl database is necessary (xbmc watched status newer) + if lastChange == 0: # information from xbmc: Criterion Playcount and lastplayed-timestamp + if lastchange_wl > lastplayed_xbmc: + return retval # no update of WL-db. Return + if playcount_wl >= playcount_xbmc and lastplayed_wl >= lastplayed_xbmc: + return retval # everything up-to-date + elif lastChange <= lastchange_wl: # information from watchedlist. Only criterion: lastChange-timestamp + return retval + + # check if the lastplayed-timestamp in xbmc is useful + if lastplayed_xbmc == 0 and lastChange == 0: + lastplayed_new = lastplayed_wl + else: + lastplayed_new = lastplayed_xbmc + else: + lastplayed_new = lastplayed_xbmc + + if mediatype == 'movie': + if int(utils.getSetting("db_format")) != 1: # sqlite3 + sql = QUERY_MV_UPDATE_SQLITE + else: # mysql + sql = QUERY_MV_UPDATE_MYSQL + values = list([playcount_xbmc, lastplayed_new, lastchange_new, imdbId]) + else: + if int(utils.getSetting("db_format")) != 1: # sqlite3 + sql = QUERY_EP_UPDATE_SQLITE + else: # mysql + sql = QUERY_EP_UPDATE_MYSQL + values = list([playcount_xbmc, lastplayed_new, lastchange_new, imdbId, season, episode]) + self.sqlcursor_wl.execute(sql, values) + retval['num_update'] = self.sqlcursor_wl.rowcount + # update the local mirror variable of the wl database: # 0imdbnumber, season, episode, 3lastPlayed, 4playCount, 5title, 6lastChange + if mediatype == 'movie': + self.watchedmovielist_wl[j] = list([imdbId, 0, 0, lastplayed_new, playcount_xbmc, name, lastchange_new]) + else: + self.watchedepisodelist_wl[j] = list([imdbId, season, episode, lastplayed_new, playcount_xbmc, name, lastchange_new]) + utils.log(u'wl_update_%s: updated wl db for "%s" (id %d). playcount: {%d -> %d}. lastplayed: {"%s" -> "%s"}. lastchange: "%s. Affected %d row."' % (mediatype, name, imdbId, playcount_wl, playcount_xbmc, utils.TimeStamptosqlDateTime(lastplayed_wl), utils.TimeStamptosqlDateTime(lastplayed_new), utils.TimeStamptosqlDateTime(lastchange_new), self.sqlcursor_wl.rowcount), xbmc.LOGDEBUG) + if playcount_xbmc > 0: + utils.showNotification(utils.getString(32403), name, xbmc.LOGDEBUG) + else: + utils.showNotification(utils.getString(32405), name, xbmc.LOGDEBUG) + else: + # the movie is not in the watched-list -> insert the movie + # order: idMovieImdb,playCount,lastChange,lastPlayed,title + if mediatype == 'movie': + if int(utils.getSetting("db_format")) != 1: # sqlite3 + sql = QUERY_MV_INSERT_SQLITE + else: # mysql + sql = QUERY_MV_INSERT_MYSQL + values = list([imdbId, playcount_xbmc, lastchange_new, lastplayed_xbmc, name]) + else: # episode + if int(utils.getSetting("db_format")) != 1: # sqlite3 + sql = QUERY_EP_INSERT_SQLITE + else: # mysql + sql = QUERY_EP_INSERT_MYSQL + values = list([imdbId, season, episode, playcount_xbmc, lastchange_new, lastplayed_xbmc]) + self.sqlcursor_wl.execute(sql, values) + utils.log(u'wl_update_%s: new entry for wl database: "%s", lastChange="%s", lastPlayed="%s", playCount=%d. Affected %d row.' % (mediatype, name, utils.TimeStamptosqlDateTime(lastchange_new), utils.TimeStamptosqlDateTime(lastplayed_xbmc), playcount_xbmc, self.sqlcursor_wl.rowcount)) + retval['num_new'] = self.sqlcursor_wl.rowcount + # update the local mirror variable of the wl database + if mediatype == 'movie': + self.watchedmovielist_wl.append(list([imdbId, 0, 0, lastplayed_xbmc, playcount_xbmc, name, lastchange_new])) + else: + self.watchedepisodelist_wl.append(list([imdbId, season, episode, lastplayed_xbmc, playcount_xbmc, name, lastchange_new])) + if playcount_xbmc > 0: + utils.showNotification(utils.getString(32402), name, xbmc.LOGDEBUG) + else: + utils.showNotification(utils.getString(32405), name, xbmc.LOGDEBUG) + if commit: + self.sqlcon_wl.commit() + return retval + + def wl_update_media(self, mediatype, row_xbmc, saveanyway, commit, lastChange): + """update the wl database for one movie/episode with the information in row_xbmc. + + Function for external access with basic error handling + + Args: + mediatype: 'episode' or 'movie' + row_xbmc: One row of the xbmc media table self.watchedmovielist_xbmc or self.watchedepisodelist_xbmc. + List with [0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid] + or [0tvdbnumber, 1season, 2episode, 3lastplayed, 4playcount, 5name, 6empty, 7episodeid] + saveanyway: Skip checks whether not to save the changes + commit: The db change is committed directly (slow with many movies, but safe) + lastChange: Last change timestamp of the given data. + If 0: Data from kodi. + If >0: Data from other watchedlist database for merging + + Returns: Dict with fields + errcode: + 0 No errors + 1 error with the database + 2 error loading the database + num_new, num_update: + Number of new and updated entries + """ + retval={'errcode':0, 'num_new':0, 'num_update':0} + try: + return self._wl_update_media(mediatype, row_xbmc, saveanyway, commit, lastChange) + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'wl_update_media: SQLite Database error accessing the wl database: ''%s''' % errstring, xbmc.LOGERROR) + utils.showNotification(utils.getString(32109), errstring, xbmc.LOGERROR) + self.close_db(1) + retval['errcode'] = 1 + except mysql.connector.Error as err: + utils.log(u"wl_update_media: MySQL Database error accessing the wl database: ''%s''" % (err.msg), xbmc.LOGERROR) + utils.showNotification(utils.getString(32108), err.msg, xbmc.LOGERROR) + self.close_db(1) + retval['errcode'] = 1 + return retval + + def merge_dropbox_local(self): + """ + Merge the remote (eg: Dropbox) database into the local one + The resulting merged database contains all watched movies and episodes of both databases + + Returns: + return code: + 0 successfully synched databases + 1 database access error + 2 database loading error + """ + + if not self.dropbox_path: + utils.log(u'merge_dropbox_local: no dropbox path -- assuming download failed. not merging remote database.') + return 1 + + try: + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + return 2 + + # utils.log(u'merge_dropbox: Inserting missing TV shows.') + # do not merge tvshows between dropbox and local wl database + count_insert = 0 + count_update = 0 + for mediatype in ['movie', 'episode']: + # strno: number of string for heading of notifications + # sql_select_dropbox: Definitions of SQL queries to get data from the dropbox database + if mediatype == 'movie': + strno = 32711 + sql_select_dropbox = QUERY_SELECT_MV_SQLITE + else: + strno = 32712 + sql_select_dropbox = QUERY_SELECT_EP_SQLITE + utils.log(u'wl_merge_dropbox_local (%s): Start merging the remote into the local database' % mediatype) + self.sqlcursor_db.execute(sql_select_dropbox) + # loop through all rows of the remote (dropbox) database and merge it into the local database with + # with the function for merging the xbmc database + if utils.getSetting("progressdialog") == 'true': + DIALOG_PROGRESS = xbmcgui.DialogProgress() + DIALOG_PROGRESS.create( utils.getString(strno) , utils.getString(32105)) + rows = self.sqlcursor_db.fetchall() + list_length = len(rows) + for i in range(list_length): + row = rows[i] # see definition of sql_select_dropbox for contents of `row` + if mediatype == 'movie': # idMovieImdb, 1lastPlayed, 2playCount, 3title, 4lastChange + name = "%s" % row[3] + playCount = row[2] + lastChange = row[4] + lastPlayed = row[1] + else: # 0idShow, 1season, 2episode, 3lastPlayed, 4playCount, 5lastChange + try: + name = '%s S%02dE%02d' % (self.tvshownames[int(row[0])], int(row[1]), int(row[2])) + except: + name = 'tvdb-id %d S%02dE%02d' % (int(row[0]), int(row[1]), int(row[2])) + playCount = row[4] + lastChange = row[5] + lastPlayed = row[3] + + # handle the row from the dropbox database as if it came from the xbmc database and + # store it in the local WL database (same function call) + # row_xbmc_sim: 0imdbnumber, 1seasonnumber, 2episodenumber, 3lastPlayed, 4playCount, 5title, 6empty, 7movieid + row_xbmc_sim = [0, 0, 0, lastPlayed, playCount, name, 0] + if mediatype == 'movie': + row_xbmc_sim[0] = row[0] + else: + row_xbmc_sim[0:3] = row[0:3] + res = self._wl_update_media(mediatype, row_xbmc_sim, 0, 0, lastChange) + count_insert += res['num_new']; count_update += res['num_update']; + + # check if update is canceled + if utils.getSetting("progressdialog") == 'true' and DIALOG_PROGRESS.iscanceled(): + return 2 + if utils.getSetting("progressdialog") == 'true': + DIALOG_PROGRESS.update(100*(i+1)/list_length, utils.getString(32105), utils.getString(32610) % (i+1, list_length, row_xbmc_sim[5]) ) + + utils.showNotification(utils.getString(strno), utils.getString(32301)%(count_insert, count_update), xbmc.LOGINFO) + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'merge_dropbox_local: SQLite Database error accessing the wl database: ''%s''' % errstring, xbmc.LOGERROR) + self.close_db(3) + # error could be that the database is locked (for tv show strings). + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + return 1 + except: + utils.log(u'merge_dropbox_local: Error getting the wl database: ''%s''' % sys.exc_info()[2], xbmc.LOGERROR) + self.close_db(3) + buggalo.onExceptionRaised() + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + return 1 + # commit all changes: This saves the database file physically + self.sqlcon_wl.commit() + return 0 + + + def merge_local_dropbox(self): + """ + Merge the local database into the remote one (e.g. dropbox) by copying all tables directly + the content is already merged after loading the remote database (merge_dropbox_local) + + Returns: + return code: + 0 successfully synched databases + 1 database access error + 2 database loading error + """ + + if not self.dropbox_path: + utils.log(u'merge_dropbox: no dropbox path -- assuming download failed. not merging remote database.') + return 1 + + try: + if self.sqlcursor_wl == 0 or self.sqlcon_wl == 0: + if self.load_db(): + return 2 + + # utils.log(u'merge_dropbox: Inserting missing TV shows.') + # do not merge tvshows between dropbox and local wl database + count_insert = 0 + count_update = 0 + + # clear dropbox database and just insert all rows from the local on + self.sqlcursor_db.execute(QUERY_CLEAR_MV_SQLITE) + self.sqlcursor_db.execute(QUERY_CLEAR_EP_SQLITE) + + + for mediatype in ['movie', 'episode']: + # strno: number of string for heading of notifications + # sql_select_wl: Definitions of SQL queries to get data from the local database + utils.log(u'merge_local_dropbox (%s): Start merging the local into the remote database' % mediatype) + if mediatype == 'movie': + strno = 32718 + rows = self.watchedmovielist_wl # 0imdbnumber, 1empty, 2empty, 3lastPlayed, 4playCount, 5title, 6lastChange + + #if int(utils.getSetting("db_format")) != 1: # SQLite3 File. + # self.sqlcursor_wl.execute(QUERY_SELECT_MV_SQLITE) + #else: # mySQL + # self.sqlcursor_wl.execute(QUERY_SELECT_MV_MYSQL) + else: + strno = 32719 + rows = self.watchedepisodelist_wl # 0tvdbnumber, 1season, 2episode, 3lastplayed, 4playcount, 5empty, 6lastChange + #if int(utils.getSetting("db_format")) != 1: # SQLite3 File. + # self.sqlcursor_wl.execute(QUERY_SELECT_EP_SQLITE) + #else: # mySQL + # self.sqlcursor_wl.execute(QUERY_SELECT_EP_MYSQL) + + # loop through all rows of the local database and merge it into the remote (dropbox) database + if utils.getSetting("progressdialog") == 'true': + DIALOG_PROGRESS = xbmcgui.DialogProgress() + DIALOG_PROGRESS.create( utils.getString(strno) , utils.getString(32716)) # TODO + #rows = self.sqlcursor_wl.fetchall() + list_length = len(rows) + for i in range(list_length): + row = rows[i] # see definition of queries for contents of `row` + if mediatype == 'movie': + name = row[5] + sql = QUERY_MV_INSERT_SQLITE # idMovieImdb,playCount,lastChange,lastPlayed,title + values = list([row[0], row[4], row[6], row[3], row[5]]) + else: + try: + name = '%s S%02dE%02d' % (self.tvshownames[int(row[0])], int(row[1]), int(row[2])) + except: + name = 'tvdb-id %d S%02dE%02d' % (int(row[0]), int(row[1]), int(row[2])) + sql = QUERY_EP_INSERT_SQLITE # idShow,season,episode,playCount,lastChange,lastPlayed + values = list([row[0], row[1], row[2], row[4], row[6], row[3]]) + self.sqlcursor_db.execute(sql, values) + + # check if update is canceled DIALOG_PROGRESS.close() + if utils.getSetting("progressdialog") == 'true' and DIALOG_PROGRESS.iscanceled(): + return 2 + if utils.getSetting("progressdialog") == 'true': + + DIALOG_PROGRESS.update(100*(i+1)/list_length, utils.getString(strno), utils.getString(32610) % (i+1, list_length, name) ) + utils.showNotification(utils.getString(strno), (utils.getString(32717))%list_length, xbmc.LOGINFO) + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + except sqlite3.Error as err: + try: + errstring = err.args[0] # TODO: Find out, why this does not work some times + except: + errstring = '' + utils.log(u'merge_local_dropbox: SQLite Database error accessing the wl database: ''%s''' % errstring, xbmc.LOGERROR) + self.close_db(3) + # error could be that the database is locked (for tv show strings). + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + return 1 + except: + utils.log(u'merge_local_dropbox: Error getting the wl database: ''%s''' % sys.exc_info()[2], xbmc.LOGERROR) + self.close_db(3) + buggalo.onExceptionRaised() + if utils.getSetting("progressdialog") == 'true': DIALOG_PROGRESS.close() + return 1 + # only commit the changes if no error occured above (to ensure database persistence) + self.sqlcon_db.commit() + self.close_db(2) # close dropbox database + return 0 + + + + def pushToDropbox(self): + dropbox_key = utils.getSetting('dropbox_apikey') + + #utils.showNotification(utils.getString(32204), utils.getString(32302)%(count_update)) + # feign success if there is no local dropbox database file + if not (self.dropbox_path and dropbox_key): + return + + dest_file = '/' + 'watchedlist.db' + old_file = '/old' + 'watchedlist.db' + + client = dropbox.Dropbox(dropbox_key) + + # delete the old watched list. Failure here doesn't really matter + try: + client.files_delete(old_file) + except: + utils.log(u'Dropbox error: Unable to delete previous old watched list (%s)' % old_file) + + # rename the previously uploaded watchlist to "oldWHATEVER" + try: + client.files_move_v2(dest_file, old_file) + except: + utils.log(u'Dropbox error: Unable rename previous watched list') + + f = open(self.dropbox_path, 'rb') + try: + response = client.files_upload(f.read(), dest_file) + except DropboxApiError as err: + utils.log(u'Dropbox upload error: ' + str(err)) + utils.showNotification(utils.getString(32708), utils.getString(32709), xbmc.LOGERROR) + return + utils.showNotification(utils.getString(32713), utils.getString(32714), xbmc.LOGINFO) + utils.log(u'Dropbox upload complete: %s -> %s' % (self.dropbox_path, dest_file)) + + + def pullFromDropbox(self): + """ + Downloads the dropbox database + + Returns: + return code: + 0 successfully downloaded databases + 1 database download error + """ + dropbox_key = utils.getSetting('dropbox_apikey') + + if not dropbox_key: + # no dropbox authorization key entered. Feature does not work. + utils.showNotification(utils.getString(32708), utils.getString(32715), xbmc.LOGWARNING) + return 0 + + utils.log(u'Downloading WatchedList from dropbox') + + client = dropbox.Dropbox(dropbox_key) + + # save the dropbox database file in the user data directory (this is only a temporary file for upload and download) + self.dropbox_path = os.path.join( utils.data_dir() , "dropbox.db" ) + dropbox_file_exists = False + + remote_file = '/' + 'watchedlist.db' + old_file = '/old' + 'watchedlist.db' + # first: Try downloading the database file (if existent). + # if that fails, try restoring the backup file (if existent). + # if that also fails, the database will be rewritten (in other function) + try: + trycount = 0 + while trycount <= 4: # two tries for db file (trycount 1, 2), then try copying backup and download that (trycount 3,4) + trycount = trycount + 1 + try: + client.files_download_to_file(self.dropbox_path, remote_file) + dropbox_file_exists = True + break + except DropboxApiError as err: + # file not available, e.g. deleted or first execution. + utils.log(u'Dropbox database download failed. %s.' % str(err)) + time.sleep(0.5) # wait to avoid immediate re-try + if err[1].is_path() and type(err[1].get_path()) == dropbox.files.LookupError: + # Error reason: file does not exist + trycount = trycount + 1 # do not try second time on downloading non-existing file + if trycount == 2 and dropbox_file_exists == False: + try: + # No file was downloaded after second try. That means the dropbox file does not exist + # Try restoring the backup file, if existent + client.files_copy(old_file, remote_file) + except DropboxApiError as err: # files_copy raises RelocationError + # file not available, e.g. deleted or not existing + utils.log(u'Dropbox restore database backup failed. %s.' % str(err)) + break + except: # catch this error, the dropbox mode will be disabled + utils.log(u'Dropbox download error: ' + str(sys.exc_info())) + utils.showNotification(utils.getString(32708), utils.getString(32710), xbmc.LOGERROR) + self.dropbox_path = '' + return 1 + # check if file was written + if dropbox_file_exists: + utils.log(u'Dropbox database downloaded: %s -> %s' % (remote_file, self.dropbox_path)) + else: + utils.log(u'Dropbox database download failed. No remote file available.') + return 0 + + def get_movie_status(self, imdbId): + """ + Return the watched state of one movie from the internal WL database copy + requires this class to have a loaded database + + Args: + imdbId (Integer) + + Returns: + List with [Playcount, lastPlayed (integer timestamp), lastChange (integer timestamp)] + if no entry is available, playcount is -1 + """ + j = [ii for ii, x in enumerate(self.watchedmovielist_wl) if x[0] == imdbId] + if len(j) == 0: + return [-1,0,0] + else: + return [self.watchedmovielist_wl[j[0]][x] for x in [4,3,6]] # return 4playCount, 3lastPlayed, 6lastChange + + def get_episode_status(self, tvdbId, season, episode): + """ + Return the watched state of one tv series episode from the internal WL database copy + requires this class to have a loaded database + + Args: + tvdbId, season, episode (Integer) + + Returns: + List with [Playcount, lastPlayed (integer timestamp), lastChange (integer timestamp)] + if no entry is available, playcount is -1 + """ + j = [ii for ii, x in enumerate(self.watchedepisodelist_wl) if x[0] == tvdbId and x[1] == season and x[2] == episode] + if len(j) == 0: + return [-1,0,0] + else: + return [self.watchedepisodelist_wl[j[0]][x] for x in [4,3,6]] # return 4playCount, 3lastPlayed, 6lastChange diff --git a/service.watchedlist/manual.py b/service.watchedlist/manual.py new file mode 100644 index 0000000000..b403f379f3 --- /dev/null +++ b/service.watchedlist/manual.py @@ -0,0 +1,30 @@ +""" +This file is entry point for manual start via the programs menu +""" + +import lib.watchedlist.utils as utils +from lib.watchedlist.watchedlist import WatchedList +import xbmc, xbmcgui + +__remotedebug__ = False +# append pydev remote debugger +if __remotedebug__: + utils.log("Initialize remote debugging.") + # Make pydev debugger works for auto reload. + try: + import pydevd + pydevd.settrace('localhost', port=60678, stdoutToServer=True, stderrToServer=True) + except ImportError: + sys.stderr.write("Error: " + + "You must add org.python.pydev.debug.pysrc to your PYTHONPATH.") + utils.showNotification('WatchedList Error', 'remote debug could not be imported.', xbmc.LOGFATAL) + sys.exit(1) + except: + utils.showNotification('WatchedList Error', 'remote debug in pydev is activated, but remote server not responding.', xbmc.LOGERROR) + sys.exit(1) + +if (not utils.getSetting("autostart") == 'true') or xbmcgui.Dialog().yesno( utils.getString(32101),utils.getString(32001) ): + # Check if we should run updates (only ask if autostart is on) + # run the program + utils.log("Update Library Manual Run.") + WatchedList().runUpdate(True) # one time update diff --git a/service.watchedlist/resources/__init__.py b/service.watchedlist/resources/__init__.py new file mode 100644 index 0000000000..b93054b3ec --- /dev/null +++ b/service.watchedlist/resources/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/service.watchedlist/resources/language/English/strings.po b/service.watchedlist/resources/language/English/strings.po new file mode 100644 index 0000000000..a24c6ce2e1 --- /dev/null +++ b/service.watchedlist/resources/language/English/strings.po @@ -0,0 +1,416 @@ +# XBMC Media Center language file +# Addon Name: WatchedList +# Addon id: service.watchedlist +# Addon version: 1.1.0 +# Addon Provider: schapplm +msgid "" +msgstr "" +"Project-Id-Version: XBMC-Addons\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: 2016-03-28 16:47+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +msgctxt "Addon Summary" +msgid "Save watched Movies and TV Episodes independent from filenames" +msgstr "" + +msgctxt "Addon Description" +msgid "Export: Searches the Kodi-Database for watched files. Determine imdb-id and thetvdb-id to identify movies and TV-episodes. Then save the list to a new independent table.\nImport: Set the watched state for each video file in Kodi.\nAutomatic background process without user interaction." +msgstr "" + +msgctxt "Addon Disclaimer" +msgid "This script does not regard the file format. A Movie watched in DVD quality will also set the 1080p version as watched." +msgstr "" + +#Notification headers + +msgctxt "#30001" +msgid "Basic Settings" +msgstr "" + +msgctxt "#30002" +msgid "Database" +msgstr "" + +msgctxt "#30011" +msgid "Update movies watched state" +msgstr "" + +msgctxt "#30012" +msgid "Update episodes watched state" +msgstr "" + +msgctxt "#30013" +msgid "autostart mode" +msgstr "" + +msgctxt "#30014" +msgid "Update interval [hours]" +msgstr "" + +msgctxt "#30015" +msgid "Startup delay befor action [minutes]" +msgstr "" + +msgctxt "#30016" +msgid "Progress Dialog" +msgstr "" + +msgctxt "#30017" +msgid "Use non-default database file" +msgstr "" + +msgctxt "#30018" +msgid "Path to database file" +msgstr "" + +msgctxt "#30019" +msgid "Database filename (SQLite Database, *.db)" +msgstr "" + +msgctxt "#30020" +msgid "Number of backup copies (-1=infinite)" +msgstr "" + +msgctxt "#30021" +msgid "Start with Kodi (autorun)" +msgstr "" + +msgctxt "#30022" +msgid "follow user setting watched/unwatched" +msgstr "" + +msgctxt "#30023" +msgid "Dropbox Auth Code" +msgstr "" + +msgctxt "#30024" +msgid "Amount of User Information" +msgstr "" + +msgctxt "#30025" +msgid "All" +msgstr "" + +msgctxt "#30026" +msgid "Only Info" +msgstr "" + +msgctxt "#30027" +msgid "Only Warning" +msgstr "" + +msgctxt "#30028" +msgid "Only Error" +msgstr "" + +msgctxt "#30029" +msgid "None" +msgstr "" + +msgctxt "#30030" +msgid "no update" +msgstr "" + +msgctxt "#30031" +msgid "one update" +msgstr "" + +msgctxt "#30032" +msgid "periodic" +msgstr "" + +#empty strings from id 30033 to 30039 + +msgctxt "#30040" +msgid "DB Method" +msgstr "" + +msgctxt "#30041" +msgid "File" +msgstr "" + +msgctxt "#30042" +msgid "MySQL" +msgstr "" + +msgctxt "#30043" +msgid "Options for accessing SQLite-DB-File" +msgstr "" + +msgctxt "#30044" +msgid "Options for accessing MySQL-Server" +msgstr "" + +msgctxt "#30045" +msgid "Dropbox synchronization" +msgstr "" + +#empty strings from id 30046 to 30050 + +msgctxt "#30051" +msgid "MySQL Server" +msgstr "" + +msgctxt "#30052" +msgid "MySQL Database" +msgstr "" + +msgctxt "#30053" +msgid "MySQL Username" +msgstr "" + +msgctxt "#30054" +msgid "MySQL Password" +msgstr "" + +msgctxt "#30055" +msgid "MySQL Port" +msgstr "" + +#empty strings from id 30056 to 32000 + +msgctxt "#32001" +msgid "manually run the database update again?" +msgstr "" + +msgctxt "#32002" +msgid "DB path does not exist: %s" +msgstr "" + +msgctxt "#32003" +msgid "next update in %d hours" +msgstr "" + +msgctxt "#32004" +msgid "begin update in %d minutes" +msgstr "" + +msgctxt "#32005" +msgid "running observation of changes of watched states" +msgstr "" + +#empty strings from id 32006 to 32100 + +msgctxt "#32101" +msgid "WatchedList" +msgstr "" + +msgctxt "#32102" +msgid "Watchedlist: Error" +msgstr "" + +msgctxt "#32103" +msgid "Load watched states from addon-database..." +msgstr "" + +msgctxt "#32104" +msgid "load watched states Kodi-database ..." +msgstr "" + +msgctxt "#32105" +msgid "update the addon watched-database ..." +msgstr "" + +msgctxt "#32106" +msgid "update the Kodi-database ..." +msgstr "" + +msgctxt "#32107" +msgid "update complete." +msgstr "" + +msgctxt "#32108" +msgid "Watchedlist: MySQL Error (WL DB)" +msgstr "" + +msgctxt "#32109" +msgid "Watchedlist: SQLite Error (WL DB)" +msgstr "" + +#empty strings from id 32110 to 32201 + +msgctxt "#32202" +msgid "Addon movie database" +msgstr "" + +msgctxt "#32203" +msgid "Addon episode database" +msgstr "" + +msgctxt "#32204" +msgid "Kodi-movie database" +msgstr "" + +msgctxt "#32205" +msgid "Kodi-episode database" +msgstr "" + +#empty strings from id 32206 to 32207 + +msgctxt "#32208" +msgid "Access Denied (username/password/rights)" +msgstr "" + +msgctxt "#32209" +msgid "Database '%s' does not exist" +msgstr "" + +msgctxt "#32210" +msgid "Access denied (user '%s', DB '%s')" +msgstr "" + +#empty strings from id 32211 to 32296 + +msgctxt "#32297" +msgid "TV-Show \"%s\" (ID %d) has no imdb-number. No processing with WL possible. Try rescraping." +msgstr "" + +msgctxt "#32298" +msgid "%d movies and %d episodes read from WL." +msgstr "" + +msgctxt "#32299" +msgid "%d movies and %d episodes read from Kodi-DB." +msgstr "" + +#empty string with id 32300 + +msgctxt "#32301" +msgid "%d watched new. %d updated" +msgstr "" + +msgctxt "#32302" +msgid "%d movies marked as watched" +msgstr "" + +msgctxt "#32303" +msgid "%d episodes marked as watched" +msgstr "" + +#Error Messages +#empty strings from id 32304 to 32400 + +msgctxt "#32401" +msgid "Set as watched (Kodi-DB)" +msgstr "" + +msgctxt "#32402" +msgid "Remember as newly watched (WL-DB)" +msgstr "" + +msgctxt "#32403" +msgid "Remember as watched (updated) (WL-DB)" +msgstr "" + +msgctxt "#32404" +msgid "Set as not watched (Kodi-DB)" +msgstr "" + +msgctxt "#32405" +msgid "Remember as not watched (WL-DB)" +msgstr "" + +#Notification text +#empty strings from id 32406 to 32600 + +msgctxt "#32601" +msgid "Error opening WL-database" +msgstr "" + +msgctxt "#32602" +msgid "Error loading WL-database" +msgstr "" + +msgctxt "#32603" +msgid "Error loading Kodi-database" +msgstr "" + +msgctxt "#32604" +msgid "Error updating WL database" +msgstr "" + +msgctxt "#32605" +msgid "Error updating Kodi-database" +msgstr "" + +msgctxt "#32606" +msgid "Writing the WL-database file %s" +msgstr "" + +msgctxt "#32607" +msgid "Unable to merge dropbox data" +msgstr "" + +#Progress Dialog +#empty string with id 32609 + +msgctxt "#32610" +msgid "%d/%d: %s" +msgstr "" + +msgctxt "#32611" +msgid "%d/%d: %s S%02dE%02d" +msgstr "" + +#Dropbox related messages +#empty strings from id 32612 to 32707 + +msgctxt "#32708" +msgid "Dropbox Error" +msgstr "" + +msgctxt "#32709" +msgid "Unable to upload" +msgstr "" + +msgctxt "#32710" +msgid "Unable to download" +msgstr "" + +msgctxt "#32711" +msgid "Dropbox-Import (Movies)" +msgstr "" + +msgctxt "#32712" +msgid "Dropbox-Import (Episodes)" +msgstr "" + +msgctxt "#32713" +msgid "Dropbox synchronization" +msgstr "" + +msgctxt "#32714" +msgid "Upload successfull" +msgstr "" + +msgctxt "#32715" +msgid "Empty authorization code in settings" +msgstr "" + +msgctxt "#32716" +msgid "Update the Dropbox database: ..." +msgstr "" + +msgctxt "#32717" +msgid "Dropbox database updated: %d entries." +msgstr "" + +msgctxt "#32718" +msgid "Dropbox-Export (Movies)" +msgstr "" + +msgctxt "#32719" +msgid "Dropbox-Export (Episodes)" +msgstr "" + +msgctxt "#32720" +msgid "Error importing python Dropbox API. Disable function." +msgstr "" diff --git a/service.watchedlist/resources/language/German/strings.po b/service.watchedlist/resources/language/German/strings.po new file mode 100644 index 0000000000..53046861dd --- /dev/null +++ b/service.watchedlist/resources/language/German/strings.po @@ -0,0 +1,386 @@ +# XBMC Media Center language file +# Addon Name: WatchedList +# Addon id: service.watchedlist +# Addon version: 1.1.0 +# Addon Provider: schapplm +msgid "" +msgstr "" +"Project-Id-Version: XBMC-Addons\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: 2016-03-28 16:47+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Moritz Schappler \n" +"Language-Team: LANGUAGE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +msgctxt "Addon Summary" +msgid "Save watched Movies and TV Episodes independent from filenames" +msgstr "Gesehen-Status von Filmen und Serien-Episoden unabhängig von Dateinamen speichern" + +msgctxt "Addon Description" +msgid "Export: Searches the Kodi-Database for watched files. Determine imdb-id and thetvdb-id to identify movies and TV-episodes. Then save the list to a new independent table.\nImport: Set the watched state for each video file in Kodi.\nAutomatic background process without user interaction." +msgstr "Export: Durchsucht die Kodi-Datenbank nach gesehenen Filmen und ordnet den Dateien die eindeutigen Kennnummern von imdb und TheTVDB zu. Gesehene Filme und Episoden werden jeweils in einer eigenen Datenbank gespeichert.\nImport: Der Gesehen-Status von Medien in Kodi wird aus der Gesehenen-Tabelle geholt.\nDie Synchronisierung geschieht automatisch im Hintergrund ohne Benutzereingriff." + +msgctxt "Addon Disclaimer" +msgid "This script does not regard the file format. A Movie watched in DVD quality will also set the 1080p version as watched." +msgstr "Dieses Script ignoriert das Dateiformat. Ein gesehener Film in DVD-Qualität wird auch in der BlueRay-Version als gesehen gekennzeichnet." + +msgctxt "#30001" +msgid "Basic Settings" +msgstr "Grundeinstellungen" + +msgctxt "#30002" +msgid "Database" +msgstr "Datenbank" + +msgctxt "#30011" +msgid "Update movies watched state" +msgstr "Aktualisiere Gesehen-Status von Filmen" + +msgctxt "#30012" +msgid "Update episodes watched state" +msgstr "Aktualisiere Gesehen-Status von Episoden" + +msgctxt "#30013" +msgid "autostart mode" +msgstr "Starttyp" + +msgctxt "#30014" +msgid "Update interval [hours]" +msgstr "Aktualisierungs-Intervall [Stunden]" + +msgctxt "#30015" +msgid "Startup delay befor action [minutes]" +msgstr "Start-Verzögerung [Minuten]" + +msgctxt "#30016" +msgid "Progress Dialog" +msgstr "Fortschrittsanzeige" + +msgctxt "#30017" +msgid "Use non-default database file" +msgstr "Eigene Datenbank-Datei" + +msgctxt "#30018" +msgid "Path to database file" +msgstr "Pfad zur Datenbank-Datei" + +msgctxt "#30019" +msgid "Database filename (SQLite Database, *.db)" +msgstr "Dateiname (*.db)" + +msgctxt "#30020" +msgid "Number of backup copies (-1=infinite)" +msgstr "Anzahl an Sicherungskopien (-1=unendlich)" + +msgctxt "#30021" +msgid "Start with Kodi (autorun)" +msgstr "Automatisch mit Kodi starten" + +msgctxt "#30022" +msgid "follow user setting watched/unwatched" +msgstr "Gesehen-Änderungen laufend überwachen" + +msgctxt "#30023" +msgid "Dropbox Auth Code" +msgstr "Dropbox Autorisierungs-Code" + +msgctxt "#30024" +msgid "Amount of User Information" +msgstr "Grad der Benutzerinformation" + +msgctxt "#30025" +msgid "All" +msgstr "Alle" + +msgctxt "#30026" +msgid "Only Info" +msgstr "Nur Infos" + +msgctxt "#30027" +msgid "Only Warning" +msgstr "Nur Warnungen" + +msgctxt "#30028" +msgid "Only Error" +msgstr "Nur Fehler" + +msgctxt "#30029" +msgid "None" +msgstr "Keine" + +msgctxt "#30030" +msgid "no update" +msgstr "Keine Aktualisierung" + +msgctxt "#30031" +msgid "one update" +msgstr "Einmalig" + +msgctxt "#30032" +msgid "periodic" +msgstr "Periodisch" + +msgctxt "#30040" +msgid "DB Method" +msgstr "DB-Art" + +msgctxt "#30041" +msgid "File" +msgstr "Datei" + +msgctxt "#30042" +msgid "MySQL" +msgstr "MySQL" + +msgctxt "#30043" +msgid "Options for accessing SQLite-DB-File" +msgstr "Optionen für SQLite-Datei-Zugriff" + +msgctxt "#30044" +msgid "Options for accessing MySQL-Server" +msgstr "Optionen für MySQL-Server-Zugriff" + +msgctxt "#30045" +msgid "Dropbox synchronization" +msgstr "Dropbox-Synchronisierung" + +msgctxt "#30051" +msgid "MySQL Server" +msgstr "MySQL Server" + +msgctxt "#30052" +msgid "MySQL Database" +msgstr "MySQL Datenbank" + +msgctxt "#30053" +msgid "MySQL Username" +msgstr "MySQL Benutzername" + +msgctxt "#30054" +msgid "MySQL Password" +msgstr "MySQL Kennwort" + +msgctxt "#30055" +msgid "MySQL Port" +msgstr "MySQL Port" + +msgctxt "#32001" +msgid "manually run the database update again?" +msgstr "Datenbank-Aktualisierung nochmal starten?" + +msgctxt "#32002" +msgid "DB path does not exist: %s" +msgstr "DB-Pfad existiert nicht: %s" + +msgctxt "#32003" +msgid "next update in %d hours" +msgstr "Nächste Aktualisierung in %d Stunden" + +msgctxt "#32004" +msgid "begin update in %d minutes" +msgstr "Starte Aktualisierung in %d Minuten" + +msgctxt "#32005" +msgid "running observation of changes of watched states" +msgstr "laufende Überwachung von Änderungen des Gesehen-Status" + +msgctxt "#32101" +msgid "WatchedList" +msgstr "WatchedList" + +msgctxt "#32102" +msgid "Watchedlist: Error" +msgstr "WatchedList: Fehler" + +msgctxt "#32103" +msgid "Load watched states from addon-database..." +msgstr "Lade Gesehen-Status aus Gesehen-Datenbank..." + +msgctxt "#32104" +msgid "load watched states Kodi-database ..." +msgstr "Lade Gesehene-Status aus Kodi-Datenbank ..." + +msgctxt "#32105" +msgid "update the addon watched-database ..." +msgstr "Aktualisiere die Gesehen-Datenbank: ..." + +msgctxt "#32106" +msgid "update the Kodi-database ..." +msgstr "Aktualisiere die Kodi-Datenbank ..." + +msgctxt "#32107" +msgid "update complete." +msgstr "Aktualisierung abgeschlossen." + +msgctxt "#32108" +msgid "Watchedlist: MySQL Error (WL DB)" +msgstr "Watchedlist: MySQL Fehler (WL DB)" + +msgctxt "#32109" +msgid "Watchedlist: SQLite Error (WL DB)" +msgstr "Watchedlist: SQLite Fehler (WL DB)" + +msgctxt "#32202" +msgid "Addon movie database" +msgstr "Gesehen-Datenbank (Filme)" + +msgctxt "#32203" +msgid "Addon episode database" +msgstr "Gesehen-Datenbank (Episoden)" + +msgctxt "#32204" +msgid "Kodi-movie database" +msgstr "Kodi-Filmdatenbank" + +msgctxt "#32205" +msgid "Kodi-episode database" +msgstr "Kodi-Episodendatenbank" + +msgctxt "#32208" +msgid "Access Denied (username/password/rights)" +msgstr "Zugriff verweigert (Benutzername/Kennwort/Rechte)" + +msgctxt "#32209" +msgid "Database '%s' does not exist" +msgstr "Datenbank '%s' existiert nicht" + +msgctxt "#32210" +msgid "Access denied (user '%s', DB '%s')" +msgstr "Zugriff verweigert (Benutzer '%s', DB '%s')" + +msgctxt "#32297" +msgid "TV-Show \"%s\" (ID %d) has no imdb-number. No processing with WL possible. Try rescraping." +msgstr "TV-Serie \"%s\" (ID %d) hat keine imdb-Nummer. Keine Verarbeitung mit WL möglich." + +msgctxt "#32298" +msgid "%d movies and %d episodes read from WL." +msgstr "%d Filme, %d Episoden in WL." + +msgctxt "#32299" +msgid "%d movies and %d episodes read from Kodi-DB." +msgstr "%d Filme, %d Episoden in Kodi-DB." + +msgctxt "#32301" +msgid "%d watched new. %d updated" +msgstr "%d neu gesehen. %d aktualisiert." + +msgctxt "#32302" +msgid "%d movies marked as watched" +msgstr "%d Filme als gesehen markiert." + +msgctxt "#32303" +msgid "%d episodes marked as watched" +msgstr "%d Episoden als gesehen markiert." + +msgctxt "#32401" +msgid "Set as watched (Kodi-DB)" +msgstr "Als gesehen markiert (Kodi-DB)" + +msgctxt "#32402" +msgid "Remember as newly watched (WL-DB)" +msgstr "Merke als neu gesehen (WL-DB)" + +msgctxt "#32403" +msgid "Remember as watched (updated) (WL-DB)" +msgstr "Merke als gesehen (aktualisiert) (WL-DB)" + +msgctxt "#32404" +msgid "Set as not watched (Kodi-DB)" +msgstr "Als ungesehen markiert (Kodi-DB)" + +msgctxt "#32405" +msgid "Remember as not watched (WL-DB)" +msgstr "Merke als ungesehen (WL-DB)" + +msgctxt "#32601" +msgid "Error opening WL-database" +msgstr "Öffnen der Gesehen-Datenbank" + +msgctxt "#32602" +msgid "Error loading WL-database" +msgstr "Laden der Gesehen-Datenbank" + +msgctxt "#32603" +msgid "Error loading Kodi-database" +msgstr "Laden der Kodi-Datenbank" + +msgctxt "#32604" +msgid "Error updating WL database" +msgstr "Aktualisieren der Gesehen-Datenbank" + +msgctxt "#32605" +msgid "Error updating Kodi-database" +msgstr "Aktualisieren der Kodi-Datenbank" + +msgctxt "#32606" +msgid "Writing the WL-database file %s" +msgstr "Beim Schreiben der Gesehen-Datenbank %s" + +msgctxt "#32607" +msgid "Unable to merge dropbox data" +msgstr "Zusammenführung mit Dropbox" + +msgctxt "#32610" +msgid "%d/%d: %s" +msgstr "%d/%d: %s" + +msgctxt "#32611" +msgid "%d/%d: %s S%02dE%02d" +msgstr "%d/%d: %s S%02dE%02d" + +msgctxt "#32708" +msgid "Dropbox Error" +msgstr "Dropbox Fehler" + +msgctxt "#32709" +msgid "Unable to upload" +msgstr "Hochladen nicht möglich" + +msgctxt "#32710" +msgid "Unable to download" +msgstr "Herunterladen nicht möglich" + +msgctxt "#32711" +msgid "Dropbox-Import (Movies)" +msgstr "Dropbox-Import (Filme)" + +msgctxt "#32712" +msgid "Dropbox-Import (Episodes)" +msgstr "Dropbox-Import (Episoden)" + +msgctxt "#32713" +msgid "Dropbox synchronization" +msgstr "Dropbox-Synchronisierung" + +msgctxt "#32714" +msgid "Upload successfull" +msgstr "Hochladen erfolgreich" + +msgctxt "#32715" +msgid "Empty authorization code in settings" +msgstr "Leerer Autorisierungscode in Einstellungen" + +msgctxt "#32716" +msgid "Update the Dropbox database: ..." +msgstr "Aktualisiere die Dropbox-Datenbank: ..." + +msgctxt "#32717" +msgid "Dropbox database updated: %d entries." +msgstr "Dropbox-Datenbank aktualisiert. %d Einträge." + +msgctxt "#32718" +msgid "Dropbox-Export (Movies)" +msgstr "Dropbox-Export (Filme)" + +msgctxt "#32719" +msgid "Dropbox-Export (Episodes)" +msgstr "Dropbox-Export (Episoden)" + +msgctxt "#32720" +msgid "Error importing python Dropbox API. Disable function." +msgstr "Fehler beim Laden der Dropbox API. Deaktiviere Funktion." diff --git a/service.watchedlist/resources/language/Hebrew/strings.po b/service.watchedlist/resources/language/Hebrew/strings.po new file mode 100644 index 0000000000..f9ac64a965 --- /dev/null +++ b/service.watchedlist/resources/language/Hebrew/strings.po @@ -0,0 +1,410 @@ +# XBMC Media Center language file +# Addon Name: WatchedList +# Addon id: service.watchedlist +# Addon version: 1.1.0 +# Addon Provider: schapplm +msgid "" +msgstr "" +"Project-Id-Version: XBMC-Addons\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: 2016-03-28 16:47+0000\n" +"PO-Revision-Date: 2017-10-09 11:16+0300\n" +"Last-Translator: A. Dambledore\n" +"Language-Team: Eng2Heb\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" + + +msgctxt "Addon Summary" +msgid "Save watched Movies and TV Episodes independent from filenames" +msgstr "שומר מצב נצפה לסרטים ופרקים טלוויזיה עצמאית משמות קבצים" + +msgctxt "Addon Description" +msgid "" +"Export: Searches the Kodi-Database for watched files. Determine imdb-id and thetvdb-id to identify movies and TV-episodes. Then save the list to a new independent table.\n" +"Import: Set the watched state for each video file in Kodi.\n" +"Automatic background process without user interaction." +msgstr "" +"ייצוא: מחפש את מסד הנתונים של קודי עבור קבצים שנצפו. קבע את iddb-id ו- thetvdb-id כדי לזהות סרטים ופרטי טלוויזיה. לאחר מכן שמור את הרשימה בטבלה עצמאית חדשה.\n" +"ייבוא: הגדר את מצב נצפה עבור כל קובץ וידאו בקודי.\n" +"תהליך רקע אוטומטי ללא אינטראקציה עם המשתמש." + +msgctxt "Addon Disclaimer" +msgid "This script does not regard the file format. A Movie watched in DVD quality will also set\tthe 1080p version as watched." +msgstr "הסקריפט הזה אינו מתייחס לתבנית הקובץ. סרט שנצפה באיכות DVD, יקבע גם את גרסת 1080p כנצפה." + +# Notification headers +msgctxt "#30001" +msgid "Basic Settings" +msgstr "הגדרות בסיסיות" + +msgctxt "#30002" +msgid "Database" +msgstr "מסד נתונים" + +msgctxt "#30011" +msgid "Update movies watched state" +msgstr "עדכון מצב נצפה לסרטים" + +msgctxt "#30012" +msgid "Update episodes watched state" +msgstr "עדכון מצב נצפה לפרקים" + +msgctxt "#30013" +msgid "autostart mode" +msgstr "מצב אתחול אוטומטי" + +msgctxt "#30014" +msgid "Update interval [hours]" +msgstr "מרווח בין עדכונים (שעות)" + +msgctxt "#30015" +msgid "Startup delay befor action [minutes]" +msgstr "השהיית הפעלה לפני פעולה [דקות]" + +msgctxt "#30016" +msgid "Progress Dialog" +msgstr "דיאלוג התקדמות" + +msgctxt "#30017" +msgid "Use non-default database file" +msgstr "השתמש בקובץ מסד נתונים שאינו ברירת מחדל" + +msgctxt "#30018" +msgid "Path to database file" +msgstr "הנתיב אל קובץ מסד הנתונים" + +msgctxt "#30019" +msgid "Database filename (SQLite Database, *.db)" +msgstr "שם הקובץ של מסד הנתונים (מסד הנתונים SQLite, * .db)" + +msgctxt "#30020" +msgid "Number of backup copies (-1=infinite)" +msgstr "מספר עותקי גיבוי (-1 = אינסופי)" + +msgctxt "#30021" +msgid "Start with Kodi (autorun)" +msgstr "להתחיל עם קודי (הפעלה אוטומטית)" + +msgctxt "#30022" +msgid "follow user setting watched/unwatched" +msgstr "עקוב אחר הגדרות המשתמש למצב נצפה או לא נצפה" + +msgctxt "#30023" +msgid "Dropbox Auth Code" +msgstr "קוד אימות בדרופבוקס" + +msgctxt "#30024" +msgid "Amount of User Information" +msgstr "כמות מידע משתמש" + +msgctxt "#30025" +msgid "All" +msgstr "הכול" + +msgctxt "#30026" +msgid "Only Info" +msgstr "רק מידע" + +msgctxt "#30027" +msgid "Only Warning" +msgstr "רק אזהרה" + +msgctxt "#30028" +msgid "Only Error" +msgstr "רק שגיאה" + +msgctxt "#30029" +msgid "None" +msgstr "ללא" + +msgctxt "#30030" +msgid "no update" +msgstr "אין עדכון" + +msgctxt "#30031" +msgid "one update" +msgstr "עדכון אחד" + +msgctxt "#30032" +msgid "periodic" +msgstr "תקופתיים" + +# empty strings from id 30033 to 30039 +msgctxt "#30040" +msgid "DB Method" +msgstr "שיטת DB" + +msgctxt "#30041" +msgid "File" +msgstr "קובץ" + +msgctxt "#30042" +msgid "MySQL" +msgstr "MySQL" + +msgctxt "#30043" +msgid "Options for accessing SQLite-DB-File" +msgstr "אפשרויות לגישה SQLite-DB-File" + +msgctxt "#30044" +msgid "Options for accessing MySQL-Server" +msgstr "אפשרויות לגישה שרת MySQL" + +msgctxt "#30045" +msgid "Dropbox synchronization" +msgstr "סנכרון דרופבוקס" + +# empty strings from id 30046 to 30050 +msgctxt "#30051" +msgid "MySQL Server" +msgstr "MySQL שרת" + +msgctxt "#30052" +msgid "MySQL Database" +msgstr "MySQL מסד הנתונים" + +msgctxt "#30053" +msgid "MySQL Username" +msgstr "MySQL שם המשתמש" + +msgctxt "#30054" +msgid "MySQL Password" +msgstr "MySQL סיסמה" + +msgctxt "#30055" +msgid "MySQL Port" +msgstr "MySQL פורט" + +# empty strings from id 30056 to 32000 +msgctxt "#32001" +msgid "manually run the database update again?" +msgstr "להפעיל באופן ידני את העדכון מסד הנתונים שוב?" + +msgctxt "#32002" +msgid "DB path does not exist: %s" +msgstr "לא קיים נתיב לבסיס הנתונים: %s" + +msgctxt "#32003" +msgid "next update in %d hours" +msgstr "העדכון הבא בעוד %d שעות" + +msgctxt "#32004" +msgid "begin update in %d minutes" +msgstr "העדכון הבא בעוד %d דקות" + +msgctxt "#32005" +msgid "running observation of changes of watched states" +msgstr "מריץ סריקה לשינויים במצב נצפה" + +# empty strings from id 32006 to 32100 +msgctxt "#32101" +msgid "WatchedList" +msgstr "רשימת נצפה" + +msgctxt "#32102" +msgid "Watchedlist: Error" +msgstr "רשימת נצפה: שגיאה" + +msgctxt "#32103" +msgid "Load watched states from addon-database..." +msgstr "טוען מצב נצפה מתוך מסד הנתונים של ההרחבה..." + +msgctxt "#32104" +msgid "load watched states Kodi-database ..." +msgstr "טוען מצב נצפה מתוך מסד הנתונים של קודי..." + +msgctxt "#32105" +msgid "update the addon watched-database ..." +msgstr "עדכון את מצב נצפה במסד הנתונים של ההרחבה..." + +msgctxt "#32106" +msgid "update the Kodi-database ..." +msgstr "עדכון את מסד הנתונים של קודי..." + +msgctxt "#32107" +msgid "update complete." +msgstr "העדכון הושלם..." + +msgctxt "#32108" +msgid "Watchedlist: MySQL Error (WL DB)" +msgstr "רשימת נצפה: שגיאה MySQL (WL DB)" + +msgctxt "#32109" +msgid "Watchedlist: SQLite Error (WL DB)" +msgstr "רשימת נצפה: שגיאה SQLite (WL DB)" + +# empty strings from id 32110 to 32201 +msgctxt "#32202" +msgid "Addon movie database" +msgstr "מסד נתוני סרטים של ההרחבה" + +msgctxt "#32203" +msgid "Addon episode database" +msgstr "מסד נתוני פרקים של ההרחבה" + +msgctxt "#32204" +msgid "Kodi-movie database" +msgstr "מסד נתוני סרטים של קודי" + +msgctxt "#32205" +msgid "Kodi-episode database" +msgstr "מסד נתוני פרקים של קודי" + +# empty strings from id 32206 to 32207 +msgctxt "#32208" +msgid "Access Denied (username/password/rights)" +msgstr "הגישה נדחתה (שם משתמש/סיסמה/זכויות)" + +msgctxt "#32209" +msgid "Database '%s' does not exist" +msgstr "מסד הנתונים '%s' אינו קיים" + +msgctxt "#32210" +msgid "Access denied (user '%s', DB '%s')" +msgstr "הגישה נדחתה (המשתמש '%s', DB '%s')" + +# empty strings from id 32211 to 32296 +msgctxt "#32297" +msgid "TV-Show \"%s\" (ID %d) has no imdb-number. No processing with WL possible. Try rescraping." +msgstr "סדרות \"%s\" (מזהה %d) אין מספר-imdb. אין אפשרות לעבד עם WL. נסה לבצע סקרייפינג מחדש." + +msgctxt "#32298" +msgid "%d movies and %d episodes read from WL." +msgstr "%d סרטים, פרקים %d נקראו מ-WL." + +msgctxt "#32299" +msgid "%d movies and %d episodes read from Kodi-DB." +msgstr "%d סרטים, פרקים %d נקראו ממסד הנתונים של קודי." + +# empty string with id 32300 +msgctxt "#32301" +msgid "%d watched new. %d updated" +msgstr "%d מצב נצפה חדש. %d עודכן" + +msgctxt "#32302" +msgid "%d movies marked as watched" +msgstr "%d סרטים שסומנו כנצפה" + +msgctxt "#32303" +msgid "%d episodes marked as watched" +msgstr "%d פרקים שסומנו כנצפה" + +# Error Messages +# empty strings from id 32304 to 32400 +msgctxt "#32401" +msgid "Set as watched (Kodi-DB)" +msgstr "הגדר כנצפה (קודי-DB)" + +msgctxt "#32402" +msgid "Remember as newly watched (WL-DB)" +msgstr "זכור כנצפה לאחרונה (WL-DB)" + +msgctxt "#32403" +msgid "Remember as watched (updated) (WL-DB)" +msgstr "זכור כנצפה (מעודכן) (WL-DB)" + +msgctxt "#32404" +msgid "Set as not watched (Kodi-DB)" +msgstr "הגדר כלא נצפה (קודי-DB)" + +msgctxt "#32405" +msgid "Remember as not watched (WL-DB)" +msgstr "זכור כלא נצפה (WL-DB)" + +# Notification text +# empty strings from id 32406 to 32600 +msgctxt "#32601" +msgid "Error opening WL-database" +msgstr "שגיאה בפתיחת מסד הנתונים WL" + +msgctxt "#32602" +msgid "Error loading WL-database" +msgstr "שגיאה בטעינת מסד הנתונים WL" + +msgctxt "#32603" +msgid "Error loading Kodi-database" +msgstr "שגיאה בטעינת מסד הנתונים של קודי" + +msgctxt "#32604" +msgid "Error updating WL database" +msgstr "שגיאה בעת עדכון מסד נתונים WL" + +msgctxt "#32605" +msgid "Error updating Kodi-database" +msgstr "שגיאה בעת עדכון מסד נתונים קודי" + +msgctxt "#32606" +msgid "Writing the WL-database file %s" +msgstr "כתיבת קובץ WL-מסד הנתונים %s" + +msgctxt "#32607" +msgid "Unable to merge dropbox data" +msgstr "אין אפשרות למזג נתונים מדרופבוקס" + +# Progress Dialog +# empty string with id 32609 +msgctxt "#32610" +msgid "%d/%d: %s" +msgstr "% d/%d: %s" + +msgctxt "#32611" +msgid "%d/%d: %s S%02dE%02d" +msgstr "% d/%d: %s S % 02dE % 02d" + +# Dropbox related messages +# empty strings from id 32612 to 32707 +msgctxt "#32708" +msgid "Dropbox Error" +msgstr "שגיאה בדרופבוקס" + +msgctxt "#32709" +msgid "Unable to upload" +msgstr "לא מצליח להעלות" + +msgctxt "#32710" +msgid "Unable to download" +msgstr "לא מצליח להוריד" + +msgctxt "#32711" +msgid "Dropbox-Import (Movies)" +msgstr "דרופבוקס-ייבוא (סרטים)" + +msgctxt "#32712" +msgid "Dropbox-Import (Episodes)" +msgstr "דרופבוקס-ייבוא (פרקים)" + +msgctxt "#32713" +msgid "Dropbox synchronization" +msgstr "סנכרון דרופבוקס" + +msgctxt "#32714" +msgid "Upload successfull" +msgstr "העלאה הסתיימה בהצלחה" + +msgctxt "#32715" +msgid "Empty authorization code in settings" +msgstr "קוד אישור ריק בהגדרות" + +msgctxt "#32716" +msgid "Update the Dropbox database: ..." +msgstr "עדכן את מסד הנתונים של דרופבוקס:..." + +msgctxt "#32717" +msgid "Dropbox database updated: %d entries." +msgstr "דרופבוקס - עדכון מסד הנתונים: %d ערכים." + +msgctxt "#32718" +msgid "Dropbox-Export (Movies)" +msgstr "דרופבוקס-ייצוא (סרטים)" + +msgctxt "#32719" +msgid "Dropbox-Export (Episodes)" +msgstr "דרופבוקס-ייצוא (פרקים)" + +msgctxt "#32720" +msgid "Error importing python Dropbox API. Disable function." +msgstr "שגיאת ייבוא מפתח פיתון Dropbox API. בטל את הפונקציה." diff --git a/service.watchedlist/resources/settings.xml b/service.watchedlist/resources/settings.xml new file mode 100644 index 0000000000..15a5082f33 --- /dev/null +++ b/service.watchedlist/resources/settings.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d45cd68412a7aa8e3ca66fa40801ff9705fce985 Mon Sep 17 00:00:00 2001 From: "Moritz Schappler (MSPC2011)" Date: Sat, 16 Dec 2017 14:07:55 +0100 Subject: [PATCH 2/2] [service.watchedlist] 1.2.1: Changes from https://github.com/xbmc/repo-scripts/pull/679 --- service.watchedlist/.gitignore | 3 --- service.watchedlist/addon.xml | 13 ++++++------- service.watchedlist/default.py | 7 ++++--- service.watchedlist/lib/watchedlist/utils.py | 12 +++--------- .../{German => resource.language.de_de}/strings.po | 0 .../{English => resource.language.en_gb}/strings.po | 0 .../{Hebrew => resource.language.he_il}/strings.po | 0 7 files changed, 13 insertions(+), 22 deletions(-) delete mode 100644 service.watchedlist/.gitignore rename service.watchedlist/resources/language/{German => resource.language.de_de}/strings.po (100%) rename service.watchedlist/resources/language/{English => resource.language.en_gb}/strings.po (100%) rename service.watchedlist/resources/language/{Hebrew => resource.language.he_il}/strings.po (100%) diff --git a/service.watchedlist/.gitignore b/service.watchedlist/.gitignore deleted file mode 100644 index 3677a81970..0000000000 --- a/service.watchedlist/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.pyo -*.pyc -*~ diff --git a/service.watchedlist/addon.xml b/service.watchedlist/addon.xml index 3bf6ee26e3..f16a51e5a7 100644 --- a/service.watchedlist/addon.xml +++ b/service.watchedlist/addon.xml @@ -5,7 +5,6 @@ provider-name="schapplm"> - @@ -17,18 +16,18 @@ -

Save watched Movies and TV Episodes independent from filenames - Gesehen-Status von Filmen und Serien-Episoden unabhängig von Dateinamen speichern - + Save watched state of movies and TV-series episodes independent from filenames + Gesehen-Status von Filmen und Serien-Episoden unabhängig von Dateinamen speichern + Export: Searches the Kodi-Database for watched files. Determine imdb-id and thetvdb-id to identify movies and TV-episodes. Then save the list to a new independent table. Import: Set the watched state for each video file in Kodi. Automatic background process without user interaction. - + Export: Durchsucht die Kodi-Datenbank nach gesehenen Filmen und ordnet den Dateien die eindeutigen Kennnummern von imdb und TheTVDB zu. Gesehene Filme und Episoden werden jeweils in einer eigenen Datenbank gespeichert. Import: Der Gesehen-Status von Medien in Kodi wird aus der Gesehenen-Tabelle geholt. Die Synchronisierung geschieht automatisch im Hintergrund ohne Benutzereingriff. - This script does not regard the file format. A Movie watched in DVD quality will also set the 1080p version as watched. - Dieses Script ignoriert das Dateiformat. Ein gesehener Film in DVD-Qualität wird auch in der BlueRay-Version als gesehen gekennzeichnet. + This script does not regard the file format. A Movie watched in DVD quality will also set the 1080p version as watched. + Dieses Script ignoriert das Dateiformat. Ein gesehener Film in DVD-Qualität wird auch in der BlueRay-Version als gesehen gekennzeichnet. all GNU GENERAL PUBLIC LICENSE. Version 2, June 1991 http://forum.kodi.tv/showthread.php?tid=164652 diff --git a/service.watchedlist/default.py b/service.watchedlist/default.py index f8188fe560..499d84b224 100644 --- a/service.watchedlist/default.py +++ b/service.watchedlist/default.py @@ -17,13 +17,14 @@ except ImportError: sys.stderr.write("Error: " + "You must add org.python.pydev.debug.pysrc to your PYTHONPATH.") - utils.showNotification('WatchedList Error', 'remote debug could not be imported.') + utils.showNotification('WatchedList Error', 'remote debug could not be imported.', xbmc.LOGERROR) sys.exit(1) except: - utils.showNotification('WatchedList Error', 'remote debug in pydev is activated, but remote server not responding.') + utils.showNotification('WatchedList Error', 'remote debug in pydev is activated, but remote server not responding.', xbmc.LOGERROR) sys.exit(1) # Run the program -xbmc.sleep(1500) # wait 1.5 seconds to prevent import-errors +if xbmc.Monitor().waitForAbort(1.5): # wait 1.5 seconds to prevent import-errors. TODO: Is this workaround still necessary? + sys.exit(0) # Abort was requested while waiting. utils.log("WatchedList Database Service starting...") WatchedList().runProgram() diff --git a/service.watchedlist/lib/watchedlist/utils.py b/service.watchedlist/lib/watchedlist/utils.py index 6da514ec82..055b99f837 100644 --- a/service.watchedlist/lib/watchedlist/utils.py +++ b/service.watchedlist/lib/watchedlist/utils.py @@ -8,17 +8,11 @@ import buggalo import re import random +import json _addon_id = u'service.watchedlist' _Addon = xbmcaddon.Addon(_addon_id) -# XBMC-JSON -if sys.version_info < (2, 7): - import simplejson -else: - import json as simplejson - - def data_dir(): """"get user data directory of this addon. according to http://wiki.xbmc.org/index.php?title=Add-on_Rules#Requirements_for_scripts_and_plugins @@ -153,10 +147,10 @@ def executeJSON(request): Args: request: Dictionary with JSON-RPC Commands """ - rpccmd = simplejson.dumps(request) # create string from dict + rpccmd = json.dumps(request) # create string from dict json_query = xbmc.executeJSONRPC(rpccmd) json_query = unicode(json_query, 'utf-8', errors='ignore') - json_response = simplejson.loads(json_query) + json_response = json.loads(json_query) # in case of exception this will be sent buggalo.addExtraData('len(json_query)',len(json_query)); buggalo.addExtraData('len(json_response)', len(json_response)); diff --git a/service.watchedlist/resources/language/German/strings.po b/service.watchedlist/resources/language/resource.language.de_de/strings.po similarity index 100% rename from service.watchedlist/resources/language/German/strings.po rename to service.watchedlist/resources/language/resource.language.de_de/strings.po diff --git a/service.watchedlist/resources/language/English/strings.po b/service.watchedlist/resources/language/resource.language.en_gb/strings.po similarity index 100% rename from service.watchedlist/resources/language/English/strings.po rename to service.watchedlist/resources/language/resource.language.en_gb/strings.po diff --git a/service.watchedlist/resources/language/Hebrew/strings.po b/service.watchedlist/resources/language/resource.language.he_il/strings.po similarity index 100% rename from service.watchedlist/resources/language/Hebrew/strings.po rename to service.watchedlist/resources/language/resource.language.he_il/strings.po