From a48ddaa9e9027799023613a408414f7b3e480fe5 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Sun, 28 Feb 2010 20:03:21 +0100 Subject: [PATCH] import --- LICENSE | 661 +++++++++++++++++++++++++++++++++++++ cgi-bin/cgi-extend.rb | 427 ++++++++++++++++++++++++ cgi-bin/debugn.rb | 45 +++ cgi-bin/epg.rb | 315 ++++++++++++++++++ cgi-bin/i18n.rb | 111 +++++++ cgi-bin/index.rb | 12 + cgi-bin/main.rb | 250 ++++++++++++++ cgi-bin/options.rb | 71 ++++ cgi-bin/records.rb | 123 +++++++ cgi-bin/sar.rb | 17 + cgi-bin/searchandrecord.rb | 82 +++++ cgi-bin/style.rb | 115 +++++++ cgi-bin/template.rb | 31 ++ cgi-bin/timer.rb | 172 ++++++++++ cgi-bin/v2h.rb | 110 ++++++ cgi-bin/vdr.rb | 1 + cgi-bin/vdr/channel.rb | 50 +++ cgi-bin/vdr/epg.rb | 251 ++++++++++++++ cgi-bin/vdr/records.rb | 53 +++ cgi-bin/vdr/sar.rb | 126 +++++++ cgi-bin/vdr/svdrp.rb | 107 ++++++ cgi-bin/vdr/timer.rb | 347 +++++++++++++++++++ cgi-bin/vdr/vdr.rb | 259 +++++++++++++++ lighttpd/vdr.conf | 27 ++ 24 files changed, 3763 insertions(+) create mode 100644 LICENSE create mode 100644 cgi-bin/cgi-extend.rb create mode 100644 cgi-bin/debugn.rb create mode 100644 cgi-bin/epg.rb create mode 100644 cgi-bin/i18n.rb create mode 100755 cgi-bin/index.rb create mode 100644 cgi-bin/main.rb create mode 100644 cgi-bin/options.rb create mode 100644 cgi-bin/records.rb create mode 100644 cgi-bin/sar.rb create mode 100755 cgi-bin/searchandrecord.rb create mode 100644 cgi-bin/style.rb create mode 100644 cgi-bin/template.rb create mode 100644 cgi-bin/timer.rb create mode 100644 cgi-bin/v2h.rb create mode 100644 cgi-bin/vdr.rb create mode 100644 cgi-bin/vdr/channel.rb create mode 100644 cgi-bin/vdr/epg.rb create mode 100644 cgi-bin/vdr/records.rb create mode 100644 cgi-bin/vdr/sar.rb create mode 100644 cgi-bin/vdr/svdrp.rb create mode 100644 cgi-bin/vdr/timer.rb create mode 100644 cgi-bin/vdr/vdr.rb create mode 100644 lighttpd/vdr.conf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/cgi-bin/cgi-extend.rb b/cgi-bin/cgi-extend.rb new file mode 100644 index 0000000..3b7aa1a --- /dev/null +++ b/cgi-bin/cgi-extend.rb @@ -0,0 +1,427 @@ +require 'cgi' +require 'uri' + +class Tagen + def method_missing *p, &e + self.class.method_missing *p, &e + end + + class <" if e + #"#{b}\n" + t += %w[/] unless e + b = "<#{t.join ' '}>" + b += "#{e.call}" if e + b + end + alias tag method_missing + + def classes pre, *p + p.collect do |i| + pre = nil if '' == pre + i = nil if '' == i + if pre && i + "#{pre}_#{i}" + else + (pre || i).to_s + end + end.join ' ' + end + end +end + +class Tagen::ConfTable ["r#{i%2}", x]) do + @tagen.td( :class => :r0) do + self[i][0] + end + + @tagen.td( :class => :r1) do + self[i][1] + end + end + end + + def gen + @tagen.table( :class => @name) do + (0...self.size).collect do |i| + self.gen_line i + end.join + end + end +end + +class Tagen::Input + def initialize cgi = nil, data = nil + @cgi, @data = cgi || Tagen.new, data || {} + end + + def time n, f = '%h:%m' + self.digits( "#{n}_hour", Time.now.hour, 2) + ' : ' + + self.digits( "#{n}_min", Time.now.min, 2) + end + + def date n, f= '%h:%m' + self.digits( "#{n}_day", Time.now.day, 2) + ' . ' + + self.digits( "#{n}_month", Time.now.month, 2) + ' . ' + + self.digits( "#{n}_year", Time.now.year, 4) + end + + def digits n, v = nil, d = nil, c = nil + n = n.to_s + v ||= '' + d = d ? { :size => d, :length => d } : {} + @cgi.input d.update( :class => [:digits, c], :type => :text, :name => n, + :id => n, :value => @data[ n]||v) + end +end + +class Tagen::InputRadio 'white-space: nowrap') do + @tagen.input( :type => :radio, :name => @name, :value => k[0], + :id => "#{@name}_#{k[0]}", :checked => @checked == k[0]) + + @tagen.label( :for => "#{@name}_#{k[0]}") { k[1] } + end + end.join ' ' + end +end + +class Tagen::Select uri) do + @tagen.select( :name => @name, :id => @name) do + body = self.collect do |k| + @tagen.option( :value => k[0], + :selected => @selected == k[0]) { k[1] } + end.join + end + @tagen.input( :type => :submit) + end + end +end + +class Tagen::SelectMenu [:select, css_class]) do + @tagen.span( :class => :selector) { @title }+ + @tagen.span( :class => :subselect) do + (0...self.length).collect do |i| + k = self[ i] + u = uri.dup + if @name + u.query = "#{u.query && "#{u.query}&"}#{@name}=#{k[0]}" + else + u.path += "/#{k[0]}" + end + @tagen.a( :href => u.to_s) { k[1] } + end.join @tagen.span( :class => %w{spacer hide}) { ' | ' } + end + end + end +end + +class Tagen::Table '...' or :value => lambda{...})" + end + + @title, @class = opts[:title], opts[:class] + + if x = opts[:sort] + self.gen_meth :sort, x + elsif x = opts[:compare] || opts[:name] + if x.kind_of? Proc + self.gen_meth :sort do |i| + i.sort &x + end + else + eval <<-EOS + class < b.#{x} + end + end + end + EOS + end + end + end + + def gen_meth name, call=nil, &e + call = e if e + raise ArgumentError, 'I need something to call.' unless call + o = if call.kind_of? Proc + eval "@#{name} = call" + "@#{name}.call o" + else "o.#{call}" + end + eval <<-EOM + class < @name, :class => :table do + @tagen.thead do + @tagen.tr do + self.gen_abstract_th self do |a, i, p| + t = a[i].title + l, d = if @sort_by == i + if @reverse + [ i, 'sorted reverse' ] + else [ ~i, 'sorted' ] + end + else [ i, 'normal' ] + end + @tagen.th :class => [p, d, "r#{i%2}"] do + if a[i].respond_to? :sort + @tagen.a :href => "?#{@name}_sortBy=#{l}" do + t + end + else t + end + end + end + end + end + + @tagen.tbody do + self.gen_abstract_tr self.sort( array) do |a, i, p| + @tagen.tr :class => [p, "row#{i}", "r#{i%2}"] do + self.gen_abstract_td self do |b, j, p| + @tagen.td :class => [p, "c#{j%2}", "col#{j}", @sort_by == j ? 'sorted' : ''] do + b[j].value a[i] + end + end + end + end + end + end + end +end + +class Tagen::Directory < Tagen::Table + attr_reader :opened + + def initialize *p + super( *p) + @opened = [] + end + + def opened= value + unless [ '', nil ].include? value + value.split( ',').each &method( 'open=') + end + end + + def open value + value = value.split '.' if value.kind_of? String + raise "Unexpected type of value instead Array: #{value.class}" unless value.kind_of? Array + @opened.push value.collect {|x| x.to_i } + end + alias open= open + + def close value + value = value.split '.' if value.kind_of? String + raise "Unexpected type of value instead Array: #{value.class}" unless value.kind_of? Array + @opened.delete value.collect {|x| x.to_i } + end + alias close= close + + def generate array + @tagen.table :id => @name, :class => :table do + @tagen.thead do + @tagen.tr do + self.gen_abstract self do |a, i, p| + t = a[i].title + l, d = if @sort_by == i + if @reverse + [ i, 'sorted reverse' ] + else [ ~i, 'sorted' ] + end + else [ i, 'normal' ] + end + @tagen.th :class => [p, d, "r#{i%2}"] do + if a[i].respond_to? :sort + @tagen.a :href => "?#{@name}_sortBy=#{l}" do + t + end + else t + end + end + end + end + end + + @tagen.tbody do + self.gen_abstract self.sort( array) do |a, i, p| + @tagen.tr :class => [p, "row#{i}", "r#{i%2}"] do + self.gen_abstract self do |b, j, p| + @tagen.td :class => [p, "c#{j%2}", "col#{j}", @sort_by == j ? 'sorted' : ''] do + b[j].value a[i] + end + end + end + end + end + end + end +end + +class Tagen::VerticalLinks 'vertical-links') do + super Tagen.span( :class => 'spacer') {' | '} + end + end + alias to_s join +end + +class Tagen::WebSearch + attr_accessor :text, :tagen + def icon() '' end + def title() '' end + + def initialize text = nil + @text, @tagen = text, Tagen.new + end + + def gen text = nil + raise ArgumentError, 'No Title to search' unless text ||= @text + t = self.icon ? @tagen.img( :src => self.icon) : '' + t += self.title || '' + @tagen.a( :href => self.uri( text), :target => :_blank) { t } + end +end + +class Tagen::IMDb < Tagen::WebSearch + def uri( t) "http://imdb.com/find?s=all&q=#{CGI.escape t}" end + def icon() 'http://imdb.com/favicon.ico' end +end + +class Tagen::Wikipedia < Tagen::WebSearch + def uri( t) + "http://de.wikipedia.org/wiki/Spezial:Suche?go=Artikel&search=#{CGI.escape t}" + end + def icon() 'http://de.wikipedia.org/favicon.ico' end +end diff --git a/cgi-bin/debugn.rb b/cgi-bin/debugn.rb new file mode 100644 index 0000000..ce2c735 --- /dev/null +++ b/cgi-bin/debugn.rb @@ -0,0 +1,45 @@ +def Kernel.debug( line) + STDERR.puts "#{caller[0]}: #{line}" +end + +def debug_func c, f + ff = case f + when /^(.*)\?$/ then "#{$1}_f" + when /^(.*)\!$/ then "#{$1}_a" + when /^(.*)=$/ then "#{$1}_g" + when "<<" then "_s" + when "+" then "_p" + when "-" then "_m" + when "@+" then "_P" + when "@-" then "_M" + else "#{f}_n" + end + wf = "__wrapped_#{c.object_id.to_s.sub /^-/, "x"}_#{ff}__".intern + return "#{c}##{f} already exists" if c.instance_methods.include? wf + pre = "\#{\"%x\"%self.hash.abs}:#{c}##{f}" + c.class_eval <<-EOF + alias #{wf} #{f} + def #{f} *args, &e + ret = if e + STDERR.puts "==>#{pre} \#{args.collect {|i| i.inspect }.join +", "}, &\#{e.inspect}" + #{wf} *args, &e + else + STDERR.puts "==>#{pre} \#{args.collect {|i| i.inspect }.join +", "}" + #{wf} *args + end + #STDERR.puts "<==#{pre}" + ret + rescue Object + STDERR.puts "<==#{pre} EXCEPTION: \#{$!.inspect}" + Kernel.raise + end + EOF +end + +def debug_class c, fs + c.instance_methods.grep fs do |f| + debug_func c, f + end +end diff --git a/cgi-bin/epg.rb b/cgi-bin/epg.rb new file mode 100644 index 0000000..e284c23 --- /dev/null +++ b/cgi-bin/epg.rb @@ -0,0 +1,315 @@ +require 'v2h' +require 'cgi' + +class V2H::EPG @conf.urlgen( "epg/at:#{t.to_i}") } + _a.update :class => :now if day..(day.in 1.day) === t + t = cgi.a( _a) { t.strftime -'%m-%d' } + sm = Tagen::SelectMenu.new t, ts + links.push sm.gen( @conf.urlgen, :timeselect) + end + links.push '' + t = time.in -@dura + links.push cgi.a( :href => @conf.urlgen( 'epg', "at:#{t.to_i}")) { + CGI.escapeHTML "<< #{t.strftime -'%H:%M'}" + } if time > Time.now + t = time.in @dura + links.push cgi.a( :href => @conf.urlgen( 'epg/now')) { -'Now' } + links.push cgi.a( :href => @conf.urlgen( 'epg', + "at:#{t.to_i}")) { + CGI.escapeHTML "#{t.strftime -'%H:%M'} >>" + } + end.to_s + end + + def get_epg chan = nil, time = nil, dura = nil + vdr, dura = @conf.vdr, dura.to_i + stime = if /^at\s+(\d+)/ =~ time then Time.at $1.to_i end + x = if dura != 0 && stime && !chan + {:between => stime.to_i .. stime.in( dura).to_i} + else + {:channel => chan, :between => stime} + end.darkknight( 'get_epg: %s') + vdr.epg.select( x).to_a + end + + def epg key + chan, time = self.key key + epg, cgi = self.get_epg( chan, time).first, @conf.cgi + raise 'No Entry found' unless epg + start, stop = epg.event.start, epg.event.stop + + body = cgi.h1( :class => :title) do + cgi.a( :class => :record, :href => @conf.urlgen( + 'epg/record', key.join( '/'), :suf => ENV[ 'PATH_INFO'])) do + 'o' + end + ' ' + + epg.title + ' ' + + Tagen::Wikipedia.new.gen( epg.title) + ' ' + + Tagen::IMDb.new.gen( epg.title) + end + + cgi.p do + cgi.b do + cgi.a( :href => @conf.urlgen( 'epg', chan)) do + epg.channel.name + end + + ': ' + + start.strftime( -'%m-%d-%Y') + + ' ' + + cgi.a( :href => @conf.urlgen( 'epg', "at:#{start.to_i}")) do + start.strftime -'%H:%M' + end + + ' - ' + + cgi.a( :href => @conf.urlgen( 'epg', "at:#{stop.to_i}")) do + stop.strftime -'%H:%M' + end + end + end + + cgi.p do + epg.description.gsub '|', cgi.br + end + + Array.new.with do |links| + links.push cgi.link( :rel =>:next, :title => -'Next', + :href => @conf.urlgen( 'epg', "#{chan}/at:#{stop.to_i}")) + V2H::Data.new links, epg.title, body + end + end + + def record key + epg = self.get_epg( *self.key( key).darkknight( 'key => %s')).flatten[0].darkknight + raise NoPage unless epg + start, stop = epg.event.start-5.minutes, epg.event.stop+5.minutes + @conf.redirect 'timer/form/timer/new', key[ 2..-1].join( '/'), + :channel => epg.channel.to_i, + :date_year => start.year, + :date_month => start.month, + :date_day => start.day, + :start_hour => start.hour, + :start_min => start.min, + :stop_hour => stop.hour, + :stop_min => stop.min, + :file => epg.title, + :summ => epg.description + end + + def list key + chan, time = self.key( key) + list_epg self.get_epg( chan, time, @dura), chan, time + end + + def list_epg epg, chan, time + vdr, cgi = @conf.vdr, @conf.cgi + table = Tagen::Table.new 'epg' + table.push :title => -'Date', + :value => lambda { |a| a.event.start.strftime -'%m-%d-%Y' } + table.push :title => -'Start', :name => 'event.start', + :value => lambda { |a| + s = a.event.start + Tagen.a :href => @conf.urlgen( 'epg', "at:#{s.to_i}") do + s.strftime -'%H:%M' + end + } + + table.push :title => -'Stop', :name => 'event.stop', + :value => lambda { |a| + s = a.event.stop + Tagen.a :href => @conf.urlgen( 'epg', "at:#{s.to_i}") do + s.strftime -'%H:%M' + end + } + table.push :title => -'Channel', :name => 'channel.name', + :value => lambda { |a| + c = a.channel + Tagen.a :href => @conf.urlgen( 'epg', c.id) do + c.name + end + } unless chan + table.push :value => lambda { |a| + start, stop = a.event.start, a.event.stop + url = @conf.urlgen 'epg/record', + "#{a.channel.id}/at:#{start.to_i}", + :suf => ENV[ 'PATH_INFO'] + start, stop = start.min+start.hour*60, stop.min+stop.hour*60 + Tagen.a( :class => :record, :href => url) { 'o' } + } + + table.push :title => -'Title', :name => 'title', :value => lambda { |a| + start, stop = a.event.start, a.event.stop + url = @conf.urlgen 'epg', "#{a.channel.id}/at:#{start.to_i}" + start, stop = start.min+start.hour*60, stop.min+stop.hour*60 + rec = a.record?.collect do |i| + case i + when false then :norecord + when true then :fullrecord + when -1 then :postrecord + when 1 then :prerecord + when 0 then :partrecord + end + end + click = Tagen.a( :href => url, :class => rec) do + if (start .. stop).include? 20*60+16 + Tagen.b { CGI.escapeHTML( a.title) } + else + CGI.escapeHTML( a.title) + end + end + click + } + + if time && 'next' != time.to_s + t = time.to_s == 'now' ? Time.now : Time.at( time.match( /\d+/).to_s.to_i) + tagen = table.tagen + eval <<-EOC + def tagen.tbody *p + ret = super *p + #{ _t = t.in -@dura + <<-EOCP if t < Time.now + ret = self.tr( :class => [:link_prev_epg, :r1]) do + self.td( :style => 'text-align: center;', + :class => [:first, :last], + :colspan => #{table.length}) do + self.a( :href => #{ + @conf.urlgen( 'epg', "at:#{_t.to_i}").inspect + }) do + "... #{_t.strftime -'%H:%M'} - #{t.strftime -'%H:%M'} ..." + end + end + end+ ret + EOCP + } + #{ _t = t.in @dura + <<-EOCP + ret += self.tr( :class => [:link_next_epg, :r1]) do + self.td( :style => 'text-align: center;', + :class => [:first, :last], + :colspan => #{table.length}) do + self.a( :href => #{ + @conf.urlgen( 'epg', "at:#{_t.to_i}").inspect + }) do + "... #{_t.strftime -'%H:%M'} - #{_t.in( @dura).strftime -'%H:%M'} ..." + end + end + end + EOCP + } + end + EOC + end + + table.sort_by = @conf.opts['epg_sortBy'] || "1" + body = self.chansel( chan, time) + table.generate( epg) + V2H::Data.new Array.new, -'EPG', body + end + + def find_form key + end + + def find_search key + h = @conf.opts.over.symbolize_keys + list_epg @conf.vdr.epg.find( h), false, nil + end + + def graph time + chan, time = self.key( time) + epg = self.get_epg nil, time, @dura + @tagen.table( :class => :timeline) do + @tagen.thead do + @tagen.tr do + end + end + + @tagen.tbody do + epg.collect do |e| + @tagen.tr do + epg.collect do |f| + @tagen.td do + end + end + end + end + end + end + end +end diff --git a/cgi-bin/i18n.rb b/cgi-bin/i18n.rb new file mode 100644 index 0000000..f4a37d1 --- /dev/null +++ b/cgi-bin/i18n.rb @@ -0,0 +1,111 @@ + +$LANG ||= ENV['LANG'] +$LANG = :en if !$LANG || $LANG.empty? + +class I18N + unless defined? @@translations + @@translations = {} + + class << @@translations + alias :entry :[] unless method_defined? :entry + + def [] key + unless self.entry key + self[ key] = {} + class << self.entry( key) + alias :entry :[] unless method_defined? :entry + + def [] ekey + self.entry( ekey) || ekey + end + end + end + + self.entry key + end + end + end + + class < str, :lang => lang, :trans => rans.inspect }.darkknight + @@translations[lang][str] = trans + end + + def get str, lang = nil + lang ||= $LANG||:en + lang = lang.to_sym + @@translations[lang][str] + end + alias call get + alias [] get + + def translations + @@translations + end + alias hash translations + end +end + +class String + def i18n lang = nil + I18N[ self, lang] + end + + def i18n! lang = nil + self.replace i18n( lang) + end + + def i18n= trans + I18N[ self] = trans + end + + def -@ + self.i18n + end + + alias :element_reference :[] unless method_defined? :element_reference + def [] *par + if par.length == 1 and par[0].class == Symbol + I18N[*par] + else + self.element_reference( *par) + end + end + + alias :element_reference= :[]= unless method_defined? :element_reference= + def []= *par + if par.length == 2 and par[0].class == Symbol + I18N.[]= *par + else + element_reference= *par + end + end + + @@ksub ||= {} + def String.ksub= hash + @@ksub = hash + end + + def ksub hash + self.gsub( /(^|[^\\])\$\{(\w+)\}/) do + $1 + ( hash[$2.to_sym] || hash[$2] || @@ksub[$2.to_sym] || @@ksub[$2] || $2 ) + end + end + + alias :__procent__ :% unless method_defined? :__procent__ + def % par + if par.class == Hash + self.ksub par + else + self.__procent__ par + end + end +end diff --git a/cgi-bin/index.rb b/cgi-bin/index.rb new file mode 100755 index 0000000..ebcb750 --- /dev/null +++ b/cgi-bin/index.rb @@ -0,0 +1,12 @@ +#!/usr/bin/ruby + +$:.unshift File.dirname( __FILE__) +require 'fcgi' +require 'main' + +vdr = VDR.new :conv => 'utf-8', :expire => 5 +environment = ENV.to_hash + +FCGI.each_cgi 'html4' do |cgi| + main vdr, cgi +end diff --git a/cgi-bin/main.rb b/cgi-bin/main.rb new file mode 100644 index 0000000..59e7fd8 --- /dev/null +++ b/cgi-bin/main.rb @@ -0,0 +1,250 @@ + +require 'syslog' +Syslog.open 'vdr-cgi', Syslog::LOG_NDELAY | Syslog::LOG_PERROR, Syslog::LOG_DAEMON + +class Object + def darkknight s = '%s' + Syslog.debug s, self.inspect + self + end +end + +require 'active_support' +require 'zlib' +require 'bdb' + +%w[i18n cgi-extend vdr options v2h style epg timer records].each do |r| + require File.join( File.dirname( __FILE__), r) +end + +EPGDATA = '/exports/vdr/epg.data' + +class String + def no?() !self.yes? end + def yes? + case self.downcase + when 'not', 'no', 'false', '0': false + when 'sure', 'yes', 'true', /^\d+$/: true + else false + end + end +end + +class Object + def with( &e) e.call self end +end + +class IO + def with &e + r = e.call self + self.close + r + end +end + +class I18N + @@translations ||= {} + class <<@@translations + def [] key + unless self.entry( key) + self[ key] = BDB::Hash.new '/var/lib/svdrpd/i18n.db', + key.to_s, BDB::CREATE + class << self.entry( key) + alias :entry :[] unless method_defined? :entry + attr_accessor :unknown + + def [] ekey + unless r = self.entry( ekey) + @unknown.push ekey + @unknown.uniq! + r = ekey + else r + end + end + end + self.entry( key).unknown = [] + end + + self.entry key + end + end +end + +def main vdr, cgi + data, cookies, cgi, env = {}, {}, cgi, cgi.env_table + cgi.params.each {|k, v| data[k] = v[0] } + + cgi.cookies['svdrp.rb'].each do |i| + if i =~ /^(.*?)=(.*)$/ + cookies[$1] = $2 + end + end + + conf = V2H::Conf.new Tagen.new, vdr, Options.new( data, cookies), + env[ 'SCRIPT_NAME'] + path = File.expand_path( env[ 'PATH_INFO'] || '').split '/' + path.shift + class < 'text/css' do + Style.new[ :default] + end + + when 'redirect': conf.redirect conf.opts.over[ 'path'] + when 'epg': V2H::EPG.new( conf)[path] + when 'timer': V2H::Timer.new( conf)[path] + when 'records': V2H::Records.new( conf)[path] + + when 'i18n' + case path.shift + when 'push' + l = cgi.params[ 'language'][ 0] + cgi.params.each do |k, v| + v = v.to_s + if k[ 0...6] == 'trans_' && !v.empty? + I18N.set CGI.unescape( k[ 6..-1]), v, l + end + end + conf.redirect path.join( '/') + + when nil + t = Tagen::Table.new 'translations' + t.push :title => -'Original', :value => lambda { |o| + o = o[ 0].to_s + Tagen.label( :for => "trans_#{CGI.escape( o)}") { o } + } + t.push :title => -'Translation', :value => lambda { |o| + Tagen.input :type => :input, + :id => "trans_#{CGI.escape( o[0].to_s)}", + :name => "trans_#{CGI.escape( o[0].to_s)}", + :value => CGI.escapeHTML( o[1].to_s) + } + u = Tagen.form( :method => :post, + :action => conf.urlgen( 'i18n/push', + env['PATH_INFO'])) do + Tagen.h1 { -'Language:' + " #{$LANG}" } + + Tagen.input( :type => 'hidden', :language => $LANG) + + t.generate( I18N.hash[ $LANG].to_hash.to_a) + + Tagen.input( :type => :submit, :value => -'Submit') + end + V2H::Data.new [], -'Translations', u + + else + V2H::Data.new [], -'Ups' do + -'I don\'t know, what you want. :-/' + end + end + + when 'confirm' + V2H::Data.new [], -'Confirm' do + o = conf.opts.over + text = o['confirm_text'] || '' + text = Zlib::Inflate.inflate text if (o['confirm_compressed']||'').yes? + p = path.join( '/').split '|', 2 + text.gsub( /[\n<>]/, &{"\n" => Tagen.br, '<' => '<', + '>' => '>'}.method( '[]')) + + Tagen.br*2 + + Tagen.a( :href => conf.urlgen( p[0])) { Tagen.b { -'Yes' } } + + ' '*4 + + Tagen.a( :href => conf.urlgen( :var => p[1], + :alt => 'records')) { Tagen.b { -'No' } } + end + + when nil: conf.redirect 'epg' + + else V2H::Data.new [], -'SVDRPD2HTTP', '' + end + + rescue SystemExit + # Everything OK -- raise it again + raise + rescue Errno::ECONNREFUSED + V2H::Data.new [], -'Connection refused', -"I can't connect to server. Check your server." + rescue Object + conf.rescued $! + end + + opts = { + 'type' => 'text/html', + 'Cache-Control' => 'no-cache', + 'Pragma' => 'no-cache' + } + cookies = conf.opts.to_h.collect { |k, v| "#{k}=#{v}" } + opts['cookie'] = CGI::Cookie.new 'svdrp.rb', *cookies unless cookies.empty? + + data ||= conf.unknown_page '' + data = V2H::Data.new [], '', data if data.kind_of? String + + cgi.out opts do + '' + + '' + + Tagen.html( :xmlns => "http://www.w3.org/1999/xhtml", + 'xml:lang' => :de) do + Tagen.head do + Tagen.title do data.title end + + Tagen.meta( 'http-equiv' => :Pragma, :content => 'no-cache') + + Tagen.link( :rel => :stylesheet, :type => 'text/css', + :href => conf.urlgen( 'style')) + + data.head.join + end + + Tagen.body do + links = [ + [ :epg, :EPG ], [ :timer, :Timer ], + [ :records, :Records ], [ :i18n, :Translations ] + ].collect do |l| + Tagen.a( :href => conf.urlgen( l[0])) { -l[1].to_s } + end + links = Tagen::VerticalLinks.new links + + d = Tagen.div( :style => 'text-align: center') { links.to_s } + + Tagen.hr( :class => :spacer) + + data.body + b = Tagen.input :type => :submit, :value => -'Submit' + u = Tagen.h5() { -'Unknown Translations:' } + + t = Tagen::Table.new( 'translations').with do |t| + t.push :title => -'Original', :value => lambda { |o| + Tagen.label( :for => "trans_#{CGI.escape o}") { o.to_s } + } + t.push :title => -'Translation', :value => lambda { |o| + Tagen.input :type => :input, + :id => "trans_#{CGI.escape o}", + :name => "trans_#{CGI.escape o}" + } + end + + c = I18N.translations[ $LANG.to_sym].unknown + u += Tagen.form( :method => :post, :action => + conf.urlgen( 'i18n/push', env['PATH_INFO'])) do + Tagen.input( :type => :hidden, :language => $LANG) + + t.generate( c) + b + end + d + (c.empty? ? '' : u) + end + end + + "\n" + end + +rescue SystemExit + # Everything OK +rescue Object + Syslog.err "#{$!} (#{$!.class}) -- #{$!.backtrace.join"\n\t"}" if $! + begin + if $! + cgi.out { "
Exception: #{CGI.escapeHTML $!.to_s} (#{CGI.escapeHTML $!.class.to_s}), #{CGI.escapeHTML $!.backtrace.join( "
")}
" } + end + end +end diff --git a/cgi-bin/options.rb b/cgi-bin/options.rb new file mode 100644 index 0000000..528bde2 --- /dev/null +++ b/cgi-bin/options.rb @@ -0,0 +1,71 @@ +module Kernel + def expect expect, get, message = nil + if expect.class == Array + ret = true + expect.each do |expect| + return if expect === get + end + else + return if expect === get + end + raise ArgumentError, message || "Expected: #{expect.inspect}, Got: #{get.inspect}", caller unless expect === get + end + + def str_options opts, *list + if opts[-1].kind_of?(Hash) + o = opts.pop + list = o.options *list + (0...list.size).to_a.collect do |i| + opts[i] || list[i] + end.push o + else opts + end + end + + def options opts, *list + opts = opts[0].options *list if opts.size == 1 && opts[0].kind_of?( Hash) + opts + end + + def consts_multiset i, *names + names = names.split /\s+/ if names.kind_of? String + names = names[0].split /\s+/ if names.kind_of?( Array) && names.length == 1 && names[0].kind_of?( String) + names.each do |n| + next if n.empty? + remove_const n if const_defined? n + const_set n, i + end + end +end + +class Hash + def options *list + list.collect do |l| + if l.kind_of? Array + self[ (l & opts.keys)[0] ] + else self[l] + end + end + end +end + +class Options :title) do + rec.title+ ' ' + + Tagen::Wikipedia.new.gen( rec.title)+ ' ' + + Tagen::IMDb.new.gen( rec.title) + end + + cgi.p do + cgi.b do + start.strftime( -'%m-%d-%Y') + + ' ' + + cgi.a( :href => @conf.urlgen( 'epg', "at:#{start.to_i}")) do + start.strftime -'%H:%M' + end + + ' - ' + + cgi.a( :href => @conf.urlgen( 'epg', "at:#{stop.to_i}")) do + stop.strftime -'%H:%M' + end + end + end + + cgi.p { rec.description.gsub '|', cgi.br } + + V2H::Data.new [], -'Record', body + end + + def delete key + if [ ENV[ 'SERVER_ADDR'], '127.0.0.1', '::1' ].include? ENV[ 'REMOTE_ADDR'] + @conf.vdr.delr key.shift + return V2H::Data.new( []), 'Kann nicht loeschen', + 'Sie duerfen nicht loeschen' + end + @conf.redirect :var => key, :alt => 'records' + end + + def list dir = [] + cgi = @conf.cgi + table = Tagen::Table.new 'records' + dtable = Tagen::Table.new 'recorddirs' + dtable.sort_by = @conf.opts['recorddirs_sortBy'] || '2' + table.sort_by = @conf.opts['records_sortBy'] || '1' + + dtable.push :title => -'Directory', :name => '[](0)', + :value => lambda { |a| + a = a[0] + Tagen.a( :href => + @conf.urlgen( 'records/list/', a.join( '/')) + ) { a[-1] } + } + dtable.push :title => -'Number of', :name => '[](1)' + dtable.push :title => -'New', :name => '[](2)' + + table.push :title => -'Title', :name => 'title', + :value => lambda { |a| + subtitle = cgi.span( :style => 'font-size: 50%') { ' - \1' } + Tagen.a( :href => @conf.urlgen( 'records/show', a.number), + :class => a.seen? ? 'notseen' : 'seen') do + a.title[-1].gsub( / - ([^~]*)/, subtitle) + end + } + table.push :title => -'Time', :name => 'time', + :value => lambda { |a| a.time.strftime -'%m-%d-%Y %H:%M' } + #table.push :title => -'Number', :name => 'number' + table.push :value => lambda { |a| + @conf.confirm( 'records/delete', a.to_i, :suf => '|records', + :class => :delete, + :txt => -'Do you want to delete record "%s"?' % + a.title.to_s) { 'x' } + } + + recs, dirs = [], Hash.new + @conf.vdr.lstr do |r| + t = r.title + if dir != t[ 0...dir.length] + elsif t.size-1 == dir.length + recs.push r + else + d = t[ 0..dir.length] + e = dirs[ d] || [0,0] + e[0] += 1 + e[1] += 1 if r.seen? + dirs[ d] = e + end + end + dirs = dirs.collect {|k, v| [ k, v[0], v[1] ] } + + page = cgi.p do + stat = @conf.vdr.stat( 'disk')[0][1] + #m = /^\s*(\d+)([a-z]?B)\s+(\d+)([a-z]?B)\s+(\d+)%\s*$/i.match stat + -'Total: %s Free: %s - %s' % stat.split + end + + if dir.empty? then '' + else + Tagen.p do + Tagen.a( :href => @conf.urlgen( 'records/list/')) do + '<<<' + end + + '/' + + (0...dir.size).collect do |i| + Tagen.a( :href => @conf.urlgen( 'records/list/', + dir[ 0..i].join( '/'))) { dir[ i] } + end.join( '/') + end + end + + (dirs.empty? ? '' : dtable.generate( dirs) + Tagen.br)+ + (recs.empty? ? '' : table.generate( recs)) + V2H::Data.new Array.new, -'Records', page + end +end diff --git a/cgi-bin/sar.rb b/cgi-bin/sar.rb new file mode 100644 index 0000000..7191547 --- /dev/null +++ b/cgi-bin/sar.rb @@ -0,0 +1,17 @@ +#TODO +class V2H::Timer ] " +end + +def help + usage +end + +def listtypes + $stderr.puts 'Possible types of records: Movie, Series' +end + +options = { :dryrun => false } + +opts = GetoptLong.new( + [ '--dryrun', '-n', GetoptLong::NO_ARGUMENT ], + [ '--help', '-h', GetoptLong::NO_ARGUMENT ], + [ '--series', '--serie', '-s', GetoptLong::NO_ARGUMENT ], + [ '--movie', '-m', GetoptLong::NO_ARGUMENT ], + [ '--type', '-t', GetoptLong::REQUIRED_ARGUMENT], + [ '--typelist', '--listtypes', '-L', GetoptLong::NO_ARGUMENT ] +) + +opts.each do |o,a| + case o + when '--help': help; exit 0 + when '--typelist': listtypes; exit 0 + when '--dryrun': options[ :dryrun] = true + when '--series': options[ :type] = :Series + when '--movie': options[ :type] = :Movie + when '--type': + if a == 'list' + listtypes + exit 0 + end + options[ :type] = a.to_sym + end +end + +EPGDATA = '/exports/vdr/epg.data' +$:.unshift File.dirname( $0) +require "vdr" + +s = search = VDR::SearchAndRecord.new +s.push /simpsons/i, nil, :Series + +vdr, ecount = s.vdr, 0 +todo = Proc.new do |e| + STDERR.print "\r#{e.event.start.strftime '%W, %H:%M'} #{e.channel.name} - #{e.title}"[0...80]+"\033[J" + if t = search.call( e) + STDERR.puts "\n" + vdr.newt t + end +end + +if options[ :dryrun] + todo = Proc.new do |e| + STDERR.print "\r#{e.event.start.strftime '%W, %H:%M'} #{e.channel.name} - #{e.title}"[0...80]+"\033[J" + STDERR.puts "\n" if search.call e + end +end + +VDR::EPG.parse vdr do |e| + begin + todo.call e + rescue VDR::TimerAlreadyDefined + STDERR.puts "# #{$!.class}" + rescue VDR::VDRError + ecount += 1 + STDERR.puts "Event: #{e.title}" + STDERR.puts "#{ecount}: #{$!.class}: #{$!}" + $!.backtrace.join( "\n").each_line do |l| + STDERR.puts "#{ecount}: #{l}" + end + end +end +STDERR.puts "\r\033[J" diff --git a/cgi-bin/style.rb b/cgi-bin/style.rb new file mode 100644 index 0000000..21d6ec4 --- /dev/null +++ b/cgi-bin/style.rb @@ -0,0 +1,115 @@ +require 'cgi' +require 'sass' + +class Style + def initialize + @styles = { + :default => <<-STYLE.gsub( /\t/, ' ').gsub( /^ {8,8}/, '') + !linkcolor = #0000A0 + !tablecolor = #FAFAFA + !tablecolorsorted = !tablecolor - 8 + !actioncolor = #F00 + !background = #EEE + + + html, body + :background = !background + a + :color = !linkcolor + :text-decoration none + &:hover, &:active + :text-decoration underline + &.prerecord, &.postrecord, &.fullrecord + :font-style italic + :color = !actioncolor + &.record + :font-weight bold + :color = !actioncolor + &.delete + :font-weight bold + :color = !actioncolor - 16 + + + table + :border-collapse collapse + :white-space nowrap + a + :display block + thead tr + th + :border 1px solid white + :border-top none + :text bold + :padding .5ex + :background = !tablecolor + &.first + :border-left none + &.last + :border-right none + a:before + :visibility hidden + :content "\\\\/" + a:hover, a:focus, a:active + &:before + :visibility visible + &.reverse a + &.sorted + :background = !tablecolorsorted + a:before + :visibility visible + a:hover, a:focus, a:active + &:before + :visibility visible + :content "/\\\\" + &.reverse + a:before + :visibility visible + :content "/\\\\" + a:hover, a:focus, a:active + &:before + :visibility visible + :content "\\\\/" + + tbody tr + td + :border 1px solid white + :padding .5ex + &.first + :border-left none + &.last + :border-right none + &.last td + :border-bottom none + &.r1 td + :background = !tablecolor + &.sorted + :background = !tablecolorsorted + &.r0 td + :background = !tablecolor - 16 + &.sorted + :background = !tablecolorsorted - 16 + + + .select + :position relative + .subselect + :display none + :position absolute + :background = !background + :padding + :right 1ex + :left 1ex + :border 1px black solid + &:hover + .subselect, .subselect * + :display block + .hide + :display none + STYLE + } + end + + def [] style + Sass::Engine.new( @styles[ style.to_sym]).render + end +end diff --git a/cgi-bin/template.rb b/cgi-bin/template.rb new file mode 100644 index 0000000..333055c --- /dev/null +++ b/cgi-bin/template.rb @@ -0,0 +1,31 @@ +require 'bdb' + +class Template + attr_reader :template, :vars, :i18n + def initialize template, vars = Hash.new, i18n = Hash.new + @template, @vars, @i18n = template, vars, i18n + end + + def [] key + @template[ key] + end + + def var k, vars = Hash.new + vars[k] || vars[k.to_sym] \ + || @vars[k] || @vars[k.to_sym] \ + || @@vars[k] || @@vars[k.to_sym] \ + || k + end + + def translate s, i18n = Hash.new + i18n[s] || @i18n[s] || @@i18n[s] || s + end + + def generate tmpl, vars = Hash.new, i18n = Hash.new + self[t].gsub( /(^|[^\\])#\{(([^}]|\})*)\}/) do + $1 + self.translate( $2.gsub( '\}', '}'), i18n) + end.gsub /(^|[^\\])\$\{(\w+)\}/) do + $1 + self.var( $2, vars) + end + end +end diff --git a/cgi-bin/timer.rb b/cgi-bin/timer.rb new file mode 100644 index 0000000..cc7b0d1 --- /dev/null +++ b/cgi-bin/timer.rb @@ -0,0 +1,172 @@ +require 'v2h' + +class V2H::Timer url, :alt => 'timer' + end + + def form url + c, cgi = @conf.opts.over, @conf.cgi + body = cgi.form( 'action' => + @conf.urlgen( :var => url.join( '/'), :alt => 'timer/new')) do + table = Tagen::ConfTable.new + + Tagen::InputRadio.new( 'active').with do |a| + a.push [ '1', -'yes' ] + a.push [ '0', -'no' ] + a.checked = c['active'] || '1' + table.push [ -'Timer active?', a.gen] + end + + Tagen::Select.new( 'channel').with do |cs| + @conf.vdr.lstc do |chan| + chan.darkknight + cs.push [ chan.number.to_s, chan.names[0] ] + end + cs.selected = c['channel'] || '1' + table.push [ Tagen.label( :for => :channel) { -'Channel' }, + cs.gen ] + end + + Tagen::Input.new( cgi, c).with do |input| + date = input.date( :date) + cgi.br + date += %W{rmon:Monday rtue:Tuesday rwed:Wednesday rthu:Thursday + rfri:Friday rsat:Saturday rsun:Sunday}.collect do |d| + d = d.split ':' + Tagen.span( :style => 'white-space: nowrap') do + cgi.input( :type => :checkbox, :name => d[ 0], + :id => d[ 0], :value => 1, + :checked => c[ d[ 0]] == '1') + + Tagen.label( :for => d[0]) { -d[1] } + end + end.join( ' ') + table.push [ Tagen.label( :for => :day) { -'Date' }, date ] + + table.push [ + Tagen.label( :for => :start_hour) { -'Start' }, + input.time( :start, c) + ] + table.push [ + Tagen.label( :for => :stop_hour) { -'Stop' }, + input.time( :stop, c) + ] + + table.push [ + Tagen.label( :for => :prio) { -'Priority' }, + prio = input.digits( :prio, 99, 2) + ] + table.push [ + Tagen.label( :for => :lifetime) { -'Lifetime' }, + input.digits( :lifetime, 99, 2) + ] + + file = cgi.input :size => 40, :type => :text, :name => :file, + :id => :file, :value => c['file'] + table.push [ Tagen.label( :for => :file) { -'File' }, file ] + + summ = cgi.textarea :cols => 40, :rows => 5, :name => :summ, + :id => :summ do c[ 'summ'].gsub( '|', "\n") end + table.push [ Tagen.label( -'Summary'), summ ] + end + + table.gen+ + cgi.input( :type => :submit, :value => 'Absenden') + end + V2H::Data.new Array.new, -'Define timer', body + end + + def timer n + cgi, timer = @conf.cgi, @conf.vdr.lstt( n)[0] + raise NoPage if timer.nil? + body = cgi.h1 do + Tagen::Wikipedia.new.gen( timer.file)+ ' '+ + Tagen::IMDb.new.gen( timer.file)+ ' '+ + timer.file + end + body += cgi.p do + b = timer.start_time + e = timer.stop_time + t = cgi.b { b.strftime( -'%m-%d-%Y') } + t += ' ' + t += b.strftime( -"%H:%M") + t += ' - ' + t + e.strftime( -"%H:%M") + end + body += cgi.p { timer.summary.gsub '|', cgi.br } + V2H::Data.new Array.new, -'Timer', body + end + + def list + table, cgi = Tagen::Table.new( 'timer'), @conf.cgi + require 'zlib' + + table.push :value => lambda { |a| + if a.status.recording then 'R' + else + @conf.confirm( 'timer/delete', a.number.to_s, + :suf => '|timer', :class => :delete, + :txt => -'Do you really want to delete timer "%s"?' % a.file.gsub( '~', '/')) do + 'x' + end + end + } + + table.push :title => -'Number', :name => :number + table.push :title => -'Channel', :name => :channel + table.push :title => -'Date', :value => lambda { |a| + d = a.day + Time.local( d.year, d.month, d.day).strftime -'%m-%d-%Y' + } + + table.push :title => -'Start', + :value => lambda { |a| -'%02i:%02i' % a.start.to_a }, + :compare => lambda { |a, b| a.start_time <=> b.start_time } + + table.push :title => -'Stop', + :value => lambda { |a| -'%02i:%02i' % a.stop.to_a }, + :compare => lambda { |a, b| a.stop_time <=> b.stop_time } + + table.push :title => -'File', :name => :file, + :value => lambda { |a| + cgi.a :href => @conf.urlgen( 'timer', a.number) do + subtitle = cgi.span( :style => 'font-size: 50%;') { ' - \1' } + a.file.gsub( / - ([^~]*)/, subtitle).gsub( '~', '/') + end + } + + table.sort_by = @conf.opts['timer_sortBy'] || '4' + t = table.generate @conf.vdr.lstt + V2H::Data.new Array.new, -'Timer', t + end +end diff --git a/cgi-bin/v2h.rb b/cgi-bin/v2h.rb new file mode 100644 index 0000000..fa92082 --- /dev/null +++ b/cgi-bin/v2h.rb @@ -0,0 +1,110 @@ +require 'cgi-extend' +require 'options' +require 'i18n' + +class NoPage #{CGI.escapeHTML e.to_s} (#{CGI.escapeHTML e.class.to_s})
"+ + e.backtrace.collect( &CGI.method( :escapeHTML)).join( '
') + V2H::Data.new [], -'Exception Raised', text + end + + def urlgen *paras + paras = Kernel.str_options paras, :pre, :var, :alt, :suf + opts = if paras.last.kind_of? Hash + o = paras.last.collect do |k, v| + "#{k}=#{CGI.escape v.to_s}" \ + unless [:pre, :var, :alt, :suf].include? k + end.compact.join ';' + o == '' ? '' : '?' + o + else '' + end + paras[1] ||= paras[2] + paras[2] = paras[3] + paras.unshift @link || '' + paras[0...4].compact.collect do |i| + j = i.kind_of?( Array) ? i.join( '/') : i.to_s + j.gsub( /([^a-z0-9_\-\/.,+()])/i) do |c| + '%%%02s' % c[0].to_s( 16) + end + end.join( '/').gsub( /\/+/, '/') + opts + end + + def confirm *paras, &e + paras = Kernel.str_options paras, :pre, :var, :alt, :suf, :class, :txt + opts = if paras.last.kind_of? Hash + o = paras.pop + txt = paras[5] || o[:confirm_text] + o[:confirm_text] = txt if txt + unless o.empty? + o = o.collect do |k, v| + "#{k}=#{CGI.escape v.to_s}" \ + unless [:pre, :var, :alt, :suf, :class, :txt].include? k + end.compact.join ';' + o == '' ? '' : '?' + o + else '' + end + else '' + end + paras[1] ||= paras[2] + paras[2] = paras[3] + paras.unshift 'confirm' + paras.unshift @link || '' + @cgi.a :href => paras[0...5].join( '/').gsub( /\/+/, '/') + opts, + :class => paras[6], &e + end + + def redirect *paras + { :redirect => self.urlgen( *paras) }.darkknight + $stdout.puts < line }.dakknight + end + end + end + ret + end + + def select h = nil, &e + e ? ::VDR::EPG::Select.new( self, h) : ::VDR::EPG::Select.new( self, h, &e) + end + + def find h = nil, &e + s = select h + e ? s.each( &e) : s.to_a + end +end + +class ::VDR::EPG::Select + attr_reader :epg, :between, :channel, :title, :description, :block + def each( &e) eval gen_filter end + def first() each do |entry| return entry end end + + def initialize epg, h = nil, &e + h ||= {} + @block, @epg, @between, @channel, @title, @description = \ + e, epg, h[:between], h[:channel], h[:title], h[:description] + @start, @stop, @duration = h[:start], h[:stop], h[:duration] + end + + def gen_filter t = nil, p = nil, u = nil + s = [] + s.push '@start === event.start' if @start + s.push '@stop === event.stop' if @stop + s.push '@duration === event.duration' if @duration + if @channel + @channel = ::VDR::EPG::Channel.new @vdr, @channel.to_s + s.push '@channel === entry.channel' + end + s.push '@description === entry.description' if @description + s.push '@title === entry.title' if @title + s.push 'event.vergleichen @between' if @between + s.push '@block.call( entry)' if @block + #s.push t || 'e.call( entry)' + s = <<-EOF + #{p || '@epg.parse do |entry|'} + event = entry.event + #{s.collect( & "(%s) || next".method( :%)).join "\n\t\t\t\t"} + #{t || 'e.call( entry)'} + #{u || 'end'} + EOF + s + end + + def collect &e + ret [] + eval gen_filter( 'ret.push( e.call( entry))') + ret + end + + def to_a + ret = [] + e = gen_filter( 'ret.push( entry)') + begin + eval e + rescue Object + File.open( '/tmp/vdr.cgi.debug', 'w+') do|f| + f.puts '='*80 + f.puts e + f.puts '-'*80 + f.puts "#{$!.message} (#{$!.class})" + f.puts "\t" + $!.backtrace.join( "\n\t") + end + raise $! + end + ret + end +end + +class ::VDR::EPG::Entry + attr_accessor :short_text, :description, :streams, :vps + attr_reader :title, :vdr, :event, :channel + def title=( value) @title = value.to_s end + + def initialize vdr, c = ::VDR::EPG::Channel.new( vdr), e = "", t = "", s = "", d = "", x = [], v = "" + ::Kernel.expect ::VDR, vdr + @vdr, self.channel, self.event, self.title, self.short_text, + self.description, self.streams, self.vps = vdr, c, e, t, s, d, x, v + end + + def channel= value + value = ::VDR::EPG::Channel.new @vdr, value if value.kind_of? ::String + ::Kernel.expect ::VDR::EPG::Channel, value + @channel = value + end + + def event= value + value = ::VDR::EPG::Event.new value if value.kind_of? ::String + ::Kernel.expect ::VDR::EPG::Event, value + @event = value + end + + def to_timer + e = self.event + ::VDR::Timer.new @vdr, 3, self.channel, ::VDR::Timer::Day.new( e.start), + ::VDR::Timer::Clock.new( e.start.hour, e.start.min), + ::VDR::Timer::Clock.new( e.stop.hour, e.stop.min), + 99, 99, self.title, self.description + end + + def record? + timer, chan = self.to_timer, self.channel + r = [] + vdr.lstt do |a| + if timer.channel == a.channel && (x = (timer =~ a)) + r.push x + end + end + r.uniq + end + + def inspect + "#<#{self.class} #{self.channel.inspect} #{self.title.inspect} #{self.event.inspect}>" + end + + def vergleichen m + [ :short_text, :description, :streams, :vps, :title, :vdr, :event, + :channel ].each do |k| + m[k] ? m[k] === e.method( k) : true + end + end + alias =~ vergleichen +end + +class ::VDR::EPG::Event + attr_reader :id, :start, :duration, :table_id + def id=( value) @id = value.to_i end + def duration=( value) @duration = value.to_i end + def table_id=( value) @table_id = value end + def to_s() [ @id, @start.to_i, @duration, @table_id].compact.join " " end + + def initialize id = 0, start = nil, duration = nil, table_id = nil + self.id, self.start, self.duration, self.table_id = if id.kind_of? ::String + id.split " " + else [ id, start, duration, table_id ] + end + end + + def stop() self.start + self.duration end + + def stop= value + value = ::Time.at value unless value.kind_of? ::Time + self.duration = (value - self.start).to_i + end + + def start= value + value = ::Time.at value.to_i unless value.kind_of? ::Time + @start = value + end + + def vergleichen value + start, stop = self.start.to_i, self.stop.to_i + case value + when ::Numeric, ::Time: (start .. stop) === value.to_i + when ::Range + value = value.begin.to_i..value.end.to_i + value.include?( start) || value.include?( stop) || + (start..stop).include?( value.begin) + when self.class + vstart, vstop = value.start.to_i, value.stop.to_i + (start..stop) === vstart || (start..stop) === vstop || + (vstart..vstop) === start + else raise ::ArgumentError, "#{value.inspect}" + end + end + alias =~ vergleichen + + def inspect + "#<#{self.class} #{self.start.inspect} #{self.duration.inspect}>" + end +end + +class VDR::EPG::Channel + attr_reader :vdr, :id + def to_i() channel.number end + def name() @name || channel.name end + + def initialize vdr, id = nil, name = nil + @vdr = vdr + self.id, self.name = if id.nil? then [ '', '' ] + elsif name.nil? then id.to_s.split ' ', 2 + else [ id, name ] + end + end + + def id= value + ::Kernel.expect ::String, value + @channel = nil + @id = value + end + + def channel + return @channel if @channel + source, tid, rid, nid = @id.split "-" + @channel = @vdr.lstc.find do |c| + c.source == source && c.tid == tid && c.rid == rid && c.nid == nid + end + end + + def name= value + ::Kernel.expect [::String, nil], value + @name = value + end + + def == v + case v + when ::Numeric: to_i == v + when ::VDR::EPG::Channel: self.id == v.id + else super v + end + end + + def inspect + "#<#{self.class} #{self.name.inspect} #{self.to_i.inspect} @id=#{self.id.inspect}>" + end +end diff --git a/cgi-bin/vdr/records.rb b/cgi-bin/vdr/records.rb new file mode 100644 index 0000000..6340061 --- /dev/null +++ b/cgi-bin/vdr/records.rb @@ -0,0 +1,53 @@ +class VDR::Records + attr_reader :number, :time, :seen, :title + + def initialize *opts + self.number, self.time, self.seen, self.title = if opts.size == 1 + if m = /^(\d+)\s+(\d{1,2}\.\d{1,2}\.\d{1,4}\s+\d{1,2}:\d{1,2})(\S*)\s+(.*)$/.match( opts[0]) + m[1..-1] + else Kernel.options opts, %W[num number], :time, :seen, :title + end + else opts + end + end + + def number= value + value = value.to_i if value.kind_of? String + Kernel.expect Integer, value + @number = value + end + + def time= value + if value.kind_of? String + value = if m = /^\s*(\d{1,2})\.(\d{1,2})\.(\d{1,4})\s+(\d{1,2}):(\d{1,2})\s*$/.match( value) + m = m[1..-1].collect do |i| + i.to_i + end + Time.local( (m[2]<2000 ? m[2]+2000 : m[2]), m[1], m[0], m[3], m[4]) + else value.to_i + end + end + value = Time.at value if value.kind_of? Integer + Kernel.expect Time, value + @time = value + end + + def title= value + value = value.split /~/ if value.kind_of? String + Kernel.expect Array, value + def value.to_s() join '/' end + @title = value + end + + def seen= value + @seen = value != '' + end + + def seen? + @seen + end + + def to_i + @number + end +end diff --git a/cgi-bin/vdr/sar.rb b/cgi-bin/vdr/sar.rb new file mode 100644 index 0000000..98206da --- /dev/null +++ b/cgi-bin/vdr/sar.rb @@ -0,0 +1,126 @@ + +class String + def tsub! opts = nil, &e + unless e + opts ||= {} + e = lambda do |f, a| + if opts.has_key? f + case v = opts[ f] + when Method, Proc: v.call a + else v.to_s + end + end + end + end + self.gsub!( %r<(\\*)\$\{(.*?)\}>) do + b, k = $1, $2 + m = if b.length%2 == 0 + k =~ %r<^(\w+)(?:\((.*)\))?$> + e.call $1 + ($2 ? '()' : ''), $2 + end || "${#{k}}" + ( '\\' * (b.length/2) ) + m + end + end + + def tsub opts = nil, &e + e ? self.dup.tsub!( opts, &e) : self.dup.tsub!( opts) + end +end + +class VDR::SearchAndRecord < Array + attr_reader :vdr + class AutoTimer + attr_reader :vdr, :type, :start, :stop, :earlier, :later + attr_accessor :channel, :file, :title + def initialize vdr, title, channel, type = nil, file = nil, + earlier = nil, later = nil + @vdr, self.title, self.channel = vdr, title.dup, channel + self.file, self.type = file || '${title}', type || :timer + self.earlier, self.later = earlier || 10, later || 10 + end + + def series() @type = :series; self end + def series?() @type == :series end + def movie() @type = :movie; self end + def movie?() @type == :movie end + def type=( x) @type = x.to_s.downcase.to_sym end + def earlier=( x) @earlier = x.to_i end + def later=( x) @later = x.to_i end + + def test e + def eqon( a, v) a ? a === v : true end + eqon( @channel, e.channel) && eqon( @title, e.title) + end + alias =~ test + + def testnrecord e + return false unless self =~ e + record e + end + alias test_and_record testnrecord + alias call testnrecord + alias run testnrecord + + def record e + t = e.to_timer + t.file = @file + case @type + when :series: t.file += '~${start(%y-%m-%d)}' + end + t.stop_time += earlier*60 + t.start_time -= later*60 + title = @title.match e.title + t.file.tsub! do |f, a| + case f + when 'start': t.start_time.strftime '%y-%m-%d.%H:%M' + when 'start()': t.start_time.strftime a + when 'stop': t.stop_time.strftime '%y-%m-%d.%H:%M' + when 'stop()': t.stop_time.strftime a + when 'title': e.title + when 'title()': title[ a.to_i] + end + end +=begin + t.file.gsub!( /\$\{([A-Za-z][0-9A-Za-z]*)(?:|\[([0-9]+)\]|\{([^}]*)\})\}/) do |a| + var, index, key = $1, $2.to_i, $3 || '' + case var + when "title": index == 0 ? e.title.dup : title[ index] + when "start": t.start_time.strftime key || '%y-%m-%d.%H:%M' + when "stop": t.stop_time.strftime key || '%y-%m-%d.%H:%M' + when *%w{wday month day hour min sec}: + '%02i' % t.start_time.method( var).call + when "year": t.start_time.year + else a.to_s + end + end +=end + t + end + end + + def testnrecord e + @vdr.lstc + @vdr.lstt + self.each do |a| + return a if a = a.call( e) + end + return nil + end + alias test_and_record testnrecord + alias call testnrecord + + def initialize *a + @vdr = VDR.new :buffered => false + super *a + end + + def run + @vdr.epg.parse &:testnrecord + end + + def push *x + super x if x.length == 1 && x[0] === AutoTimer + super AutoTimer.new( @vdr, *x) + end + def []=( k, x) super k, x if x === AutoTimer end +end diff --git a/cgi-bin/vdr/svdrp.rb b/cgi-bin/vdr/svdrp.rb new file mode 100644 index 0000000..9ec7229 --- /dev/null +++ b/cgi-bin/vdr/svdrp.rb @@ -0,0 +1,107 @@ +require 'socket' + +class Svdrp + attr_reader :addr, :port, :sock, :firstline + +#private + def initialize addr = nil, port = nil + @addr, @port, @sock = addr || 'localhost', port || 2001, nil + self + end + + def closed? + @sock.nil? || @sock.closed? + end + + def connect + return true unless closed? + @sock = TCPSocket.new @addr, @port + @firstline = self.cmd + end + + def close + @firstline = nil + @sock.close + end + + def puts line + @sock.puts line if connect + rescue Errno::EPIPE + close + retry + end + + def gets + line = @sock.gets + return nil if line.nil? + /^(\d\d\d)([ -])(.*?)[\n\r]*$/.match line + [ $2 == ' ', $1.to_i, $3 ] + end + + def Svdrp.vdr_command cmd, np = 0, op = 0 + cmd = cmd.to_s + ps = (0...np).to_a.collect { |i| "p#{i}" } + ps+= (np...(np+op)).to_a.collect { |i| "p#{i} = ''" } + pu = [ "#{cmd}" ] + pu+= (0...(np+op)).to_a.collect { |i| "\#\{p#{i}\}" } + ps.push "&e" + eval <<-EOC.gsub( /^\t+/, '') + def #{cmd} #{ps.join ', '} + if e.nil? + self.cmd "#{pu.join( " ")}" + else + self.cmd "#{pu.join( " ")}", &e + end + end + EOC + end + +public + def cmd line = nil, &e + unless e + a = Array.new + e = lambda do |last, err, line| + a.push [err, line] + end + end + ret = nil + puts line unless line.nil? + last = false + until last + last, err, line = gets + next unless last + ret = e.call last, err, line + end + ret + end + + vdr_command :chan, 0, 1 + vdr_command :clre + vdr_command :delc, 1 + vdr_command :delr, 1 + vdr_command :delt, 1 + vdr_command :edit, 1 + vdr_command :grab, 1, 4 + vdr_command :help, 0, 1 + vdr_command :hitk, 0, 1 + vdr_command :lstc, 0, 1 + vdr_command :lste, 0, 2 + vdr_command :lstr, 0, 1 + vdr_command :lstt, 0, 1 + vdr_command :mesg, 0, 1 + vdr_command :modc, 2 + vdr_command :modt, 2 + vdr_command :movc, 2 + vdr_command :movt, 2 + vdr_command :newc, 1 + vdr_command :newt, 1 + vdr_command :next, 0, 1 + vdr_command :play, 1, 1 + vdr_command :plug, 1, 2 + vdr_command :pute + vdr_command :scan + vdr_command :stat, 1 + vdr_command :updt, 1 + vdr_command :volu, 0, 1 + vdr_command :quit +end diff --git a/cgi-bin/vdr/timer.rb b/cgi-bin/vdr/timer.rb new file mode 100644 index 0000000..f853241 --- /dev/null +++ b/cgi-bin/vdr/timer.rb @@ -0,0 +1,347 @@ +require 'options' + +class VDR::Timer + attr_reader :vdr, :status, :channel, :day, :start, :stop, :priority + attr_reader :file, :summary, :number, :lifetime + + def initialize vdr, status = VDR::Timer::Status.new, channel = 1, + day = VDR::Timer::Day.new, start = VDR::Timer::Clock.new, + stop = VDR::Timer::Clock.new, priority = 0, lifetime = 0, + file = "", summary = "", number = 0 + + @vdr = vdr + self.status, self.channel, self.day, self.start, self.stop, + self.priority, self.lifetime, self.file, self.summary, + @number = if status.kind_of? String + status = /^((\d+) )?(.*)$/.match status + status[3].split( ':', 9) + [ status[2].to_i ] + else [ status, channel, day, start, stop, priority, + lifetime, file, summary, number ] + end + end + + def status= value + value = VDR::Timer::Status.new value if [ String, Fixnum ].include? value.class + Kernel.expect VDR::Timer::Status, value + @status = value + end + + def priority= value + value = value.to_i + Kernel.expect 0..99, value + @priority = value + end + + def file= value + @file = value.to_s.gsub ":", "|" + end + + def channel= value + value = value.to_i + Kernel.expect Fixnum, value + @channel = value + end + + def start= value + case value + when String, Integer: value = VDR::Timer::Clock.new value + end + Kernel.expect VDR::Timer::Clock, value + @start = value + end + + def stop_time + day, stop = self.day, self.stop + t = Time.gm day.year, day.month, day.day, stop.hour, stop.minute + t += 1440 if self.start > stop + t + end + + def stop_time= t + t = Time.local t if t === Integer + @stop = VDR::Timer::Clock.new t.hour, t.min + end + + def start_time + day, start = self.day, self.start + Time.gm day.year, day.month, day.day, start.hour, start.minute + end + + def start_time= t + t = Time.local t if t === Integer + @day = VDR::Timer::Day.new t.year, t.month, t.day + @start = VDR::Timer::Clock.new t.hour, t.min + end + + def stop= value + case value + when String, Integer: value = VDR::Timer::Clock.new value + end + Kernel.expect VDR::Timer::Clock, value + @stop = value + end + + def summary= value + @summary = value.to_s.gsub "\n", "|" + end + + def lifetime= value + value = value.to_i + Kernel.expect 0..99, value + @lifetime = value + end + + def day= value + value = VDR::Timer::Day.new value if value.kind_of? String + Kernel.expect VDR::Timer::Day, value + @day = value + end + + def to_s + "#{@status}:#{@channel}:#{@day}:#{@start}:#{@stop}:#{@priority}:#{@lifetime}:#{@file}:#{@summary}" + end + + def =~ value + start, stop, vstart, vstop = self.start_time, self.stop_time, + value.start_time, value.stop_time + pre, post = (vstart..vstop) === start, (vstart..vstop) === stop + if pre && post then true + elsif pre then -1 + elsif post then 1 + elsif (start..stop) === vstart then 0 + else false + end + end +end + +class VDR::Timer::Status + %w{ACTIVE INSTANT USE_VPS RECORDING}.each_with_index do |l,i| + i, m = i**2, l.downcase + consts_multiset i, l + c = lambda {|| @repeat&i == i } + define_method m, &c + define_method "#{m}?", &c + define_method "#{m}=" do |value| + @repeat = @repeat & ~i | (value ? i : 0) + end + end + + attr_reader :status + + def initialize( status = 0) self.status = status end + def to_s() @status.to_s end + + def status= value + value = value.to_i + Kernel.expect [Bignum, Fixnum], value + @status = value + end +end + +class VDR::Timer::Clock + attr_reader :hour, :minute + + def initialize hour = 0, minute = nil + self.hour, self.minute = if minute.nil? + case hour + when String then [ hour[0..1], hour[2..3] ] + when Integer then [ hour/60, hour%60 ] + else [ 0, 0 ] + end + else + [ hour, minute ] + end + end + + def minute= value + value = value.to_i + Kernel.expect 0..60, value + @minute = value + end + + def hour= value + value = value.to_i + Kernel.expect 0..23, value + @hour = value + end + + def -( value) self.class.new self.to_i - value end + def +( value) self.class.new self.to_i + value end + def <( value) self.to_i < value.to_i end + def >( value) self.to_i > value.to_i end + + def to_i= value + Kernel.expect Integer, value + self.hour, self.minute = value/60, value%60 + end + + def to_i() @hour*60 + @minute end + def to_s() "%02i%02i" % [ @hour, @minute ] end + def to_a() [@hour, @minute] end + def <=>() value self.to_i <=> value.to_i end +end + +class VDR::Timer::Day + <<-EOD.split( "\n").each_with_index do |l,i| + MON MO MONDAY MONTAG + TUE DI TUESDAY DIENSTAG + WED MI WEDNESDAY MITTWOCH + THU DO THURSDAY DONNERSTAG + FRI FR FRIDAY FREITAG + SAT SA SATURDAY SAMSTAG + SUN SO SUNDAY SONNTAG + EOD + l = l.split( /\s+/)[1..-1] + i, m = i**2, l[2].downcase + consts_multiset i, *l + c = lambda {|| @repeat&i == i } + define_method m, &c + define_method "#{m}?", &c + define_method "#{m}=" do |value| + @repeat = @repeat & ~i | (value ? i : 0) + end + end + + <<-EOD.split( "\n").each_with_index { |l,i| consts_multiset i, l } + DEL DELETE + JAN JANUARY JANUAR + FEB FEBRUARY FEBRUAR + MAR MARCH MAERZ + APR APRIL + MAY MAI + JUN JUNE JUNI + JUL JULY JULI + AUG AUGUST + SEP SEPTEMBER + OCT OCTOBER + NOV NOVEMBER + DEC DEZ DECEMBER DEZEMBER + EOD + + attr_reader :year, :month, :day, :repeat + + def initialize *paras + # year = Time.new.year, month = Time.new.month, day = Time.new.day, repeat = 0 + x = nil + + if paras.size == 1 + x = case paras[0] + when String + value = paras[0] + if days = /^(\d{4})-(\d{2})-(\d{2})$/.match( value) + days = days.to_a[0..-1] + days[0] = 0 + days + elsif days = /^([a-zA-Z\-]{7})@(\d{4})-(\d{2})-(\d{2})$/.match( value) + days.to_a[1..-1] + elsif /^[a-zA-Z\-]{7}$/.match value + [ value, 0, 0, 0 ] + else raise ArgumentError, "Not a valid Day-string (#{value})" + end + when Time + t = paras[0] + [ 0, t.year, t.month, t.day ] + end + end + self.repeat, self.year, self.month, self.day = *x || [ paras[3] || 0, paras[0] || Time.new.year, paras[1] || Time.new.month, paras[2] || Time.new.day ] + end + + def year= value + value = value.to_i + Kernel.expect Fixnum, value + @year = value + end + + def month= value + value = case value.to_s.downcase + when %w{del delete} then 0 + when %w{jan january januar} then 1 + when %w{feb february februar} then 2 + when %w{mar march maerz} then 3 + when %w{apr april} then 4 + when %w{may mai} then 5 + when %w{jun june juni} then 6 + when %w{jul july juli} then 7 + when %w{aug august} then 8 + when %w{sep september} then 9 + when %w{oct october} then 10 + when %w{nov november} then 11 + when %w{dec december dez dezember} then 12 + else value.to_s.to_i + end if value.kind_of?( String) || value.kind_of?( Symbol) + Kernel.expect 0..12, value + @month = value + end + + def day= value + value = value.to_i + Kernel.expect 0..31, value + @day = value + end + + def repeat= value + if value.kind_of? String + d = value + value = 0 + (0...7).each do |i| + j = i**2 + value |= j unless d[i] ==?- + end + end + Kernel.expect 0..MON|TUE|WED|THU|FRI|SAT|SUN, value + @repeat = value + end + + def -( value) self + -value end + def <=>( value) self.to_i||0 <=> value.to_i||0 end + def to_a() [ @repeat, @month, @day, @year ] end + + def to_i + @repeat == 0 ? nil : (Time.local( @year, @month, @day).to_i / 86400) + end + + def to_s + if (@month | @day | @year == 0) && @repeat == 0 + "" + elsif @repeat == 0 + "%04i-%02i-%02i" % [ @year, @month, @day] + else + str = "MTWTFSS" + str += "@%04i-%02i-%02i" % [ @year, @month, @day] unless [ @year, @month, @day ].include? 0 + (0...7).each do |i| + j = i**2 + str[i] = '-' unless @repeat&j == j + end + str + end + end + + def + value + t = if @year | @month | @day == 0 + [ 0, 0, 0] + else + t = Time.gm( @year, @month, @day) + 86400*value + [ t.year, t.month, t.day ] + end + self.class.new t[0], t[1], t[2], @repeat && (((@repeat + (@repeat << 7)) >> (value%7)) & 127) + end + + def =~ value + day = self.to_i + if day + case value + when Time then day == value.to_i / 86400 + when Integer then day == value + else super value + end + else + case value + when Time + day = 2**value.wday + self.repeat & day == day + when Integer then self === Time.at( value) + else super value + end + end + end +end diff --git a/cgi-bin/vdr/vdr.rb b/cgi-bin/vdr/vdr.rb new file mode 100644 index 0000000..9c15973 --- /dev/null +++ b/cgi-bin/vdr/vdr.rb @@ -0,0 +1,259 @@ + +require 'options' +require 'iconv' +require 'vdr/svdrp' + +class ::VDR <::Svdrp +end + +require 'vdr/channel' +require 'vdr/epg' +require 'vdr/timer' +require 'vdr/records' +require 'vdr/sar' + +class ::Buffer + attr_accessor :o + attr_reader :d, :l + + def initialize d = nil, o = 60 + @l, @o = Time.at( 0), o + self.d = d if d + end + + def d= v + @d, @l = v, Time.now+@o + end + + def outdated? + Time.now <= @l + end +end + +class ExpCache < Hash + attr_accessor :expire + attr_reader :expires + def initialize expire = nil + @expire, @expires = expire || 60, {} + end + def expired? k + @expires[ k] && @expires[ k] < Time.now + end + def expire k + if expired? k + self.delete k + @expires.delete k + end + end + + %w{[] fetch key? has_key? include? member?}.each do |cmd| + eval <<-EOC + def #{cmd} k + expire k + super k + end + EOC + end + %w{[]= store}.each do |cmd| + eval <<-EOC + def #{cmd} k, v + @expires[k] = Time.now + @expire + super k, v + end + EOC + end + + %w{clear delete}.each do |cmd| + eval <<-EOC + def #{cmd} *p + @expires.#{cmd} *p + super *p + end + EOC + end + + %w{delete_if each each_key each_pair reject reject!}.each do |cmd| + eval <<-EOC + def #{cmd} + super do |*p| + expires p[0] + yield *p if has_key? k + end + end + EOC + end + + def each_value + each do |k, v| + expire k + yield v if has_key? k + end + end +end + +class VDR + class VDRError = 400 + end if x[0] + x + end + + def cmd line = nil, &e + unless e + a = Array.new + e = lambda do |last, err, line| + a.push [ err, line ] + end + end + ret = nil + puts line unless line.nil? + last = false + begin + until last + last, err, line = gets + next unless line + ret = e.call last, err, line + end + rescue VDRError + raise + rescue + line = gets until line[0] + raise + end + ret + end + + def super_cached m, x, p = nil + <<-EOC + a = @cache[ #{m.inspect}] + if a.nil? + a = Array.new + if e + super #{p} do |last, err, line| + x = #{x} + a.push x + e.call x + end + else + super #{p} do |last, err, line| + a.push #{x} + end + end + @cache[ #{m.inspect}] = a + elsif e + a.each &e + end + a + EOC + end + + def lstr num = nil, &e + e ||= Array.new.method :push + if num + c, e = "", VDR::EPG::Entry.new( self) + super num do |last, error, line| + l = line[2..-1] + case line[0] + when ?C then e.channel = l + when ?E then e.event = l + when ?T then e.title = l + when ?S then e.short_text = l + when ?D then e.description = l + when ?X then e.streams.push l + when ?V then e.vps = l + when ?c + else { :unexpected_epg_data => line }.darkknight + end unless last + end + e + else + eval super_cached( :lstr, 'VDR::Records.new( line)') + end + rescue VDR::VDRError => e + raise unless e.err == 550 + [] + end + + def lstc num = nil, &e + eval super_cached( :lstc, 'VDR::Channel.new( self, line)') + rescue VDR::VDRError => e + raise unless e.err == 550 + [] + end + + def lstt num = nil, &e + eval super_cached( "lstt_#{num}", 'VDR::Timer.new( self, line)', num) + rescue VDR::VDRError => e + raise unless e.err == 550 + [] + end + + def newt value + value = value.to_s + Kernel.expect String, value + super value + end + + def delt value + value = value.number if value.kind_of? VDR::Timer + value = value.to_i + value = nil if value < 1 + Kernel.expect Integer, value + super value + end + + def lste chan = nil, time = nil, &e + e ||= Array.new.method :push + ret, c, event = nil, "", VDR::EPG::Entry.new( self) + parser = lambda do |last, error, line| + l = line[2..-1] + case line[0] + when ?C then c = l + when ?E then event = VDR::EPG::Entry.new self, c, l + when ?T then event.title = l + when ?S then event.short_text = l + when ?D then event.description = l + when ?X then event.streams.push l + when ?V then event.vps = l + when ?e then ret = e.call event + when ?c + else { :unexpected_epg_data => line }.darkknight + end unless last + end + super chan, time, &parser + ret + end + + def epg() + EPG.new self + end +end diff --git a/lighttpd/vdr.conf b/lighttpd/vdr.conf new file mode 100644 index 0000000..1870d9b --- /dev/null +++ b/lighttpd/vdr.conf @@ -0,0 +1,27 @@ + +$HTTP["url"] =~ "^/vdr/" { +# auth.backend = "ldap" +# auth.backend.ldap.hostname = "ldap.example.org" +# auth.backend.ldap.base-dn = "ou=People,o=example,c=org" +# auth.backend.ldap.filter = "(uid=$)" +# auth.backend.ldap.allow-empty-pw = "disable" +# auth.require = ( +# "" => ( +# "method" => "basic", +# "realm" => "VDR", +# "require" => "valid-user" +# ) +# ) + + fastcgi.server = ( + "/vdr" => ( + "localhost" => ( + "socket" => "/var/run/lighttpd/lighttpd-fastcgi-vdr-" + PID + ".socket", + "bin-path" => "/var/www/localhost/cgi-bin/vdr/index.rb", + "check-local" => "disable" + ) + ) + ) +} + +# vim: set ft=conf foldmethod=marker et :