initial import
git-svn-id: svn://svn.berlios.de/gpodder@1 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
commit
bde9998063
24 changed files with 3718 additions and 0 deletions
2
AUTHORS
Normal file
2
AUTHORS
Normal file
|
@ -0,0 +1,2 @@
|
|||
Thomas Perl <thp@perli.net>
|
||||
Peter Hoffmann <tosh@cs.tu-berlin.de>
|
340
COPYING
Normal file
340
COPYING
Normal file
|
@ -0,0 +1,340 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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 a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE 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.
|
||||
|
||||
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
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
13
ChangeLog
Normal file
13
ChangeLog
Normal file
|
@ -0,0 +1,13 @@
|
|||
Son Nov 20 22:28:10 CET 2005 <thp@perli.net>
|
||||
* Fixed version info reading in setup.py
|
||||
+ Added "INSTALL" file
|
||||
+ Updated TODO list
|
||||
+ Added GPL (file COPYING)
|
||||
|
||||
Sam Nov 19 18:17:35 CET 2005 <thp@perli.net>
|
||||
+ Created ChangeLog
|
||||
* Updated TODO list
|
||||
* Cleanup of source tree
|
||||
* Makefile changed to fit to new source structure
|
||||
* Unified version info (only have to edit bin/gpodder)
|
||||
|
14
INSTALL
Normal file
14
INSTALL
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
gPodder installation
|
||||
|
||||
To install gPodder on your computer, make sure you have python and
|
||||
gPodder's dependencies (see README) installed. Then, go into the
|
||||
source dir (you probably are in it now as you are reading this) and
|
||||
execute the following command to install it, either as root or with
|
||||
sudo:
|
||||
|
||||
make install
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal file
|
@ -0,0 +1,4 @@
|
|||
include README AUTHORS TODO MANIFEST.in ChangeLog Makefile setup.py
|
||||
include src/gpodder/gpodder.py.orig
|
||||
recursive-include data *
|
||||
recursive-include doc/man *
|
56
Makefile
Normal file
56
Makefile
Normal file
|
@ -0,0 +1,56 @@
|
|||
#
|
||||
# gpodder makefile
|
||||
# copyright 2005 thomas perl <thp@perli.net>
|
||||
# released under the gnu gpl
|
||||
#
|
||||
|
||||
##########################################################################
|
||||
|
||||
BINFILE=bin/gpodder
|
||||
GLADEFILE=data/gpodder.glade
|
||||
GUIFILE=src/gpodder/gpodder.py
|
||||
MANPAGE=doc/man/gpodder.man.1
|
||||
|
||||
##########################################################################
|
||||
|
||||
all: generators
|
||||
|
||||
##########################################################################
|
||||
|
||||
test: generators
|
||||
$(BINFILE) --debug
|
||||
|
||||
release: generators
|
||||
python setup.py sdist
|
||||
|
||||
install: generators
|
||||
python setup.py install
|
||||
|
||||
##########################################################################
|
||||
|
||||
generators: gen_manpage gen_glade
|
||||
|
||||
gen_manpage:
|
||||
help2man -N ./bin/gpodder >$(MANPAGE)
|
||||
|
||||
gen_glade: $(GLADEFILE)
|
||||
tepache --no-helper --glade=$(GLADEFILE) --output=$(GUIFILE)
|
||||
chmod -x $(GUIFILE) $(GUIFILE).orig
|
||||
|
||||
##########################################################################
|
||||
|
||||
clean:
|
||||
python setup.py clean
|
||||
rm -f src/gpodder/*.pyc src/gpodder/*.bak MANIFEST $(MANPAGE) PKG-INFO
|
||||
rm -rf build
|
||||
|
||||
distclean: clean
|
||||
rm -rf dist
|
||||
|
||||
##########################################################################
|
||||
|
||||
.PHONY: all test release install generators gen_manpage gen_glade clean distclean
|
||||
|
||||
##########################################################################
|
||||
|
||||
|
96
README
Normal file
96
README
Normal file
|
@ -0,0 +1,96 @@
|
|||
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1THIS IS OUTDATED!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
build deps: help2man, tepache
|
||||
|
||||
___
|
||||
|'\ | | /''\ |
|
||||
|./ .| .| | | |__
|
||||
/'\ | /\ / | / | /| |' | | \
|
||||
\_/ | \/ \_| \_| \_ | \../ o ___/
|
||||
\_/ www.perli.net/projekte/gpodder
|
||||
|
||||
- -- == === ================================= === == -- -
|
||||
|
||||
|
||||
gPodder 0.5 README
|
||||
==================
|
||||
Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
Released on 31.10.2005 under the GNU GPL
|
||||
|
||||
|
||||
============================
|
||||
1) What's new in version 0.5
|
||||
============================
|
||||
|
||||
please see the gPodder homepage for most recent information.
|
||||
|
||||
gPodder 0.5 is a pre-release snapshot of the upcoming 0.6 release.
|
||||
|
||||
===============
|
||||
2) Dependencies
|
||||
===============
|
||||
|
||||
Debian/Ubuntu users can install the dependencies with:
|
||||
|
||||
apt-get install python python-glade2 python-gtk2 wget
|
||||
|
||||
Other users: Please install the above packages somehow ;)
|
||||
|
||||
=====================
|
||||
3) How to run/install
|
||||
=====================
|
||||
|
||||
Extract the tar archive somewhere into your home directory and cd into
|
||||
the directory where you extracted it, for example:
|
||||
|
||||
tar xzvf gpodder-0.5-pre.tar.gz
|
||||
cd gpodder-0.5-pre/
|
||||
|
||||
After that you can start gPodder 0.5 by starting it with the following
|
||||
command:
|
||||
|
||||
./gpodder-run
|
||||
|
||||
Currently there is no way to automatically install it system-wide, but
|
||||
if you are whiz enough you will figure out how to do it anyway - if not,
|
||||
you can always write an e-mail to me :)
|
||||
|
||||
=============
|
||||
4) How to use
|
||||
=============
|
||||
|
||||
gPodder was designed with usability in mind. Saying that gPodder is
|
||||
self-explaining saves me from writing long documentation. Will write some
|
||||
more documentation for the 0.6 relese.
|
||||
|
||||
=============
|
||||
5) Known bugs
|
||||
=============
|
||||
|
||||
Here is a short list of known bugs which i (or someone else) will fix
|
||||
soon hopefully:
|
||||
|
||||
*) Who has seen the "downloaded podcasts" screen?
|
||||
*) Some more code coolness
|
||||
*) Preferences dialog
|
||||
*) Export channel list
|
||||
*) YOUR FEATURE REQUEST/BUG REPORT HERE
|
||||
|
||||
=========================================
|
||||
6) Feature requests, bug reports, patches
|
||||
=========================================
|
||||
|
||||
Feel free to mail me about feature requests, bug reports, patches or nearly
|
||||
anything that comes to your mind relating gPodder:
|
||||
|
||||
eMail: thp@perli.net
|
||||
www: http://www.perli.net/projekte/gpodder/
|
||||
|
||||
|
||||
Last but not least... have fun with gPodder :)
|
||||
|
||||
-- Thomas Perl, October 31st 2005
|
||||
|
||||
|
17
TODO
Normal file
17
TODO
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
* update README for next release
|
||||
* update INSTALL file for next release
|
||||
|
||||
* generate a Debian package for the next release
|
||||
|
||||
* maybe add a id into channels.xml
|
||||
|
||||
* test in ./bin/gpodder if wget is available
|
||||
|
||||
* In der Auswahlansicht farbig hinterlegen ob ein Podcast schon heruntergeladen wurde.
|
||||
* Vielleicht eine Channel All Ansicht.
|
||||
|
||||
* Auswahl mehrerer Feeds zum Download
|
||||
* Anzeige/Sortieren der Ansicht
|
||||
* evtl. Datum mit speichern
|
||||
|
98
bin/gpodder
Executable file
98
bin/gpodder
Executable file
|
@ -0,0 +1,98 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""gPodder Media Aggregator"""
|
||||
|
||||
# PLEASE DO NOT CHANGE FORMAT OF __version__ LINE (Makefile reads this)
|
||||
|
||||
__author__ = "Thomas Perl <thp@perli.net>"
|
||||
__version__ = "SVN.20051120"
|
||||
__date__ = "2005-11-20"
|
||||
__copyright__ = "Copyright (c) 2005 %s. All rights reserved." %__author__
|
||||
__licence__ = "GPL"
|
||||
|
||||
import sys,os
|
||||
from optparse import OptionParser
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
parser = OptionParser(usage="usage: %%prog [options] arg1 arg2\n%s" %__doc__,
|
||||
version = "%%prog %s %s" %(__version__, __date__))
|
||||
|
||||
parser.add_option("-v", "--verbose",
|
||||
action="store_true", dest="verbose", default=False,
|
||||
help="print extra information")
|
||||
|
||||
parser.add_option("--debug",
|
||||
action="store_true", dest="debug", default=False,
|
||||
help="run local version for debugging")
|
||||
|
||||
parser.add_option("-l", "--list",
|
||||
action="store_true", dest="list", default=False,
|
||||
help="list all channel subscriptions")
|
||||
|
||||
parser.add_option("-r", "--run",
|
||||
action="store_true", dest="run", default=False,
|
||||
help="update channels and download new podcasts")
|
||||
|
||||
parser.add_option("-u", "--update",
|
||||
action="store_true", dest="update", default=False,
|
||||
help="update channels")
|
||||
|
||||
parser.add_option("-a", "--add", dest="add",
|
||||
help="add FEEDURL to channel subscriptions", metavar="FEEDURL")
|
||||
|
||||
parser.add_option("-d", "--delete", dest="delete",
|
||||
help="delete channel n", metavar="n")
|
||||
|
||||
|
||||
(options, args) = parser.parse_args(argv)
|
||||
|
||||
if options.verbose:
|
||||
print "Options: "+str(options)
|
||||
print "Args: "+str(args)
|
||||
|
||||
if options.debug:
|
||||
sys.path = ['./src/','../src/'] + sys.path
|
||||
#sys.path.append('../src/')
|
||||
#sys.path.append('./src/')
|
||||
|
||||
from gpodder import console
|
||||
|
||||
if options.list:
|
||||
console.list_channels()
|
||||
|
||||
elif options.run:
|
||||
console.run()
|
||||
|
||||
elif options.update:
|
||||
console.update()
|
||||
|
||||
elif options.add:
|
||||
if options.add != None and options.add != "" and (options.add[:4] == "http" or options.add[:3] == "ftp"):
|
||||
console.add_channel(options.add)
|
||||
else:
|
||||
print 'unknown channel: %s' % options.add
|
||||
|
||||
elif options.delete:
|
||||
try:
|
||||
chid = int(options.delete)
|
||||
console.del_channel(chid)
|
||||
print "channel IDs may have changed, list before deleting again"
|
||||
except ValueError:
|
||||
print '%s is not a valid id' %options.delete
|
||||
|
||||
else:
|
||||
#default run gui
|
||||
from gpodder import gpodder
|
||||
if options.debug:
|
||||
gpodder.glade_dir = "../data"
|
||||
gpodder.main( __version__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
10
data/gpodder.desktop
Normal file
10
data/gpodder.desktop
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Desktop Entry]
|
||||
Name=gPodder
|
||||
Comment=Media Agregator
|
||||
Exec=gpodder
|
||||
Icon=/usr/share/gpodder/images/gpodder.png
|
||||
Miniicon=/usr/share/gpodder/images/gpodder.png
|
||||
GenericName=Media Agregator
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=AudioVideo;Audio
|
1017
data/gpodder.glade
Normal file
1017
data/gpodder.glade
Normal file
File diff suppressed because it is too large
Load diff
BIN
data/gpodder.png
Normal file
BIN
data/gpodder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
35
doc/man/gpodder.man.1
Normal file
35
doc/man/gpodder.man.1
Normal file
|
@ -0,0 +1,35 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.35.
|
||||
.TH GPODDER "1" "November 2005" "gpodder SVN.20051120 2005-11-20" "User Commands"
|
||||
.SH NAME
|
||||
gpodder \- manual page for gpodder SVN.20051120 2005-11-20
|
||||
.SH DESCRIPTION
|
||||
usage: gpodder [options] arg1 arg2
|
||||
gPodder Media Aggregator
|
||||
.SS "options:"
|
||||
.TP
|
||||
\fB\-\-version\fR
|
||||
show program's version number and exit
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
show this help message and exit
|
||||
.TP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
print extra information
|
||||
.TP
|
||||
\fB\-\-debug\fR
|
||||
run local version for debugging
|
||||
.TP
|
||||
\fB\-l\fR, \fB\-\-list\fR
|
||||
list all channel subscriptions
|
||||
.TP
|
||||
\fB\-r\fR, \fB\-\-run\fR
|
||||
update channels and download new podcasts
|
||||
.TP
|
||||
\fB\-u\fR, \fB\-\-update\fR
|
||||
update channels
|
||||
.TP
|
||||
\fB\-aFEEDURL\fR, \fB\-\-add\fR=\fIFEEDURL\fR
|
||||
add FEEDURL to channel subscriptions
|
||||
.TP
|
||||
\fB\-dn\fR, \fB\-\-delete\fR=\fIn\fR
|
||||
delete channel n
|
41
setup.py
Normal file
41
setup.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# gPodder setup script
|
||||
|
||||
|
||||
import glob
|
||||
import os
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
# read the version from the gpodder main program
|
||||
gpodder_version = os.popen( "cat bin/gpodder |grep ^__version__.*=|cut -d\\\" -f2").read().strip()
|
||||
|
||||
|
||||
inst_manpages = glob.glob( 'doc/man/*.1')
|
||||
inst_images = [ 'data/gpodder.png' ]
|
||||
inst_share = [ 'data/gpodder.glade' ]
|
||||
inst_icons = [ 'data/gpodder.desktop' ]
|
||||
|
||||
|
||||
data_files = [
|
||||
('share/man/man1', inst_manpages),
|
||||
('share/gpodder/images', inst_images),
|
||||
('share/gpodder', inst_share),
|
||||
('share/applications', inst_icons)
|
||||
]
|
||||
|
||||
|
||||
setup(
|
||||
name = 'gPodder',
|
||||
version = gpodder_version,
|
||||
package_dir = { '':'src' },
|
||||
packages = [ 'gpodder' ],
|
||||
description = 'Media Aggregator',
|
||||
author = 'Thomas Perl',
|
||||
author_email = 'thp@perli.net',
|
||||
url = 'http://perli.net/projekte/gpodder/',
|
||||
scripts = [ 'bin/gpodder' ],
|
||||
data_files = data_files
|
||||
)
|
||||
|
341
src/gpodder/SimpleGladeApp.py
Normal file
341
src/gpodder/SimpleGladeApp.py
Normal file
|
@ -0,0 +1,341 @@
|
|||
"""
|
||||
SimpleGladeApp.py
|
||||
Module that provides an object oriented abstraction to pygtk and libglade.
|
||||
Copyright (C) 2004 Sandino Flores Moreno
|
||||
"""
|
||||
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
import tokenize
|
||||
import gtk
|
||||
import gtk.glade
|
||||
import weakref
|
||||
import inspect
|
||||
|
||||
__version__ = "1.0"
|
||||
__author__ = 'Sandino "tigrux" Flores-Moreno'
|
||||
|
||||
def bindtextdomain(app_name, locale_dir=None):
|
||||
"""
|
||||
Bind the domain represented by app_name to the locale directory locale_dir.
|
||||
It has the effect of loading translations, enabling applications for different
|
||||
languages.
|
||||
|
||||
app_name:
|
||||
a domain to look for translations, tipically the name of an application.
|
||||
|
||||
locale_dir:
|
||||
a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo
|
||||
If omitted or None, then the current binding for app_name is used.
|
||||
"""
|
||||
try:
|
||||
import locale
|
||||
import gettext
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
gtk.glade.bindtextdomain(app_name, locale_dir)
|
||||
gettext.install(app_name, locale_dir, unicode=1)
|
||||
except (IOError,locale.Error), e:
|
||||
print "Warning", app_name, e
|
||||
__builtins__.__dict__["_"] = lambda x : x
|
||||
|
||||
|
||||
class SimpleGladeApp:
|
||||
|
||||
def __init__(self, path, root=None, domain=None, **kwargs):
|
||||
"""
|
||||
Load a glade file specified by glade_filename, using root as
|
||||
root widget and domain as the domain for translations.
|
||||
|
||||
If it receives extra named arguments (argname=value), then they are used
|
||||
as attributes of the instance.
|
||||
|
||||
path:
|
||||
path to a glade filename.
|
||||
If glade_filename cannot be found, then it will be searched in the
|
||||
same directory of the program (sys.argv[0])
|
||||
|
||||
root:
|
||||
the name of the widget that is the root of the user interface,
|
||||
usually a window or dialog (a top level widget).
|
||||
If None or ommited, the full user interface is loaded.
|
||||
|
||||
domain:
|
||||
A domain to use for loading translations.
|
||||
If None or ommited, no translation is loaded.
|
||||
|
||||
**kwargs:
|
||||
a dictionary representing the named extra arguments.
|
||||
It is useful to set attributes of new instances, for example:
|
||||
glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value")
|
||||
sets two attributes (foo and bar) to glade_app.
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
self.glade_path = path
|
||||
else:
|
||||
glade_dir = os.path.dirname( sys.argv[0] )
|
||||
self.glade_path = os.path.join(glade_dir, path)
|
||||
for key, value in kwargs.items():
|
||||
try:
|
||||
setattr(self, key, weakref.proxy(value) )
|
||||
except TypeError:
|
||||
setattr(self, key, value)
|
||||
self.glade = None
|
||||
self.install_custom_handler(self.custom_handler)
|
||||
self.glade = self.create_glade(self.glade_path, root, domain)
|
||||
if root:
|
||||
self.main_widget = self.get_widget(root)
|
||||
else:
|
||||
self.main_widget = None
|
||||
self.normalize_names()
|
||||
self.add_callbacks(self)
|
||||
self.new()
|
||||
|
||||
def __repr__(self):
|
||||
class_name = self.__class__.__name__
|
||||
if self.main_widget:
|
||||
root = gtk.Widget.get_name(self.main_widget)
|
||||
repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root)
|
||||
else:
|
||||
repr = '%s(path="%s")' % (class_name, self.glade_path)
|
||||
return repr
|
||||
|
||||
def new(self):
|
||||
"""
|
||||
Method called when the user interface is loaded and ready to be used.
|
||||
At this moment, the widgets are loaded and can be refered as self.widget_name
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_callbacks(self, callbacks_proxy):
|
||||
"""
|
||||
It uses the methods of callbacks_proxy as callbacks.
|
||||
The callbacks are specified by using:
|
||||
Properties window -> Signals tab
|
||||
in glade-2 (or any other gui designer like gazpacho).
|
||||
|
||||
Methods of classes inheriting from SimpleGladeApp are used as
|
||||
callbacks automatically.
|
||||
|
||||
callbacks_proxy:
|
||||
an instance with methods as code of callbacks.
|
||||
It means it has methods like on_button1_clicked, on_entry1_activate, etc.
|
||||
"""
|
||||
self.glade.signal_autoconnect(callbacks_proxy)
|
||||
|
||||
def normalize_names(self):
|
||||
"""
|
||||
It is internally used to normalize the name of the widgets.
|
||||
It means a widget named foo:vbox-dialog in glade
|
||||
is refered self.vbox_dialog in the code.
|
||||
|
||||
It also sets a data "prefixes" with the list of
|
||||
prefixes a widget has for each widget.
|
||||
"""
|
||||
for widget in self.get_widgets():
|
||||
widget_name = gtk.Widget.get_name(widget)
|
||||
prefixes_name_l = widget_name.split(":")
|
||||
prefixes = prefixes_name_l[ : -1]
|
||||
widget_api_name = prefixes_name_l[-1]
|
||||
widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) )
|
||||
gtk.Widget.set_name(widget, widget_api_name)
|
||||
if hasattr(self, widget_api_name):
|
||||
raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name))
|
||||
else:
|
||||
setattr(self, widget_api_name, widget)
|
||||
if prefixes:
|
||||
gtk.Widget.set_data(widget, "prefixes", prefixes)
|
||||
|
||||
def add_prefix_actions(self, prefix_actions_proxy):
|
||||
"""
|
||||
By using a gui designer (glade-2, gazpacho, etc)
|
||||
widgets can have a prefix in theirs names
|
||||
like foo:entry1 or foo:label3
|
||||
It means entry1 and label3 has a prefix action named foo.
|
||||
|
||||
Then, prefix_actions_proxy must have a method named prefix_foo which
|
||||
is called everytime a widget with prefix foo is found, using the found widget
|
||||
as argument.
|
||||
|
||||
prefix_actions_proxy:
|
||||
An instance with methods as prefix actions.
|
||||
It means it has methods like prefix_foo, prefix_bar, etc.
|
||||
"""
|
||||
prefix_s = "prefix_"
|
||||
prefix_pos = len(prefix_s)
|
||||
|
||||
is_method = lambda t : callable( t[1] )
|
||||
is_prefix_action = lambda t : t[0].startswith(prefix_s)
|
||||
drop_prefix = lambda (k,w): (k[prefix_pos:],w)
|
||||
|
||||
members_t = inspect.getmembers(prefix_actions_proxy)
|
||||
methods_t = filter(is_method, members_t)
|
||||
prefix_actions_t = filter(is_prefix_action, methods_t)
|
||||
prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) )
|
||||
|
||||
for widget in self.get_widgets():
|
||||
prefixes = gtk.Widget.get_data(widget, "prefixes")
|
||||
if prefixes:
|
||||
for prefix in prefixes:
|
||||
if prefix in prefix_actions_d:
|
||||
prefix_action = prefix_actions_d[prefix]
|
||||
prefix_action(widget)
|
||||
|
||||
def custom_handler(self,
|
||||
glade, function_name, widget_name,
|
||||
str1, str2, int1, int2):
|
||||
"""
|
||||
Generic handler for creating custom widgets, internally used to
|
||||
enable custom widgets (custom widgets of glade).
|
||||
|
||||
The custom widgets have a creation function specified in design time.
|
||||
Those creation functions are always called with str1,str2,int1,int2 as
|
||||
arguments, that are values specified in design time.
|
||||
|
||||
Methods of classes inheriting from SimpleGladeApp are used as
|
||||
creation functions automatically.
|
||||
|
||||
If a custom widget has create_foo as creation function, then the
|
||||
method named create_foo is called with str1,str2,int1,int2 as arguments.
|
||||
"""
|
||||
try:
|
||||
handler = getattr(self, function_name)
|
||||
return handler(str1, str2, int1, int2)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def gtk_widget_show(self, widget, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
The widget is showed.
|
||||
Equivalent to widget.show()
|
||||
"""
|
||||
widget.show()
|
||||
|
||||
def gtk_widget_hide(self, widget, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
The widget is hidden.
|
||||
Equivalent to widget.hide()
|
||||
"""
|
||||
widget.hide()
|
||||
|
||||
def gtk_widget_grab_focus(self, widget, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
The widget grabs the focus.
|
||||
Equivalent to widget.grab_focus()
|
||||
"""
|
||||
widget.grab_focus()
|
||||
|
||||
def gtk_widget_destroy(self, widget, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
The widget is destroyed.
|
||||
Equivalent to widget.destroy()
|
||||
"""
|
||||
widget.destroy()
|
||||
|
||||
def gtk_window_activate_default(self, window, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
The default widget of the window is activated.
|
||||
Equivalent to window.activate_default()
|
||||
"""
|
||||
widget.activate_default()
|
||||
|
||||
def gtk_true(self, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
Equivalent to return True in a callback.
|
||||
Useful for stopping propagation of signals.
|
||||
"""
|
||||
return True
|
||||
|
||||
def gtk_false(self, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
Equivalent to return False in a callback.
|
||||
"""
|
||||
return False
|
||||
|
||||
def gtk_main_quit(self, *args):
|
||||
"""
|
||||
Predefined callback.
|
||||
Equivalent to self.quit()
|
||||
"""
|
||||
self.quit()
|
||||
|
||||
def main(self):
|
||||
"""
|
||||
Starts the main loop of processing events.
|
||||
The default implementation calls gtk.main()
|
||||
|
||||
Useful for applications that needs a non gtk main loop.
|
||||
For example, applications based on gstreamer needs to override
|
||||
this method with gst.main()
|
||||
|
||||
Do not directly call this method in your programs.
|
||||
Use the method run() instead.
|
||||
"""
|
||||
gtk.main()
|
||||
|
||||
def quit(self):
|
||||
"""
|
||||
Quit processing events.
|
||||
The default implementation calls gtk.main_quit()
|
||||
|
||||
Useful for applications that needs a non gtk main loop.
|
||||
For example, applications based on gstreamer needs to override
|
||||
this method with gst.main_quit()
|
||||
"""
|
||||
gtk.main_quit()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Starts the main loop of processing events checking for Control-C.
|
||||
|
||||
The default implementation checks wheter a Control-C is pressed,
|
||||
then calls on_keyboard_interrupt().
|
||||
|
||||
Use this method for starting programs.
|
||||
"""
|
||||
try:
|
||||
self.main()
|
||||
except KeyboardInterrupt:
|
||||
self.on_keyboard_interrupt()
|
||||
|
||||
def on_keyboard_interrupt(self):
|
||||
"""
|
||||
This method is called by the default implementation of run()
|
||||
after a program is finished by pressing Control-C.
|
||||
"""
|
||||
pass
|
||||
|
||||
def install_custom_handler(self, custom_handler):
|
||||
gtk.glade.set_custom_handler(custom_handler)
|
||||
|
||||
def create_glade(self, glade_path, root, domain):
|
||||
return gtk.glade.XML(self.glade_path, root, domain)
|
||||
|
||||
def get_widget(self, widget_name):
|
||||
return self.glade.get_widget(widget_name)
|
||||
|
||||
def get_widgets(self):
|
||||
return self.glade.get_widget_prefix("")
|
0
src/gpodder/__init__.py
Normal file
0
src/gpodder/__init__.py
Normal file
91
src/gpodder/console.py
Normal file
91
src/gpodder/console.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from libgpodder import gPodderChannelReader
|
||||
from libgpodder import gPodderChannelWriter
|
||||
from libgpodder import gPodderLib
|
||||
from libpodcasts import podcastChannel
|
||||
from librssreader import rssReader
|
||||
from libwget import downloadThread
|
||||
|
||||
|
||||
#TODO: move to libwget??
|
||||
class DownloadPool:
|
||||
def __init__(self, max_downloads = 3):
|
||||
assert(max_downloads > 0)
|
||||
|
||||
self.max_downloads = max_downloads
|
||||
self.cur_downloads = 0
|
||||
|
||||
def addone(self):
|
||||
self.cur_downloads += 1
|
||||
|
||||
def set(self):
|
||||
'''Ping function for downloadThread'''
|
||||
if self.cur_downloads >0:
|
||||
self.cur_downloads -= 1
|
||||
else:
|
||||
self.cur_downloads = 0
|
||||
|
||||
def may_download(self):
|
||||
return self.cur_downloads < self.max_downloads
|
||||
|
||||
|
||||
def list_channels():
|
||||
reader = gPodderChannelReader()
|
||||
reader.read()
|
||||
for id, channel in enumerate(reader.channels):
|
||||
print "%s: %s" %(id, channel.url)
|
||||
|
||||
def add_channel(url):
|
||||
reader = gPodderChannelReader()
|
||||
channels = reader.read()
|
||||
|
||||
cachefile = gPodderLib().downloadRss(url)
|
||||
rssreader = rssReader()
|
||||
rssreader.parseXML(url, cachefile)
|
||||
|
||||
channels.append(rssreader.channel)
|
||||
gPodderChannelWriter().write(channels)
|
||||
|
||||
def del_channel(chid):
|
||||
#TODO maybe add id to channels.xml
|
||||
reader = gPodderChannelReader()
|
||||
channels = reader.read()
|
||||
if chid >=0 and chid < len(channels):
|
||||
ch = channels.pop(chid)
|
||||
print 'delete channel: %s' %ch.url
|
||||
gPodderChannelWriter().write(channels)
|
||||
else:
|
||||
print '%s is not a valid id' %str(chid)
|
||||
|
||||
|
||||
def update():
|
||||
reader = gPodderChannelReader()
|
||||
reader.read(True)
|
||||
|
||||
|
||||
def run():
|
||||
'''Update channels und download all items which are not already downloaded'''
|
||||
reader = gPodderChannelReader()
|
||||
updated_channels = reader.read(True)
|
||||
|
||||
pool = DownloadPool()
|
||||
|
||||
for channel in updated_channels:
|
||||
for item in channel.items:
|
||||
filename = gPodderLib().getPodcastFilename(channel, item.url)
|
||||
#TODO: do not check here with os.path. This should be part of item/gPodderLib ala item.already_downloaded()
|
||||
if not os.path.exists(filename):
|
||||
while not pool.may_download():
|
||||
time.sleep(3)
|
||||
|
||||
pool.addone()
|
||||
#thread will call pool.set() when finished
|
||||
thread = downloadThread(item.url, filename, pool)
|
||||
thread.download()
|
||||
|
||||
|
415
src/gpodder/gpodder.py
Normal file
415
src/gpodder/gpodder.py
Normal file
|
@ -0,0 +1,415 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
# Python module src/gpodder/gpodder.py
|
||||
# Autogenerated from gpodder.glade
|
||||
# Generated on Mon Nov 21 19:17:24 2005
|
||||
|
||||
# Warning: Do not modify any context comment such as #--
|
||||
# They are required to keep user's code
|
||||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
import os
|
||||
import gtk
|
||||
import gobject
|
||||
import sys
|
||||
from threading import Event
|
||||
|
||||
|
||||
from SimpleGladeApp import SimpleGladeApp
|
||||
from SimpleGladeApp import bindtextdomain
|
||||
|
||||
from libpodcasts import podcastChannel
|
||||
from libpodcasts import podcastItem
|
||||
|
||||
from libpodcasts import channelsToModel
|
||||
|
||||
from librssreader import rssReader
|
||||
from libwget import downloadThread
|
||||
|
||||
from libgpodder import gPodderLib
|
||||
from libgpodder import gPodderChannelReader
|
||||
from libgpodder import gPodderChannelWriter
|
||||
|
||||
|
||||
app_name = "gpodder"
|
||||
app_version = "unknown" # will be set in main() call
|
||||
app_authors = [ "Thomas Perl <thp@perli.net>" ]
|
||||
app_copyright = "Copyright (c) 2005 Thomas Perl"
|
||||
app_website = "http://www.perli.net/projekte/gpodder/"
|
||||
|
||||
#glade_dir = "../data"
|
||||
glade_dir = "/usr/share/gpodder/"
|
||||
locale_dir = ""
|
||||
|
||||
bindtextdomain(app_name, locale_dir)
|
||||
|
||||
|
||||
class Gpodder(SimpleGladeApp):
|
||||
channels = []
|
||||
|
||||
active_item = None
|
||||
items_model = None
|
||||
|
||||
active_channel = None
|
||||
channels_model = None
|
||||
|
||||
channels_loaded = False
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodder",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodder.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#self.gPodder.set_title( self.gPodder.get_title())
|
||||
#self.statusLabel.set_text( "Welcome to gPodder! Suggestions? Mail to: thp@perli.net")
|
||||
# set up the rendering of the comboAvailable combobox
|
||||
cellrenderer = gtk.CellRendererText()
|
||||
self.comboAvailable.pack_start( cellrenderer, True)
|
||||
self.comboAvailable.add_attribute( cellrenderer, 'text', 1)
|
||||
|
||||
# set up the rendering of the treeAvailable treeview
|
||||
itemcolumns = [
|
||||
#gtk.TreeViewColumn( "", gtk.CellRendererToggle(), active=3),
|
||||
gtk.TreeViewColumn( "Episode", gtk.CellRendererText(), text=1),
|
||||
gtk.TreeViewColumn( "Size", gtk.CellRendererText(), text=2)
|
||||
]
|
||||
|
||||
for itemcolumn in itemcolumns:
|
||||
self.treeAvailable.append_column( itemcolumn)
|
||||
|
||||
# xml test
|
||||
#reader = rssReader()
|
||||
#reader.parseXML( "http://www.perli.net", "test.xml")
|
||||
#self.channels.append( reader.channel)
|
||||
#reader.parseXML( "http://www.lugradio.org/episodes.rss", "episodes.rss")
|
||||
#self.channels.append( reader.channel)
|
||||
reader = gPodderChannelReader()
|
||||
self.channels = reader.read( False)
|
||||
self.channels_loaded = True
|
||||
|
||||
# update view
|
||||
self.updateComboBox()
|
||||
#-- Gpodder.new }
|
||||
|
||||
#-- Gpodder custom methods {
|
||||
# Write your own methods here
|
||||
def updateComboBox( self):
|
||||
self.channels_model = channelsToModel( self.channels)
|
||||
|
||||
self.comboAvailable.set_model( self.channels_model)
|
||||
try:
|
||||
self.comboAvailable.set_active( 0)
|
||||
except:
|
||||
print "probably no channels found"
|
||||
#self.updateTreeView()
|
||||
|
||||
def updateTreeView( self):
|
||||
try:
|
||||
self.items_model = self.channels[self.active_channel].getItemsModel()
|
||||
self.treeAvailable.set_model( self.items_model)
|
||||
except:
|
||||
dlg = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
dlg.set_markup( "<big>No Channels found</big>\n\nClick on \"Channels\"->\"Add channel..\" to add a new channel.")
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
print "probably no feeds or channels found"
|
||||
|
||||
def not_implemented( self, message = "some unknown function"):
|
||||
dlg = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
dlg.set_title( "Release early, release often!")
|
||||
dlg.set_markup( "<big>Woohoo! You've found a secret..</big>\n\nSorry, but due to the fact that this is just a pre-release, the lazy programmer has just forgotten to implement\n\n<b>" + message + "</b>\n\nHe promises he will in the next release!")
|
||||
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
#-- Gpodder custom methods }
|
||||
|
||||
#-- Gpodder.close_gpodder {
|
||||
def close_gpodder(self, widget, *args):
|
||||
print "close_gpodder called with self.%s" % widget.get_name()
|
||||
|
||||
if self.channels_loaded:
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
|
||||
self.gtk_main_quit()
|
||||
#-- Gpodder.close_gpodder }
|
||||
|
||||
#-- Gpodder.on_itemUpdate_activate {
|
||||
def on_itemUpdate_activate(self, widget, *args):
|
||||
print "on_itemUpdate_activate called with self.%s" % widget.get_name()
|
||||
reader = gPodderChannelReader()
|
||||
#self.channels = reader.read( True)
|
||||
#self.labelStatus.set_text( "Updating feed cache...")
|
||||
please_wait = gtk.MessageDialog()
|
||||
please_wait.set_markup( "<big><b>Updating feed cache</b></big>\n\nPlease wait while gPodder is\nupdating the feed cache...")
|
||||
please_wait.show()
|
||||
self.channels = reader.read( True)
|
||||
please_wait.destroy()
|
||||
#self.labelStatus.set_text( "")
|
||||
self.updateComboBox()
|
||||
#-- Gpodder.on_itemUpdate_activate }
|
||||
|
||||
#-- Gpodder.on_itemPreferences_activate {
|
||||
def on_itemPreferences_activate(self, widget, *args):
|
||||
print "on_itemPreferences_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the preferences dialog")
|
||||
#-- Gpodder.on_itemPreferences_activate }
|
||||
|
||||
#-- Gpodder.on_itemAddChannel_activate {
|
||||
def on_itemAddChannel_activate(self, widget, *args):
|
||||
print "on_itemAddChannel_activate called with self.%s" % widget.get_name()
|
||||
result = Gpodderchannel().requestURL()
|
||||
if result != None and result != "" and (result[:4] == "http" or result[:3] == "ftp"):
|
||||
print "will ADD: " + result
|
||||
self.statusLabel.set_text( "Fetching channel index...")
|
||||
channel_new = podcastChannel( result)
|
||||
channel_new.shortname = "__unknown__"
|
||||
self.channels.append( channel_new)
|
||||
|
||||
# fetch metadata for that channel
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
|
||||
# fetch feed for that channel
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
|
||||
self.updateComboBox()
|
||||
self.statusLabel.set_text( "")
|
||||
#-- Gpodder.on_itemAddChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemEditChannel_activate {
|
||||
def on_itemEditChannel_activate(self, widget, *args):
|
||||
print "on_itemEditChannel_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the edit channel dialog")
|
||||
#print self.channels[self.active_channel].title
|
||||
#-- Gpodder.on_itemEditChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemRemoveChannel_activate {
|
||||
def on_itemRemoveChannel_activate(self, widget, *args):
|
||||
print "on_itemRemoveChannel_activate called with self.%s" % widget.get_name()
|
||||
try:
|
||||
self.channels.remove( self.channels[self.active_channel])
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
self.updateComboBox()
|
||||
except:
|
||||
print "could not delete - nothing selected, probably"
|
||||
#-- Gpodder.on_itemRemoveChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemExportChannels_activate {
|
||||
def on_itemExportChannels_activate(self, widget, *args):
|
||||
print "on_itemExportChannels_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the export feature")
|
||||
#-- Gpodder.on_itemExportChannels_activate }
|
||||
|
||||
#-- Gpodder.on_itemAbout_activate {
|
||||
def on_itemAbout_activate(self, widget, *args):
|
||||
print "on_itemAbout_activate called with self.%s" % widget.get_name()
|
||||
dlg = gtk.AboutDialog()
|
||||
dlg.set_name( app_name)
|
||||
dlg.set_version( app_version)
|
||||
dlg.set_authors( app_authors)
|
||||
dlg.set_copyright( app_copyright)
|
||||
dlg.set_website( app_website)
|
||||
#FIXME: add hanlding hiere dlg.set_logo( gtk.gdk.pixbuf_new_from_file_at_size( "gpodder.png", 164, 164))
|
||||
dlg.run()
|
||||
#-- Gpodder.on_itemAbout_activate }
|
||||
|
||||
#-- Gpodder.on_comboAvailable_changed {
|
||||
def on_comboAvailable_changed(self, widget, *args):
|
||||
print "on_comboAvailable_changed called with self.%s" % widget.get_name()
|
||||
self.active_channel = self.comboAvailable.get_active()
|
||||
self.updateTreeView()
|
||||
#-- Gpodder.on_comboAvailable_changed }
|
||||
|
||||
#-- Gpodder.on_treeAvailable_row_activated {
|
||||
def on_treeAvailable_row_activated(self, widget, *args):
|
||||
print "on_treeAvailable_row_activated called with self.%s" % widget.get_name()
|
||||
selection_tuple = self.treeAvailable.get_selection().get_selected()
|
||||
selection_iter = selection_tuple[1]
|
||||
url = self.items_model.get_value( selection_iter, 0)
|
||||
|
||||
self.active_item = self.channels[self.active_channel].getActiveByUrl( url)
|
||||
|
||||
status = Gpodderstatus()
|
||||
current_channel = self.channels[self.active_channel]
|
||||
current_podcast = current_channel.items[self.active_item]
|
||||
if status.setup( current_channel, current_podcast):
|
||||
status.download()
|
||||
else:
|
||||
message_dialog = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
message_dialog.set_markup( "<big><b>Already downloaded</b></big>\n\nYou have already downloaded this episode.")
|
||||
message_dialog.run()
|
||||
message_dialog.destroy()
|
||||
#-- Gpodder.on_treeAvailable_row_activated }
|
||||
|
||||
#-- Gpodder.on_btnDownload_clicked {
|
||||
def on_btnDownload_clicked(self, widget, *args):
|
||||
print "on_btnDownload_clicked called with self.%s" % widget.get_name()
|
||||
self.on_treeAvailable_row_activated( widget, args)
|
||||
#-- Gpodder.on_btnDownload_clicked }
|
||||
|
||||
|
||||
class Gpodderstatus(SimpleGladeApp):
|
||||
event = None
|
||||
channel = None
|
||||
podcast = None
|
||||
thread = None
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderStatus",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderstatus.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderstatus.new }
|
||||
|
||||
#-- Gpodderstatus custom methods {
|
||||
# Write your own methods here
|
||||
def setup( self, channel, podcast):
|
||||
self.channel = channel
|
||||
self.podcast = podcast
|
||||
|
||||
self.labelFrom.set_markup( "<b>" + self.channel.title + "</b>")
|
||||
self.labelFilename.set_markup( "<b>" + self.podcast.title + "</b>")
|
||||
|
||||
filename = gPodderLib().getPodcastFilename( self.channel, self.podcast.url)
|
||||
|
||||
if os.path.exists( filename) == False:
|
||||
self.event = Event()
|
||||
self.thread = downloadThread( self.podcast.url, filename, self.event)
|
||||
return True
|
||||
else:
|
||||
self.cancel()
|
||||
return False
|
||||
|
||||
def download( self):
|
||||
self.thread.download()
|
||||
|
||||
while self.event.isSet() == False:
|
||||
self.event.wait( 0.1)
|
||||
|
||||
self.labelSpeed.set_text( self.thread.speed)
|
||||
self.progressBar.set_fraction( float(self.thread.percentage))
|
||||
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
|
||||
self.gPodderStatus.destroy()
|
||||
|
||||
def cancel( self):
|
||||
self.on_btnCancel_clicked( self.btnCancel, None)
|
||||
#-- Gpodderstatus custom methods }
|
||||
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy {
|
||||
def on_gPodderStatus_destroy(self, widget, *args):
|
||||
print "on_gPodderStatus_destroy called with self.%s" % widget.get_name()
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy }
|
||||
|
||||
#-- Gpodderstatus.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
|
||||
if self.thread != None:
|
||||
self.thread.cancel()
|
||||
|
||||
while self.event != None and self.event.isSet() == False:
|
||||
None
|
||||
|
||||
self.gPodderStatus.destroy()
|
||||
#-- Gpodderstatus.on_btnCancel_clicked }
|
||||
|
||||
|
||||
class Gpodderchannel(SimpleGladeApp):
|
||||
waiting = None
|
||||
url = ""
|
||||
result = False
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderChannel",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderchannel.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderchannel.new }
|
||||
|
||||
#-- Gpodderchannel custom methods {
|
||||
# Write your own methods here
|
||||
def requestURL( self, message = None):
|
||||
if message != None:
|
||||
self.labelIntroduction.set_text( message)
|
||||
|
||||
self.waiting = Event()
|
||||
while self.waiting.isSet() == False:
|
||||
self.waiting.wait( 0.01)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
|
||||
if self.result == True:
|
||||
return self.url
|
||||
else:
|
||||
return None
|
||||
#-- Gpodderchannel custom methods }
|
||||
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy {
|
||||
def on_gPodderChannel_destroy(self, widget, *args):
|
||||
print "on_gPodderChannel_destroy called with self.%s" % widget.get_name()
|
||||
self.result = False
|
||||
self.url = self.entryURL.get_text()
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy }
|
||||
|
||||
#-- Gpodderchannel.on_btnOK_clicked {
|
||||
def on_btnOK_clicked(self, widget, *args):
|
||||
print "on_btnOK_clicked called with self.%s" % widget.get_name()
|
||||
self.gPodderChannel.destroy()
|
||||
self.result = True
|
||||
|
||||
if self.waiting != None:
|
||||
self.waiting.set()
|
||||
#-- Gpodderchannel.on_btnOK_clicked }
|
||||
|
||||
#-- Gpodderchannel.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
self.gPodderChannel.destroy()
|
||||
self.result = False
|
||||
|
||||
if self.waiting != None:
|
||||
self.waiting.set()
|
||||
#-- Gpodderchannel.on_btnCancel_clicked }
|
||||
|
||||
|
||||
#-- main {
|
||||
|
||||
def main( __version__ = None):
|
||||
global app_version
|
||||
|
||||
gtk.gdk.threads_init()
|
||||
app_version = __version__
|
||||
g_podder = Gpodder()
|
||||
#g_podder_status = Gpodderstatus()
|
||||
#g_podder_channel = Gpodderchannel()
|
||||
|
||||
g_podder.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "please run the gpodder binary, not this file"
|
||||
sys.exit( -1)
|
||||
|
||||
#-- main }
|
415
src/gpodder/gpodder.py.bak
Normal file
415
src/gpodder/gpodder.py.bak
Normal file
|
@ -0,0 +1,415 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
# Python module src/gpodder/gpodder.py
|
||||
# Autogenerated from gpodder.glade
|
||||
# Generated on Sun Nov 20 22:21:30 2005
|
||||
|
||||
# Warning: Do not modify any context comment such as #--
|
||||
# They are required to keep user's code
|
||||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
import os
|
||||
import gtk
|
||||
import gobject
|
||||
import sys
|
||||
from threading import Event
|
||||
|
||||
|
||||
from SimpleGladeApp import SimpleGladeApp
|
||||
from SimpleGladeApp import bindtextdomain
|
||||
|
||||
from libpodcasts import podcastChannel
|
||||
from libpodcasts import podcastItem
|
||||
|
||||
from libpodcasts import channelsToModel
|
||||
|
||||
from librssreader import rssReader
|
||||
from libwget import downloadThread
|
||||
|
||||
from libgpodder import gPodderLib
|
||||
from libgpodder import gPodderChannelReader
|
||||
from libgpodder import gPodderChannelWriter
|
||||
|
||||
|
||||
app_name = "gpodder"
|
||||
app_version = "unknown" # will be set in main() call
|
||||
app_authors = [ "Thomas Perl <thp@perli.net>" ]
|
||||
app_copyright = "Copyright (c) 2005 Thomas Perl"
|
||||
app_website = "http://www.perli.net/projekte/gpodder/"
|
||||
|
||||
#glade_dir = "../data"
|
||||
glade_dir = "/usr/share/gpodder/"
|
||||
locale_dir = ""
|
||||
|
||||
bindtextdomain(app_name, locale_dir)
|
||||
|
||||
|
||||
class Gpodder(SimpleGladeApp):
|
||||
channels = []
|
||||
|
||||
active_item = None
|
||||
items_model = None
|
||||
|
||||
active_channel = None
|
||||
channels_model = None
|
||||
|
||||
channels_loaded = False
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodder",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodder.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#self.gPodder.set_title( self.gPodder.get_title())
|
||||
#self.statusLabel.set_text( "Welcome to gPodder! Suggestions? Mail to: thp@perli.net")
|
||||
# set up the rendering of the comboAvailable combobox
|
||||
cellrenderer = gtk.CellRendererText()
|
||||
self.comboAvailable.pack_start( cellrenderer, True)
|
||||
self.comboAvailable.add_attribute( cellrenderer, 'text', 1)
|
||||
|
||||
# set up the rendering of the treeAvailable treeview
|
||||
itemcolumns = [
|
||||
#gtk.TreeViewColumn( "", gtk.CellRendererToggle(), active=3),
|
||||
gtk.TreeViewColumn( "Episode", gtk.CellRendererText(), text=1),
|
||||
gtk.TreeViewColumn( "Size", gtk.CellRendererText(), text=2)
|
||||
]
|
||||
|
||||
for itemcolumn in itemcolumns:
|
||||
self.treeAvailable.append_column( itemcolumn)
|
||||
|
||||
# xml test
|
||||
#reader = rssReader()
|
||||
#reader.parseXML( "http://www.perli.net", "test.xml")
|
||||
#self.channels.append( reader.channel)
|
||||
#reader.parseXML( "http://www.lugradio.org/episodes.rss", "episodes.rss")
|
||||
#self.channels.append( reader.channel)
|
||||
reader = gPodderChannelReader()
|
||||
self.channels = reader.read( False)
|
||||
self.channels_loaded = True
|
||||
|
||||
# update view
|
||||
self.updateComboBox()
|
||||
#-- Gpodder.new }
|
||||
|
||||
#-- Gpodder custom methods {
|
||||
# Write your own methods here
|
||||
def updateComboBox( self):
|
||||
self.channels_model = channelsToModel( self.channels)
|
||||
|
||||
self.comboAvailable.set_model( self.channels_model)
|
||||
try:
|
||||
self.comboAvailable.set_active( 0)
|
||||
except:
|
||||
print "probably no channels found"
|
||||
#self.updateTreeView()
|
||||
|
||||
def updateTreeView( self):
|
||||
try:
|
||||
self.items_model = self.channels[self.active_channel].getItemsModel()
|
||||
self.treeAvailable.set_model( self.items_model)
|
||||
except:
|
||||
dlg = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
dlg.set_markup( "<big>No Channels found</big>\n\nClick on \"Channels\"->\"Add channel..\" to add a new channel.")
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
print "probably no feeds or channels found"
|
||||
|
||||
def not_implemented( self, message = "some unknown function"):
|
||||
dlg = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
dlg.set_title( "Release early, release often!")
|
||||
dlg.set_markup( "<big>Woohoo! You've found a secret..</big>\n\nSorry, but due to the fact that this is just a pre-release, the lazy programmer has just forgotten to implement\n\n<b>" + message + "</b>\n\nHe promises he will in the next release!")
|
||||
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
#-- Gpodder custom methods }
|
||||
|
||||
#-- Gpodder.close_gpodder {
|
||||
def close_gpodder(self, widget, *args):
|
||||
print "close_gpodder called with self.%s" % widget.get_name()
|
||||
|
||||
if self.channels_loaded:
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
|
||||
self.gtk_main_quit()
|
||||
#-- Gpodder.close_gpodder }
|
||||
|
||||
#-- Gpodder.on_itemUpdate_activate {
|
||||
def on_itemUpdate_activate(self, widget, *args):
|
||||
print "on_itemUpdate_activate called with self.%s" % widget.get_name()
|
||||
reader = gPodderChannelReader()
|
||||
#self.channels = reader.read( True)
|
||||
#self.labelStatus.set_text( "Updating feed cache...")
|
||||
please_wait = gtk.MessageDialog()
|
||||
please_wait.set_markup( "<big><b>Updating feed cache</b></big>\n\nPlease wait while gPodder is\nupdating the feed cache...")
|
||||
please_wait.show()
|
||||
self.channels = reader.read( True)
|
||||
please_wait.destroy()
|
||||
#self.labelStatus.set_text( "")
|
||||
self.updateComboBox()
|
||||
#-- Gpodder.on_itemUpdate_activate }
|
||||
|
||||
#-- Gpodder.on_itemPreferences_activate {
|
||||
def on_itemPreferences_activate(self, widget, *args):
|
||||
print "on_itemPreferences_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the preferences dialog")
|
||||
#-- Gpodder.on_itemPreferences_activate }
|
||||
|
||||
#-- Gpodder.on_itemAddChannel_activate {
|
||||
def on_itemAddChannel_activate(self, widget, *args):
|
||||
print "on_itemAddChannel_activate called with self.%s" % widget.get_name()
|
||||
result = Gpodderchannel().requestURL()
|
||||
if result != None and result != "" and (result[:4] == "http" or result[:3] == "ftp"):
|
||||
print "will ADD: " + result
|
||||
self.statusLabel.set_text( "Fetching channel index...")
|
||||
channel_new = podcastChannel( result)
|
||||
channel_new.shortname = "__unknown__"
|
||||
self.channels.append( channel_new)
|
||||
|
||||
# fetch metadata for that channel
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
|
||||
# fetch feed for that channel
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
|
||||
self.updateComboBox()
|
||||
self.statusLabel.set_text( "")
|
||||
#-- Gpodder.on_itemAddChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemEditChannel_activate {
|
||||
def on_itemEditChannel_activate(self, widget, *args):
|
||||
print "on_itemEditChannel_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the edit channel dialog")
|
||||
#print self.channels[self.active_channel].title
|
||||
#-- Gpodder.on_itemEditChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemRemoveChannel_activate {
|
||||
def on_itemRemoveChannel_activate(self, widget, *args):
|
||||
print "on_itemRemoveChannel_activate called with self.%s" % widget.get_name()
|
||||
try:
|
||||
self.channels.remove( self.channels[self.active_channel])
|
||||
gPodderChannelWriter().write( self.channels)
|
||||
self.channels = gPodderChannelReader().read( False)
|
||||
self.updateComboBox()
|
||||
except:
|
||||
print "could not delete - nothing selected, probably"
|
||||
#-- Gpodder.on_itemRemoveChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemExportChannels_activate {
|
||||
def on_itemExportChannels_activate(self, widget, *args):
|
||||
print "on_itemExportChannels_activate called with self.%s" % widget.get_name()
|
||||
self.not_implemented( "the export feature")
|
||||
#-- Gpodder.on_itemExportChannels_activate }
|
||||
|
||||
#-- Gpodder.on_itemAbout_activate {
|
||||
def on_itemAbout_activate(self, widget, *args):
|
||||
print "on_itemAbout_activate called with self.%s" % widget.get_name()
|
||||
dlg = gtk.AboutDialog()
|
||||
dlg.set_name( app_name)
|
||||
dlg.set_version( app_version)
|
||||
dlg.set_authors( app_authors)
|
||||
dlg.set_copyright( app_copyright)
|
||||
dlg.set_website( app_website)
|
||||
#FIXME: add hanlding hiere dlg.set_logo( gtk.gdk.pixbuf_new_from_file_at_size( "gpodder.png", 164, 164))
|
||||
dlg.run()
|
||||
#-- Gpodder.on_itemAbout_activate }
|
||||
|
||||
#-- Gpodder.on_comboAvailable_changed {
|
||||
def on_comboAvailable_changed(self, widget, *args):
|
||||
print "on_comboAvailable_changed called with self.%s" % widget.get_name()
|
||||
self.active_channel = self.comboAvailable.get_active()
|
||||
self.updateTreeView()
|
||||
#-- Gpodder.on_comboAvailable_changed }
|
||||
|
||||
#-- Gpodder.on_treeAvailable_row_activated {
|
||||
def on_treeAvailable_row_activated(self, widget, *args):
|
||||
print "on_treeAvailable_row_activated called with self.%s" % widget.get_name()
|
||||
selection_tuple = self.treeAvailable.get_selection().get_selected()
|
||||
selection_iter = selection_tuple[1]
|
||||
url = self.items_model.get_value( selection_iter, 0)
|
||||
|
||||
self.active_item = self.channels[self.active_channel].getActiveByUrl( url)
|
||||
|
||||
status = Gpodderstatus()
|
||||
current_channel = self.channels[self.active_channel]
|
||||
current_podcast = current_channel.items[self.active_item]
|
||||
if status.setup( current_channel, current_podcast):
|
||||
status.download()
|
||||
else:
|
||||
message_dialog = gtk.MessageDialog( self.gPodder, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
|
||||
message_dialog.set_markup( "<big><b>Already downloaded</b></big>\n\nYou have already downloaded this episode.")
|
||||
message_dialog.run()
|
||||
message_dialog.destroy()
|
||||
#-- Gpodder.on_treeAvailable_row_activated }
|
||||
|
||||
#-- Gpodder.on_btnDownload_clicked {
|
||||
def on_btnDownload_clicked(self, widget, *args):
|
||||
print "on_btnDownload_clicked called with self.%s" % widget.get_name()
|
||||
self.on_treeAvailable_row_activated( widget, args)
|
||||
#-- Gpodder.on_btnDownload_clicked }
|
||||
|
||||
|
||||
class Gpodderstatus(SimpleGladeApp):
|
||||
event = None
|
||||
channel = None
|
||||
podcast = None
|
||||
thread = None
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderStatus",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderstatus.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderstatus.new }
|
||||
|
||||
#-- Gpodderstatus custom methods {
|
||||
# Write your own methods here
|
||||
def setup( self, channel, podcast):
|
||||
self.channel = channel
|
||||
self.podcast = podcast
|
||||
|
||||
self.labelFrom.set_markup( "<b>" + self.channel.title + "</b>")
|
||||
self.labelFilename.set_markup( "<b>" + self.podcast.title + "</b>")
|
||||
|
||||
filename = gPodderLib().getPodcastFilename( self.channel, self.podcast.url)
|
||||
|
||||
if os.path.exists( filename) == False:
|
||||
self.event = Event()
|
||||
self.thread = downloadThread( self.podcast.url, filename, self.event)
|
||||
return True
|
||||
else:
|
||||
self.cancel()
|
||||
return False
|
||||
|
||||
def download( self):
|
||||
self.thread.download()
|
||||
|
||||
while self.event.isSet() == False:
|
||||
self.event.wait( 0.1)
|
||||
|
||||
self.labelSpeed.set_text( self.thread.speed)
|
||||
self.progressBar.set_fraction( float(self.thread.percentage))
|
||||
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
|
||||
self.gPodderStatus.destroy()
|
||||
|
||||
def cancel( self):
|
||||
self.on_btnCancel_clicked( self.btnCancel, None)
|
||||
#-- Gpodderstatus custom methods }
|
||||
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy {
|
||||
def on_gPodderStatus_destroy(self, widget, *args):
|
||||
print "on_gPodderStatus_destroy called with self.%s" % widget.get_name()
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy }
|
||||
|
||||
#-- Gpodderstatus.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
|
||||
if self.thread != None:
|
||||
self.thread.cancel()
|
||||
|
||||
while self.event != None and self.event.isSet() == False:
|
||||
None
|
||||
|
||||
self.gPodderStatus.destroy()
|
||||
#-- Gpodderstatus.on_btnCancel_clicked }
|
||||
|
||||
|
||||
class Gpodderchannel(SimpleGladeApp):
|
||||
waiting = None
|
||||
url = ""
|
||||
result = False
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderChannel",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderchannel.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderchannel.new }
|
||||
|
||||
#-- Gpodderchannel custom methods {
|
||||
# Write your own methods here
|
||||
def requestURL( self, message = None):
|
||||
if message != None:
|
||||
self.labelIntroduction.set_text( message)
|
||||
|
||||
self.waiting = Event()
|
||||
while self.waiting.isSet() == False:
|
||||
self.waiting.wait( 0.01)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
|
||||
if self.result == True:
|
||||
return self.url
|
||||
else:
|
||||
return None
|
||||
#-- Gpodderchannel custom methods }
|
||||
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy {
|
||||
def on_gPodderChannel_destroy(self, widget, *args):
|
||||
print "on_gPodderChannel_destroy called with self.%s" % widget.get_name()
|
||||
self.result = False
|
||||
self.url = self.entryURL.get_text()
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy }
|
||||
|
||||
#-- Gpodderchannel.on_btnOK_clicked {
|
||||
def on_btnOK_clicked(self, widget, *args):
|
||||
print "on_btnOK_clicked called with self.%s" % widget.get_name()
|
||||
self.gPodderChannel.destroy()
|
||||
self.result = True
|
||||
|
||||
if self.waiting != None:
|
||||
self.waiting.set()
|
||||
#-- Gpodderchannel.on_btnOK_clicked }
|
||||
|
||||
#-- Gpodderchannel.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
self.gPodderChannel.destroy()
|
||||
self.result = False
|
||||
|
||||
if self.waiting != None:
|
||||
self.waiting.set()
|
||||
#-- Gpodderchannel.on_btnCancel_clicked }
|
||||
|
||||
|
||||
#-- main {
|
||||
|
||||
def main( __version__ = None):
|
||||
global app_version
|
||||
|
||||
gtk.gdk.threads_init()
|
||||
app_version = __version__
|
||||
g_podder = Gpodder()
|
||||
#g_podder_status = Gpodderstatus()
|
||||
#g_podder_channel = Gpodderchannel()
|
||||
|
||||
g_podder.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "please run the gpodder binary, not this file"
|
||||
sys.exit( -1)
|
||||
|
||||
#-- main }
|
173
src/gpodder/gpodder.py.orig
Normal file
173
src/gpodder/gpodder.py.orig
Normal file
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
# Python module src/gpodder/gpodder.py
|
||||
# Autogenerated from gpodder.glade
|
||||
# Generated on Mon Nov 21 19:17:24 2005
|
||||
|
||||
# Warning: Do not modify any context comment such as #--
|
||||
# They are required to keep user's code
|
||||
|
||||
import os
|
||||
|
||||
import gtk
|
||||
|
||||
from SimpleGladeApp import SimpleGladeApp
|
||||
from SimpleGladeApp import bindtextdomain
|
||||
|
||||
app_name = "gpodder"
|
||||
app_version = "0.0.1"
|
||||
|
||||
glade_dir = ""
|
||||
locale_dir = ""
|
||||
|
||||
bindtextdomain(app_name, locale_dir)
|
||||
|
||||
|
||||
class Gpodder(SimpleGladeApp):
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodder",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodder.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodder.new }
|
||||
|
||||
#-- Gpodder custom methods {
|
||||
# Write your own methods here
|
||||
#-- Gpodder custom methods }
|
||||
|
||||
#-- Gpodder.close_gpodder {
|
||||
def close_gpodder(self, widget, *args):
|
||||
print "close_gpodder called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.close_gpodder }
|
||||
|
||||
#-- Gpodder.on_itemUpdate_activate {
|
||||
def on_itemUpdate_activate(self, widget, *args):
|
||||
print "on_itemUpdate_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemUpdate_activate }
|
||||
|
||||
#-- Gpodder.on_itemPreferences_activate {
|
||||
def on_itemPreferences_activate(self, widget, *args):
|
||||
print "on_itemPreferences_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemPreferences_activate }
|
||||
|
||||
#-- Gpodder.on_itemAddChannel_activate {
|
||||
def on_itemAddChannel_activate(self, widget, *args):
|
||||
print "on_itemAddChannel_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemAddChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemEditChannel_activate {
|
||||
def on_itemEditChannel_activate(self, widget, *args):
|
||||
print "on_itemEditChannel_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemEditChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemRemoveChannel_activate {
|
||||
def on_itemRemoveChannel_activate(self, widget, *args):
|
||||
print "on_itemRemoveChannel_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemRemoveChannel_activate }
|
||||
|
||||
#-- Gpodder.on_itemExportChannels_activate {
|
||||
def on_itemExportChannels_activate(self, widget, *args):
|
||||
print "on_itemExportChannels_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemExportChannels_activate }
|
||||
|
||||
#-- Gpodder.on_itemAbout_activate {
|
||||
def on_itemAbout_activate(self, widget, *args):
|
||||
print "on_itemAbout_activate called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_itemAbout_activate }
|
||||
|
||||
#-- Gpodder.on_comboAvailable_changed {
|
||||
def on_comboAvailable_changed(self, widget, *args):
|
||||
print "on_comboAvailable_changed called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_comboAvailable_changed }
|
||||
|
||||
#-- Gpodder.on_treeAvailable_row_activated {
|
||||
def on_treeAvailable_row_activated(self, widget, *args):
|
||||
print "on_treeAvailable_row_activated called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_treeAvailable_row_activated }
|
||||
|
||||
#-- Gpodder.on_btnDownload_clicked {
|
||||
def on_btnDownload_clicked(self, widget, *args):
|
||||
print "on_btnDownload_clicked called with self.%s" % widget.get_name()
|
||||
#-- Gpodder.on_btnDownload_clicked }
|
||||
|
||||
|
||||
class Gpodderstatus(SimpleGladeApp):
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderStatus",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderstatus.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderstatus.new }
|
||||
|
||||
#-- Gpodderstatus custom methods {
|
||||
# Write your own methods here
|
||||
#-- Gpodderstatus custom methods }
|
||||
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy {
|
||||
def on_gPodderStatus_destroy(self, widget, *args):
|
||||
print "on_gPodderStatus_destroy called with self.%s" % widget.get_name()
|
||||
#-- Gpodderstatus.on_gPodderStatus_destroy }
|
||||
|
||||
#-- Gpodderstatus.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
#-- Gpodderstatus.on_btnCancel_clicked }
|
||||
|
||||
|
||||
class Gpodderchannel(SimpleGladeApp):
|
||||
|
||||
def __init__(self, path="gpodder.glade",
|
||||
root="gPodderChannel",
|
||||
domain=app_name, **kwargs):
|
||||
path = os.path.join(glade_dir, path)
|
||||
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
|
||||
|
||||
#-- Gpodderchannel.new {
|
||||
def new(self):
|
||||
print "A new %s has been created" % self.__class__.__name__
|
||||
#-- Gpodderchannel.new }
|
||||
|
||||
#-- Gpodderchannel custom methods {
|
||||
# Write your own methods here
|
||||
#-- Gpodderchannel custom methods }
|
||||
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy {
|
||||
def on_gPodderChannel_destroy(self, widget, *args):
|
||||
print "on_gPodderChannel_destroy called with self.%s" % widget.get_name()
|
||||
#-- Gpodderchannel.on_gPodderChannel_destroy }
|
||||
|
||||
#-- Gpodderchannel.on_btnOK_clicked {
|
||||
def on_btnOK_clicked(self, widget, *args):
|
||||
print "on_btnOK_clicked called with self.%s" % widget.get_name()
|
||||
#-- Gpodderchannel.on_btnOK_clicked }
|
||||
|
||||
#-- Gpodderchannel.on_btnCancel_clicked {
|
||||
def on_btnCancel_clicked(self, widget, *args):
|
||||
print "on_btnCancel_clicked called with self.%s" % widget.get_name()
|
||||
#-- Gpodderchannel.on_btnCancel_clicked }
|
||||
|
||||
|
||||
#-- main {
|
||||
|
||||
def main():
|
||||
g_podder = Gpodder()
|
||||
g_podder_status = Gpodderstatus()
|
||||
g_podder_channel = Gpodderchannel()
|
||||
|
||||
g_podder.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
#-- main }
|
157
src/gpodder/libgpodder.py
Normal file
157
src/gpodder/libgpodder.py
Normal file
|
@ -0,0 +1,157 @@
|
|||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
#
|
||||
# libgpodder.py -- gpodder configuration
|
||||
# thomas perl <thp@perli.net> 20051030
|
||||
#
|
||||
#
|
||||
|
||||
import gtk
|
||||
|
||||
from xml.sax.saxutils import DefaultHandler
|
||||
from xml.sax import make_parser
|
||||
from string import strip
|
||||
from os.path import expanduser
|
||||
from os.path import basename
|
||||
from os.path import exists
|
||||
from os.path import dirname
|
||||
from os import mkdir
|
||||
from threading import Event
|
||||
|
||||
from libpodcasts import configChannel
|
||||
from librssreader import rssReader
|
||||
from libwget import downloadThread
|
||||
|
||||
class gPodderLib( object):
|
||||
gpodderdir = ""
|
||||
downloaddir = ""
|
||||
cachedir = ""
|
||||
|
||||
def __init__( self):
|
||||
self.gpodderdir = expanduser( "~/.config/gpodder/")
|
||||
self.createIfNecessary( self.gpodderdir)
|
||||
self.downloaddir = self.gpodderdir + "downloads/"
|
||||
self.createIfNecessary( self.downloaddir)
|
||||
self.cachedir = self.gpodderdir + "cache/"
|
||||
self.createIfNecessary( self.cachedir)
|
||||
|
||||
def createIfNecessary( self, path):
|
||||
#TODO: recursive mkdir all parent directories
|
||||
|
||||
if path.endswith('/'):
|
||||
path = path[:-1]
|
||||
|
||||
if not exists(dirname(path)):
|
||||
mkdir(dirname(path))
|
||||
|
||||
if not exists( path):
|
||||
mkdir( path)
|
||||
|
||||
def getConfigFilename( self):
|
||||
return self.gpodderdir + "gpodder.conf.xml"
|
||||
|
||||
def getChannelsFilename( self):
|
||||
return self.gpodderdir + "channels.xml"
|
||||
|
||||
def getChannelSaveDir( self, filename):
|
||||
savedir = self.downloaddir + filename + "/"
|
||||
self.createIfNecessary( savedir)
|
||||
|
||||
return savedir
|
||||
|
||||
def getChannelCacheFile( self, filename):
|
||||
return self.cachedir + filename + ".xml"
|
||||
|
||||
def getPodcastFilename( self, channel, url):
|
||||
return self.getChannelSaveDir( configChannel( channel.title).filename) + basename( url)
|
||||
|
||||
def downloadRss( self, channel_url, channel_filename = "__unknown__", force_update = True):
|
||||
if channel_filename == "":
|
||||
channel_filename = "__unknown__"
|
||||
|
||||
cachefile = gPodderLib().getChannelCacheFile( channel_filename)
|
||||
|
||||
if (channel_filename == "__unknown__" or exists( cachefile) == False) or force_update:
|
||||
event = Event()
|
||||
downloadThread( channel_url, cachefile, event).download()
|
||||
|
||||
while event.isSet() == False:
|
||||
event.wait( 0.2)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
|
||||
return cachefile
|
||||
|
||||
|
||||
|
||||
class gPodderChannelWriter( object):
|
||||
def __init__( self):
|
||||
None
|
||||
|
||||
def write( self, channels):
|
||||
filename = gPodderLib().getChannelsFilename()
|
||||
fd = open( filename, "w")
|
||||
fd.write( "<!-- automatically generated, will be overwritten on next gpodder shutdown.-->\n")
|
||||
fd.write( "<channels>\n")
|
||||
for chan in channels:
|
||||
configch = configChannel( chan.title, chan.url, chan.shortname)
|
||||
fd.write( " <channel name=\"" + configch.filename + "\">\n")
|
||||
fd.write( " <url>" + configch.url + "</url>\n")
|
||||
fd.write( " </channel>\n")
|
||||
fd.write( "</channels>\n")
|
||||
fd.close()
|
||||
|
||||
class gPodderChannelReader( DefaultHandler):
|
||||
channels = []
|
||||
current_item = None
|
||||
current_element_data = ""
|
||||
|
||||
def __init__( self):
|
||||
None
|
||||
|
||||
def read( self, force_update = False):
|
||||
self.channels = []
|
||||
parser = make_parser()
|
||||
parser.setContentHandler( self)
|
||||
if exists( gPodderLib().getChannelsFilename()):
|
||||
parser.parse( gPodderLib().getChannelsFilename())
|
||||
else:
|
||||
return []
|
||||
reader = rssReader()
|
||||
input_channels = []
|
||||
|
||||
for channel in self.channels:
|
||||
cachefile = gPodderLib().downloadRss( channel.url, channel.filename, force_update)
|
||||
reader.parseXML( channel.url, cachefile)
|
||||
|
||||
if channel.filename != "" and channel.filename != "__unknown__":
|
||||
reader.channel.shortname = channel.filename
|
||||
|
||||
input_channels.append( reader.channel)
|
||||
|
||||
return input_channels
|
||||
|
||||
def startElement( self, name, attrs):
|
||||
self.current_element_data = ""
|
||||
|
||||
if name == "channel":
|
||||
self.current_item = configChannel()
|
||||
self.current_item.filename = attrs.get( "name", "")
|
||||
|
||||
def endElement( self, name):
|
||||
if self.current_item != None:
|
||||
if name == "url":
|
||||
self.current_item.url = self.current_element_data
|
||||
if name == "channel":
|
||||
self.channels.append( self.current_item)
|
||||
self.current_item = None
|
||||
|
||||
def characters( self, ch):
|
||||
self.current_element_data = self.current_element_data + ch
|
||||
|
||||
|
141
src/gpodder/libpodcasts.py
Normal file
141
src/gpodder/libpodcasts.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
#
|
||||
# libpodcasts.py -- data classes for gpodder
|
||||
# thomas perl <thp@perli.net> 20051029
|
||||
#
|
||||
#
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
|
||||
# podcastChannel: holds data for a complete channel
|
||||
class podcastChannel(object):
|
||||
url = ""
|
||||
title = ""
|
||||
link = ""
|
||||
description = ""
|
||||
items = []
|
||||
image = None
|
||||
shortname = None
|
||||
|
||||
def __init__( self, url = "", title = "", link = "", description = ""):
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.link = link
|
||||
self.description = description
|
||||
self.items = []
|
||||
|
||||
def addItem( self, item):
|
||||
self.items.append( item)
|
||||
|
||||
def printChannel( self):
|
||||
print "- Channel: \"" + self.title + "\""
|
||||
for item in self.items:
|
||||
print "-- Item: \"" + item.title + "\""
|
||||
|
||||
def getItemsModel( self):
|
||||
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
|
||||
|
||||
for item in self.items:
|
||||
# Skip items with no download url
|
||||
if item.url != "":
|
||||
new_iter = new_model.append()
|
||||
new_model.set( new_iter, 0, item.url)
|
||||
new_model.set( new_iter, 1, item.title)
|
||||
new_model.set( new_iter, 2, item.getSize())
|
||||
new_model.set( new_iter, 3, True)
|
||||
|
||||
return new_model
|
||||
|
||||
def getActiveByUrl( self, url):
|
||||
i = 0
|
||||
|
||||
for item in self.items:
|
||||
if item.url == url:
|
||||
return i
|
||||
i = i + 1
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
# podcastItem: holds data for one object in a channel
|
||||
class podcastItem(object):
|
||||
url = ""
|
||||
title = ""
|
||||
length = ""
|
||||
mimetype = ""
|
||||
guid = ""
|
||||
description = ""
|
||||
link = ""
|
||||
|
||||
def __init__( self, url = "", title = "", length = "0", mimetype = "", guid = "", description = "", link = ""):
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.length = length
|
||||
self.mimetype = mimetype
|
||||
self.guid = guid
|
||||
self.description = description
|
||||
self.link = ""
|
||||
|
||||
def getSize( self):
|
||||
kilobyte = 1024
|
||||
megabyte = kilobyte * 1024
|
||||
gigabyte = megabyte * 1024
|
||||
|
||||
size = int( self.length)
|
||||
if size > gigabyte:
|
||||
return str( size / gigabyte) + " GB"
|
||||
if size > megabyte:
|
||||
return str( size / megabyte) + " MB"
|
||||
if size > kilobyte:
|
||||
return str( size / kilobyte) + " KB"
|
||||
|
||||
return str( size) + " Bytes"
|
||||
|
||||
|
||||
class configChannel( object):
|
||||
title =""
|
||||
url =""
|
||||
filename = None
|
||||
|
||||
def __init__( self, title = "", url = "", filename = None):
|
||||
self.title = title
|
||||
self.url = url
|
||||
|
||||
if filename == None:
|
||||
self.filename = self.createFilename()
|
||||
else:
|
||||
self.filename = filename
|
||||
|
||||
def createFilename( self):
|
||||
result = ""
|
||||
|
||||
for char in self.title.lower():
|
||||
if (char >= 'a' and char <= 'z') or (char >= 'A' and char <= 'Z') or (char >= '1' and char <= '9'):
|
||||
result = result + char
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def channelsToModel( channels):
|
||||
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_OBJECT)
|
||||
|
||||
for channel in channels:
|
||||
new_iter = new_model.append()
|
||||
new_model.set( new_iter, 0, channel.url)
|
||||
new_model.set( new_iter, 1, channel.title + " ("+channel.url+")")
|
||||
#if channel.image != None:
|
||||
# new_model.set( new_iter, 2, gtk.gdk.pixbuf_new_from_file_at_size( channel.image, 60, 60))
|
||||
#else:
|
||||
# new_model.set( new_iter, 2, None)
|
||||
|
||||
return new_model
|
||||
|
||||
|
77
src/gpodder/librssreader.py
Normal file
77
src/gpodder/librssreader.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
#
|
||||
# librssreader.py -- xml reader functionality
|
||||
# thomas perl <thp@perli.net> 20051029
|
||||
#
|
||||
#
|
||||
|
||||
from xml.sax.saxutils import DefaultHandler
|
||||
from xml.sax import make_parser
|
||||
from string import strip
|
||||
|
||||
from libpodcasts import podcastChannel
|
||||
from libpodcasts import podcastItem
|
||||
|
||||
class rssReader( DefaultHandler):
|
||||
channel_url = ""
|
||||
channel = None
|
||||
current_item = None
|
||||
current_element_data = ""
|
||||
|
||||
def __init__( self):
|
||||
None
|
||||
|
||||
def parseXML( self, url, filename):
|
||||
self.channel_url = url
|
||||
parser = make_parser()
|
||||
parser.setContentHandler( self)
|
||||
parser.parse( filename)
|
||||
|
||||
def startElement( self, name, attrs):
|
||||
self.current_element_data = ""
|
||||
|
||||
if name == "channel":
|
||||
self.channel = podcastChannel( self.channel_url)
|
||||
if name == "item":
|
||||
self.current_item = podcastItem()
|
||||
|
||||
if name == "enclosure" and self.current_item != None:
|
||||
self.current_item.url = attrs.get( "url", "")
|
||||
self.current_item.length = attrs.get( "length", "")
|
||||
self.current_item.mimetype = attrs.get( "type", "")
|
||||
|
||||
if name == "itunes:image":
|
||||
self.channel.image = attrs.get( "href", "")
|
||||
|
||||
def endElement( self, name):
|
||||
if self.current_item == None:
|
||||
if name == "title":
|
||||
self.channel.title = self.current_element_data
|
||||
if name == "link":
|
||||
self.channel.link = self.current_element_data
|
||||
if name == "description":
|
||||
self.channel.description = self.current_element_data
|
||||
|
||||
if self.current_item != None:
|
||||
if name == "title":
|
||||
self.current_item.title = self.current_element_data
|
||||
if name == "link":
|
||||
self.current_item.link = self.current_element_data
|
||||
if name == "description":
|
||||
self.current_item.description = self.current_element_data
|
||||
if name == "guid":
|
||||
self.current_item.guid = self.current_element_data
|
||||
if name == "item":
|
||||
self.channel.addItem( self.current_item)
|
||||
self.current_item = None
|
||||
|
||||
def characters( self, ch):
|
||||
self.current_element_data = self.current_element_data + ch
|
||||
|
||||
|
165
src/gpodder/libwget.py
Normal file
165
src/gpodder/libwget.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
|
||||
#
|
||||
# gPodder
|
||||
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
||||
# Released under the GNU General Public License (GPL)
|
||||
#
|
||||
|
||||
#
|
||||
# libwget.py -- wget download functionality
|
||||
# thomas perl <thp@perli.net> 20051029
|
||||
#
|
||||
#
|
||||
|
||||
from os.path import basename
|
||||
from os.path import dirname
|
||||
|
||||
from os import system
|
||||
from threading import Thread
|
||||
from shutil import move
|
||||
|
||||
import popen2
|
||||
import re
|
||||
|
||||
class downloadThread( object):
|
||||
url = ""
|
||||
filename = ""
|
||||
tempname = ""
|
||||
|
||||
ready_event = None
|
||||
pid = -1
|
||||
percentage = "0"
|
||||
speed = "unknown"
|
||||
|
||||
thread = None
|
||||
result = -1
|
||||
|
||||
def __init__( self, url, filename, ready_event = None):
|
||||
self.url = url.replace( "%20", " ")
|
||||
|
||||
self.filename = filename
|
||||
self.tempname = dirname( self.filename) + "/.tmp-" + basename( self.filename)
|
||||
|
||||
self.ready_event = ready_event
|
||||
self.pid= -1
|
||||
self.percentage = "0"
|
||||
self.speed = "unknown"
|
||||
|
||||
self.thread = None
|
||||
self.result = -1
|
||||
|
||||
def thread_function( self):
|
||||
command = "/usr/bin/wget \"" + self.url + "\" -O \"" + self.tempname + "\""
|
||||
print command
|
||||
process = popen2.Popen3( command, True)
|
||||
|
||||
self.pid = process.pid
|
||||
stderr = process.childerr
|
||||
|
||||
while process.poll() == -1:
|
||||
msg = stderr.readline( 80)
|
||||
print msg
|
||||
msg = msg.strip()
|
||||
|
||||
if msg.find("%") != -1:
|
||||
self.percentage = (int(msg[(msg.find("%") - 2)] + msg[(msg.find("%") - 1)])+0.001)/100.0;
|
||||
|
||||
iter = re.compile('...\... .B\/s').finditer( msg)
|
||||
for speed_string in iter:
|
||||
self.speed = speed_string.group(0).strip()
|
||||
|
||||
if process.wait() == 0:
|
||||
move( self.tempname, self.filename)
|
||||
|
||||
self.result = process.poll()
|
||||
self.pid = -1
|
||||
|
||||
if self.ready_event != None:
|
||||
self.ready_event.set()
|
||||
|
||||
def cancel( self):
|
||||
if self.pid != -1:
|
||||
system( "kill -9 " + str( self.pid))
|
||||
|
||||
def download( self):
|
||||
self.thread = Thread( target=self.thread_function)
|
||||
self.thread.start()
|
||||
|
||||
|
||||
|
||||
def getDownloadFilename( url):
|
||||
global downloadpath
|
||||
filename = os.path.basename( url)
|
||||
|
||||
# strip question mark (and everything behind it)
|
||||
indexOfQuestionMark = filename.rfind( "?")
|
||||
if( indexOfQuestionMark != -1):
|
||||
filename = filename[0:indexOfQuestionMark]
|
||||
# end if
|
||||
|
||||
downloadfilename = downloadpath+filename
|
||||
|
||||
return downloadfilename.replace( "%20", " ")
|
||||
# end getDownloadFilename()
|
||||
|
||||
# downloadFile: simply download a file to current directory
|
||||
def downloadFile( url):
|
||||
filename = getDownloadFilename( url)
|
||||
# download it, and never overwrite anything =)
|
||||
result = downloadProcedure( url, filename, False)
|
||||
|
||||
return result
|
||||
# end downloadFile()
|
||||
|
||||
# getWebData: get an rss feed and save it locally, return content
|
||||
def getWebData( url, force_update):
|
||||
filename = configpath + md5.new( url).hexdigest() + ".feed"
|
||||
downloadProcedure( url, filename, force_update)
|
||||
|
||||
return filename
|
||||
# end getWebData()
|
||||
|
||||
# downloadProcedure: gerneric implementation of downloading with gui
|
||||
def downloadProcedure( url, filename, force_update):
|
||||
global dlinfo_speed
|
||||
global dlinfo_percentage
|
||||
global dlinfo_result
|
||||
|
||||
url = url.replace( "%20", " ")
|
||||
|
||||
dlinfo_speed = "initializing download..."
|
||||
dlinfo_percentage = "0"
|
||||
dlinfo_result = -1
|
||||
|
||||
# check if file does not exist and download if necessary
|
||||
if( os.path.exists( filename) == False or force_update == True):
|
||||
wait_dialog_display( url, filename, dlinfo_speed)
|
||||
|
||||
# set up the thread and start it in the background
|
||||
finished = threading.Event()
|
||||
argumente = ( url, filename, finished)
|
||||
mythread = threading.Thread( target=downloadThread, args=argumente )
|
||||
mythread.start()
|
||||
|
||||
# wait for thread to be finished
|
||||
while finished.isSet() == False:
|
||||
finished.wait( 0.1)
|
||||
wait_dialog_update( url, filename, dlinfo_speed, dlinfo_percentage)
|
||||
updui()
|
||||
# end while
|
||||
wait_dialog_destroy()
|
||||
# end if
|
||||
|
||||
# the error handling comes here..
|
||||
if dlinfo_result > 0:
|
||||
if dlinfo_result == 9:
|
||||
showMessage( "Download has been cancelled.")
|
||||
else:
|
||||
showMessage( "wget exited with status: " + str( dlinfo_result))
|
||||
print "*** THERE HAS BEEN AN ERROR WHILE DOWNLOADING **"
|
||||
# end if
|
||||
# end if
|
||||
|
||||
# make the download result available to caller
|
||||
return dlinfo_result
|
||||
# end downloadProcedure()
|
Loading…
Reference in a new issue