From ba7e499862f20e6e77738b1e83d5ec16346b52fb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Oct 2009 17:57:56 -0600 Subject: [PATCH] Refactor the news download scheduler. Now has support for customizing the tags assigned to downloaded news. Since the entire subsystem was rewritten, you may experience bugs. --- .bzrignore | 2 +- .../recipes/24sata.recipe | 0 .../recipes/24sata_rs.recipe | 0 .../recipes/7dias.recipe | 0 .../recipes/accountancyage.recipe | 0 .../recipes/adventuregamers.recipe | 0 .../recipes/al_jazeera.recipe | 0 .../recipes/ambito.recipe | 0 .../recipes/amspec.recipe | 0 .../recipes/ap.recipe | 0 .../recipes/ars_technica.recipe | 0 .../recipes/atlantic.recipe | 0 .../recipes/axxon_news.recipe | 0 .../recipes/azstarnet.recipe | 0 .../recipes/b92.recipe | 0 .../recipes/barrons.recipe | 0 .../recipes/bbc.recipe | 0 .../recipes/bbcvietnamese.recipe | 0 .../recipes/beta.recipe | 0 .../recipes/beta_en.recipe | 0 .../recipes/blic.recipe | 0 .../recipes/borba.recipe | 0 .../recipes/buenosaireseconomico.recipe | 0 .../recipes/business_standard.recipe | 0 .../recipes/business_week.recipe | 0 .../recipes/businessworldin.recipe | 0 .../recipes/carta.recipe | 0 .../recipes/chicago_breaking_news.recipe | 0 .../recipes/chicago_tribune.recipe | 0 .../recipes/chr_mon.recipe | 0 .../recipes/cincinnati_enquirer.recipe | 0 .../recipes/clarin.recipe | 0 .../recipes/climate_progress.recipe | 0 .../recipes/cnn.recipe | 0 .../recipes/coding_horror.recipe | 0 .../recipes/common_dreams.recipe | 0 .../recipes/corriere_della_sera_en.recipe | 0 .../recipes/corriere_della_sera_it.recipe | 0 .../recipes/courrierinternational.recipe | 0 .../recipes/craigslist.recipe | 0 .../recipes/criticadigital.recipe | 0 .../recipes/cubadebate.recipe | 0 .../recipes/cyberpresse.recipe | 0 .../recipes/daily_mail.recipe | 0 .../recipes/daily_telegraph.recipe | 0 .../recipes/danas.recipe | 0 .../recipes/darknet.recipe | 0 .../recipes/de_standaard.recipe | 0 .../recipes/degentenaar.recipe | 0 .../recipes/demorgen_be.recipe | 0 .../recipes/der_standard.recipe | 0 .../recipes/diagonales.recipe | 0 .../recipes/diepresse.recipe | 0 .../recipes/discover_magazine.recipe | 0 .../recipes/dna.recipe | 0 .../recipes/dnevni_avaz.recipe | 0 .../recipes/dnevnik_cro.recipe | 0 .../recipes/e_novine.recipe | 0 .../recipes/ecogeek.recipe | 0 .../recipes/economist.recipe | 0 .../recipes/el_mercurio_chile.recipe | 0 .../recipes/el_pais.recipe | 0 .../recipes/el_universal.recipe | 0 .../recipes/elargentino.recipe | 0 .../recipes/elcronista.recipe | 0 .../recipes/elektrolese.recipe | 0 .../recipes/elmundo.recipe | 0 .../recipes/elperiodico_catalan.recipe | 0 .../recipes/elperiodico_spanish.recipe | 0 .../recipes/eltiempo_hn.recipe | 0 .../recipes/endgadget.recipe | 0 .../recipes/espn.recipe | 0 .../recipes/esquire.recipe | 0 .../recipes/estadao.recipe | 0 .../recipes/exiled.recipe | 0 .../recipes/expansion_spanish.recipe | 0 .../recipes/fastcompany.recipe | 0 .../recipes/faznet.recipe | 0 .../recipes/financial_times.recipe | 0 .../recipes/forbes.recipe | 0 .../recipes/freakonomics.recipe | 0 .../recipes/ftd.recipe | 0 .../recipes/fudzilla.recipe | 0 .../recipes/glas_srpske.recipe | 0 .../recipes/glasgow_herald.recipe | 0 .../recipes/glasjavnosti.recipe | 0 .../recipes/globe_and_mail.recipe | 0 .../recipes/granma.recipe | 0 .../recipes/greader.recipe | 0 .../recipes/guardian.recipe | 0 .../recipes/gva_be.recipe | 0 .../recipes/h1.recipe | 0 .../recipes/h2.recipe | 0 .../recipes/h3.recipe | 0 .../recipes/harpers.recipe | 0 .../recipes/harpers_full.recipe | 0 .../recipes/heise.recipe | 0 .../recipes/hindu.recipe | 0 .../recipes/hln.recipe | 0 .../recipes/hln_be.recipe | 0 .../recipes/hna.recipe | 0 .../recipes/honoluluadvertiser.recipe | 0 .../recipes/honvedelem.recipe | 0 .../recipes/hrt.recipe | 0 .../recipes/huntechnet.recipe | 0 .../recipes/iht.recipe | 0 .../recipes/index_hu.recipe | 0 .../recipes/indy_star.recipe | 0 .../recipes/infobae.recipe | 0 .../recipes/infoworld.recipe | 0 .../recipes/inquirer_net.recipe | 0 .../recipes/instapaper.recipe | 0 .../recipes/intelligencer.recipe | 0 .../recipes/irish_times.recipe | 0 .../recipes/japan_times.recipe | 0 .../recipes/javalobby.recipe | 0 .../recipes/jb_online.recipe | 0 .../recipes/joelonsoftware.recipe | 0 .../recipes/jpost.recipe | 0 .../recipes/jutarnji.recipe | 0 .../recipes/juventudrebelde.recipe | 0 .../recipes/juventudrebelde_english.recipe | 0 .../recipes/kellog_faculty.recipe | 0 .../recipes/kellog_insight.recipe | 0 .../recipes/krstarica.recipe | 0 .../recipes/krstarica_en.recipe | 0 .../recipes/la_cuarta.recipe | 0 .../recipes/la_republica.recipe | 0 .../recipes/la_segunda.recipe | 0 .../recipes/la_tercera.recipe | 0 .../recipes/lamujerdemivida.recipe | 0 .../recipes/lanacion.recipe | 0 .../recipes/lanacion_chile.recipe | 0 .../recipes/laprensa.recipe | 0 .../recipes/laprensa_hn.recipe | 0 .../recipes/laprensa_ni.recipe | 0 .../recipes/latimes.recipe | 0 .../recipes/latribuna.recipe | 0 .../recipes/lavanguardia.recipe | 0 .../recipes/le_monde.recipe | 0 .../recipes/le_temps.recipe | 0 .../recipes/lemonde_dip.recipe | 0 .../recipes/liberation.recipe | 0 .../recipes/linux_magazine.recipe | 0 .../recipes/linuxdevices.recipe | 0 .../recipes/livemint.recipe | 0 .../recipes/lrb.recipe | 0 .../recipes/marca.recipe | 0 .../recipes/mediapart.recipe | 0 .../recipes/miami_herald.recipe | 0 .../recipes/miradasalsur.recipe | 0 .../recipes/mondedurable.recipe | 0 .../recipes/moneynews.recipe | 0 .../recipes/monitor.recipe | 0 .../recipes/moscow_times.recipe | 0 .../recipes/msdnmag_en.recipe | 0 .../recipes/nacional_cro.recipe | 0 .../recipes/nasa.recipe | 0 .../recipes/new_scientist.recipe | 0 .../recipes/new_york_review_of_books.recipe | 0 .../new_york_review_of_books_no_sub.recipe | 0 .../recipes/new_yorker.recipe | 0 .../recipes/news_times.recipe | 0 .../recipes/newsweek.recipe | 0 .../recipes/newsweek_argentina.recipe | 0 .../recipes/nin.recipe | 0 .../recipes/noaa.recipe | 0 .../recipes/novosti.recipe | 0 .../recipes/nspm.recipe | 0 .../recipes/nspm_int.recipe | 0 .../recipes/nytimes.recipe | 2 +- .../recipes/nytimes_sub.recipe | 0 .../recipes/nzz_ger.recipe | 0 .../recipes/o_globo.recipe | 0 .../recipes/ourdailybread.recipe | 0 .../recipes/outlook_india.recipe | 0 .../recipes/pagina12.recipe | 0 .../recipes/pcworld_hu.recipe | 0 .../recipes/pescanik.recipe | 0 .../recipes/phd_comics.recipe | 0 .../recipes/philly.recipe | 0 .../recipes/physics_today.recipe | 0 .../recipes/physics_world.recipe | 0 .../recipes/pobjeda.recipe | 0 .../recipes/politico.recipe | 0 .../recipes/politika.recipe | 0 .../recipes/portfolio.recipe | 0 .../recipes/pressonline.recipe | 0 .../recipes/publico.recipe | 0 .../recipes/republika.recipe | 0 .../recipes/reuters.recipe | 0 .../recipes/rga.recipe | 0 .../recipes/rts.recipe | 0 .../recipes/salon.recipe | 0 .../recipes/san_fran_chronicle.recipe | 0 .../recipes/sanjosemercurynews.recipe | 0 .../recipes/science_aas.recipe | 0 .../recipes/science_news.recipe | 0 .../recipes/sciencedaily.recipe | 0 .../recipes/scientific_american.recipe | 0 .../recipes/scott_hanselman.recipe | 0 .../recipes/seattle_times.recipe | 0 .../recipes/security_watch.recipe | 0 .../recipes/serverside.recipe | 0 .../recipes/shacknews.recipe | 0 .../recipes/slashdot.recipe | 0 .../recipes/slate.recipe | 0 .../recipes/smashing.recipe | 0 .../recipes/smh.recipe | 0 .../recipes/soldiers.recipe | 0 .../recipes/spiegel_int.recipe | 0 .../recipes/spiegelde.recipe | 0 .../recipes/st_petersburg_times.recipe | 0 .../recipes/stackoverflow.recipe | 0 .../recipes/starbulletin.recipe | 0 .../recipes/straitstimes.recipe | 0 .../recipes/sueddeutsche.recipe | 0 .../recipes/tanjug.recipe | 0 .../recipes/telegraph_uk.recipe | 0 .../recipes/telepolis.recipe | 0 .../recipes/telepolis_artikel.recipe | 0 .../recipes/teleread.recipe | 0 .../recipes/the_age.recipe | 0 .../recipes/the_budget_fashionista.recipe | 0 .../recipes/the_nation.recipe | 0 .../recipes/the_new_republic.recipe | 0 .../recipes/the_oz.recipe | 0 .../recipes/the_register.recipe | 0 .../recipes/the_scotsman.recipe | 0 .../recipes/thedgesingapore.recipe | 0 .../recipes/theeconomictimes_india.recipe | 0 .../recipes/themarketticker.recipe | 0 .../recipes/theoldfoodie.recipe | 0 .../recipes/theonion.recipe | 0 .../recipes/thestar.recipe | 0 .../recipes/tijd.recipe | 0 .../recipes/time_magazine.recipe | 0 .../recipes/times_online.recipe | 0 .../recipes/tnxm.recipe | 0 .../recipes/tomshardware.recipe | 0 .../recipes/tomshardware_de.recipe | 0 .../recipes/toronto_sun.recipe | 0 .../recipes/tweakers.recipe | 0 .../recipes/twitchfilms.recipe | 0 .../recipes/uncrate.recipe | 0 .../recipes/upi.recipe | 0 .../recipes/usatoday.recipe | 0 .../recipes/usnews.recipe | 0 .../recipes/utne.recipe | 0 .../recipes/vecernji_list.recipe | 0 .../recipes/veintitres.recipe | 0 .../recipes/vijesti.recipe | 0 .../recipes/vnexpress.recipe | 0 .../recipes/volksrant.recipe | 0 .../recipes/vreme.recipe | 0 .../recipes/wash_post.recipe | 0 .../recipes/wikinews_en.recipe | 0 .../recipes/winsupersite.recipe | 0 .../recipes/wired.recipe | 0 .../recipes/woz_die.recipe | 0 .../recipes/wsj.recipe | 0 .../recipes/xkcd.recipe | 0 .../recipes/zaobao.recipe | 0 .../recipes/zdnet.recipe | 0 .../recipes/zeitde.recipe | 0 setup/check.py | 44 +- setup/resources.py | 31 +- src/calibre/gui2/__init__.py | 3 +- src/calibre/gui2/dialogs/scheduler.py | 708 ++++++------------ src/calibre/gui2/dialogs/scheduler.ui | 480 ++++++------ src/calibre/gui2/dialogs/user_profiles.py | 180 +++-- src/calibre/gui2/dialogs/user_profiles.ui | 23 +- src/calibre/gui2/library.py | 4 +- src/calibre/gui2/main.py | 25 +- src/calibre/gui2/tools.py | 17 +- src/calibre/library/database.py | 26 +- src/calibre/library/database2.py | 13 +- src/calibre/web/feeds/input.py | 7 +- src/calibre/web/feeds/recipes/__init__.py | 121 +-- src/calibre/web/feeds/recipes/collection.py | 331 ++++++++ src/calibre/web/feeds/recipes/model.py | 340 +++++++++ 281 files changed, 1393 insertions(+), 964 deletions(-) rename src/calibre/web/feeds/recipes/recipe_24sata.py => resources/recipes/24sata.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_24sata_rs.py => resources/recipes/24sata_rs.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_7dias.py => resources/recipes/7dias.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_accountancyage.py => resources/recipes/accountancyage.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_adventuregamers.py => resources/recipes/adventuregamers.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_al_jazeera.py => resources/recipes/al_jazeera.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ambito.py => resources/recipes/ambito.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_amspec.py => resources/recipes/amspec.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ap.py => resources/recipes/ap.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ars_technica.py => resources/recipes/ars_technica.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_atlantic.py => resources/recipes/atlantic.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_axxon_news.py => resources/recipes/axxon_news.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_azstarnet.py => resources/recipes/azstarnet.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_b92.py => resources/recipes/b92.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_barrons.py => resources/recipes/barrons.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_bbc.py => resources/recipes/bbc.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_bbcvietnamese.py => resources/recipes/bbcvietnamese.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_beta.py => resources/recipes/beta.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_beta_en.py => resources/recipes/beta_en.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_blic.py => resources/recipes/blic.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_borba.py => resources/recipes/borba.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_buenosaireseconomico.py => resources/recipes/buenosaireseconomico.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_business_standard.py => resources/recipes/business_standard.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_business_week.py => resources/recipes/business_week.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_businessworldin.py => resources/recipes/businessworldin.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_carta.py => resources/recipes/carta.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_chicago_breaking_news.py => resources/recipes/chicago_breaking_news.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_chicago_tribune.py => resources/recipes/chicago_tribune.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_chr_mon.py => resources/recipes/chr_mon.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_cincinnati_enquirer.py => resources/recipes/cincinnati_enquirer.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_clarin.py => resources/recipes/clarin.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_climate_progress.py => resources/recipes/climate_progress.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_cnn.py => resources/recipes/cnn.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_coding_horror.py => resources/recipes/coding_horror.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_common_dreams.py => resources/recipes/common_dreams.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_corriere_della_sera_en.py => resources/recipes/corriere_della_sera_en.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_corriere_della_sera_it.py => resources/recipes/corriere_della_sera_it.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_courrierinternational.py => resources/recipes/courrierinternational.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_craigslist.py => resources/recipes/craigslist.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_criticadigital.py => resources/recipes/criticadigital.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_cubadebate.py => resources/recipes/cubadebate.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_cyberpresse.py => resources/recipes/cyberpresse.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_daily_mail.py => resources/recipes/daily_mail.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_daily_telegraph.py => resources/recipes/daily_telegraph.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_danas.py => resources/recipes/danas.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_darknet.py => resources/recipes/darknet.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_de_standaard.py => resources/recipes/de_standaard.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_degentenaar.py => resources/recipes/degentenaar.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_demorgen_be.py => resources/recipes/demorgen_be.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_der_standard.py => resources/recipes/der_standard.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_diagonales.py => resources/recipes/diagonales.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_diepresse.py => resources/recipes/diepresse.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_discover_magazine.py => resources/recipes/discover_magazine.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_dna.py => resources/recipes/dna.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_dnevni_avaz.py => resources/recipes/dnevni_avaz.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_dnevnik_cro.py => resources/recipes/dnevnik_cro.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_e_novine.py => resources/recipes/e_novine.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ecogeek.py => resources/recipes/ecogeek.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_economist.py => resources/recipes/economist.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_el_mercurio_chile.py => resources/recipes/el_mercurio_chile.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_el_pais.py => resources/recipes/el_pais.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_el_universal.py => resources/recipes/el_universal.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elargentino.py => resources/recipes/elargentino.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elcronista.py => resources/recipes/elcronista.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elektrolese.py => resources/recipes/elektrolese.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elmundo.py => resources/recipes/elmundo.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elperiodico_catalan.py => resources/recipes/elperiodico_catalan.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_elperiodico_spanish.py => resources/recipes/elperiodico_spanish.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_eltiempo_hn.py => resources/recipes/eltiempo_hn.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_endgadget.py => resources/recipes/endgadget.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_espn.py => resources/recipes/espn.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_esquire.py => resources/recipes/esquire.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_estadao.py => resources/recipes/estadao.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_exiled.py => resources/recipes/exiled.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_expansion_spanish.py => resources/recipes/expansion_spanish.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_fastcompany.py => resources/recipes/fastcompany.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_faznet.py => resources/recipes/faznet.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_financial_times.py => resources/recipes/financial_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_forbes.py => resources/recipes/forbes.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_freakonomics.py => resources/recipes/freakonomics.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ftd.py => resources/recipes/ftd.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_fudzilla.py => resources/recipes/fudzilla.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_glas_srpske.py => resources/recipes/glas_srpske.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_glasgow_herald.py => resources/recipes/glasgow_herald.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_glasjavnosti.py => resources/recipes/glasjavnosti.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_globe_and_mail.py => resources/recipes/globe_and_mail.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_granma.py => resources/recipes/granma.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_greader.py => resources/recipes/greader.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_guardian.py => resources/recipes/guardian.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_gva_be.py => resources/recipes/gva_be.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_h1.py => resources/recipes/h1.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_h2.py => resources/recipes/h2.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_h3.py => resources/recipes/h3.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_harpers.py => resources/recipes/harpers.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_harpers_full.py => resources/recipes/harpers_full.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_heise.py => resources/recipes/heise.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_hindu.py => resources/recipes/hindu.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_hln.py => resources/recipes/hln.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_hln_be.py => resources/recipes/hln_be.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_hna.py => resources/recipes/hna.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_honoluluadvertiser.py => resources/recipes/honoluluadvertiser.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_honvedelem.py => resources/recipes/honvedelem.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_hrt.py => resources/recipes/hrt.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_huntechnet.py => resources/recipes/huntechnet.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_iht.py => resources/recipes/iht.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_index_hu.py => resources/recipes/index_hu.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_indy_star.py => resources/recipes/indy_star.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_infobae.py => resources/recipes/infobae.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_infoworld.py => resources/recipes/infoworld.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_inquirer_net.py => resources/recipes/inquirer_net.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_instapaper.py => resources/recipes/instapaper.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_intelligencer.py => resources/recipes/intelligencer.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_irish_times.py => resources/recipes/irish_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_japan_times.py => resources/recipes/japan_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_javalobby.py => resources/recipes/javalobby.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_jb_online.py => resources/recipes/jb_online.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_joelonsoftware.py => resources/recipes/joelonsoftware.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_jpost.py => resources/recipes/jpost.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_jutarnji.py => resources/recipes/jutarnji.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_juventudrebelde.py => resources/recipes/juventudrebelde.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_juventudrebelde_english.py => resources/recipes/juventudrebelde_english.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_kellog_faculty.py => resources/recipes/kellog_faculty.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_kellog_insight.py => resources/recipes/kellog_insight.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_krstarica.py => resources/recipes/krstarica.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_krstarica_en.py => resources/recipes/krstarica_en.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_la_cuarta.py => resources/recipes/la_cuarta.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_la_republica.py => resources/recipes/la_republica.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_la_segunda.py => resources/recipes/la_segunda.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_la_tercera.py => resources/recipes/la_tercera.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lamujerdemivida.py => resources/recipes/lamujerdemivida.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lanacion.py => resources/recipes/lanacion.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lanacion_chile.py => resources/recipes/lanacion_chile.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_laprensa.py => resources/recipes/laprensa.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_laprensa_hn.py => resources/recipes/laprensa_hn.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_laprensa_ni.py => resources/recipes/laprensa_ni.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_latimes.py => resources/recipes/latimes.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_latribuna.py => resources/recipes/latribuna.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lavanguardia.py => resources/recipes/lavanguardia.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_le_monde.py => resources/recipes/le_monde.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_le_temps.py => resources/recipes/le_temps.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lemonde_dip.py => resources/recipes/lemonde_dip.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_liberation.py => resources/recipes/liberation.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_linux_magazine.py => resources/recipes/linux_magazine.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_linuxdevices.py => resources/recipes/linuxdevices.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_livemint.py => resources/recipes/livemint.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_lrb.py => resources/recipes/lrb.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_marca.py => resources/recipes/marca.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_mediapart.py => resources/recipes/mediapart.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_miami_herald.py => resources/recipes/miami_herald.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_miradasalsur.py => resources/recipes/miradasalsur.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_mondedurable.py => resources/recipes/mondedurable.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_moneynews.py => resources/recipes/moneynews.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_monitor.py => resources/recipes/monitor.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_moscow_times.py => resources/recipes/moscow_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_msdnmag_en.py => resources/recipes/msdnmag_en.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nacional_cro.py => resources/recipes/nacional_cro.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nasa.py => resources/recipes/nasa.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_new_scientist.py => resources/recipes/new_scientist.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_new_york_review_of_books.py => resources/recipes/new_york_review_of_books.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_new_york_review_of_books_no_sub.py => resources/recipes/new_york_review_of_books_no_sub.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_new_yorker.py => resources/recipes/new_yorker.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_news_times.py => resources/recipes/news_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_newsweek.py => resources/recipes/newsweek.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_newsweek_argentina.py => resources/recipes/newsweek_argentina.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nin.py => resources/recipes/nin.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_noaa.py => resources/recipes/noaa.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_novosti.py => resources/recipes/novosti.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nspm.py => resources/recipes/nspm.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nspm_int.py => resources/recipes/nspm_int.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nytimes.py => resources/recipes/nytimes.recipe (99%) rename src/calibre/web/feeds/recipes/recipe_nytimes_sub.py => resources/recipes/nytimes_sub.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_nzz_ger.py => resources/recipes/nzz_ger.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_o_globo.py => resources/recipes/o_globo.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_ourdailybread.py => resources/recipes/ourdailybread.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_outlook_india.py => resources/recipes/outlook_india.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_pagina12.py => resources/recipes/pagina12.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_pcworld_hu.py => resources/recipes/pcworld_hu.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_pescanik.py => resources/recipes/pescanik.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_phd_comics.py => resources/recipes/phd_comics.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_philly.py => resources/recipes/philly.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_physics_today.py => resources/recipes/physics_today.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_physics_world.py => resources/recipes/physics_world.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_pobjeda.py => resources/recipes/pobjeda.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_politico.py => resources/recipes/politico.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_politika.py => resources/recipes/politika.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_portfolio.py => resources/recipes/portfolio.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_pressonline.py => resources/recipes/pressonline.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_publico.py => resources/recipes/publico.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_republika.py => resources/recipes/republika.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_reuters.py => resources/recipes/reuters.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_rga.py => resources/recipes/rga.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_rts.py => resources/recipes/rts.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_salon.py => resources/recipes/salon.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_san_fran_chronicle.py => resources/recipes/san_fran_chronicle.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_sanjosemercurynews.py => resources/recipes/sanjosemercurynews.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_science_aas.py => resources/recipes/science_aas.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_science_news.py => resources/recipes/science_news.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_sciencedaily.py => resources/recipes/sciencedaily.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_scientific_american.py => resources/recipes/scientific_american.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_scott_hanselman.py => resources/recipes/scott_hanselman.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_seattle_times.py => resources/recipes/seattle_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_security_watch.py => resources/recipes/security_watch.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_serverside.py => resources/recipes/serverside.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_shacknews.py => resources/recipes/shacknews.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_slashdot.py => resources/recipes/slashdot.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_slate.py => resources/recipes/slate.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_smashing.py => resources/recipes/smashing.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_smh.py => resources/recipes/smh.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_soldiers.py => resources/recipes/soldiers.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_spiegel_int.py => resources/recipes/spiegel_int.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_spiegelde.py => resources/recipes/spiegelde.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_st_petersburg_times.py => resources/recipes/st_petersburg_times.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_stackoverflow.py => resources/recipes/stackoverflow.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_starbulletin.py => resources/recipes/starbulletin.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_straitstimes.py => resources/recipes/straitstimes.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_sueddeutsche.py => resources/recipes/sueddeutsche.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tanjug.py => resources/recipes/tanjug.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_telegraph_uk.py => resources/recipes/telegraph_uk.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_telepolis.py => resources/recipes/telepolis.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_telepolis_artikel.py => resources/recipes/telepolis_artikel.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_teleread.py => resources/recipes/teleread.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_age.py => resources/recipes/the_age.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_budget_fashionista.py => resources/recipes/the_budget_fashionista.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_nation.py => resources/recipes/the_nation.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_new_republic.py => resources/recipes/the_new_republic.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_oz.py => resources/recipes/the_oz.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_register.py => resources/recipes/the_register.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_the_scotsman.py => resources/recipes/the_scotsman.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_thedgesingapore.py => resources/recipes/thedgesingapore.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_theeconomictimes_india.py => resources/recipes/theeconomictimes_india.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_themarketticker.py => resources/recipes/themarketticker.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_theoldfoodie.py => resources/recipes/theoldfoodie.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_theonion.py => resources/recipes/theonion.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_thestar.py => resources/recipes/thestar.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tijd.py => resources/recipes/tijd.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_time_magazine.py => resources/recipes/time_magazine.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_times_online.py => resources/recipes/times_online.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tnxm.py => resources/recipes/tnxm.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tomshardware.py => resources/recipes/tomshardware.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tomshardware_de.py => resources/recipes/tomshardware_de.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_toronto_sun.py => resources/recipes/toronto_sun.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_tweakers.py => resources/recipes/tweakers.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_twitchfilms.py => resources/recipes/twitchfilms.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_uncrate.py => resources/recipes/uncrate.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_upi.py => resources/recipes/upi.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_usatoday.py => resources/recipes/usatoday.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_usnews.py => resources/recipes/usnews.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_utne.py => resources/recipes/utne.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_vecernji_list.py => resources/recipes/vecernji_list.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_veintitres.py => resources/recipes/veintitres.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_vijesti.py => resources/recipes/vijesti.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_vnexpress.py => resources/recipes/vnexpress.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_volksrant.py => resources/recipes/volksrant.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_vreme.py => resources/recipes/vreme.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_wash_post.py => resources/recipes/wash_post.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_wikinews_en.py => resources/recipes/wikinews_en.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_winsupersite.py => resources/recipes/winsupersite.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_wired.py => resources/recipes/wired.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_woz_die.py => resources/recipes/woz_die.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_wsj.py => resources/recipes/wsj.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_xkcd.py => resources/recipes/xkcd.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_zaobao.py => resources/recipes/zaobao.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_zdnet.py => resources/recipes/zdnet.recipe (100%) rename src/calibre/web/feeds/recipes/recipe_zeitde.py => resources/recipes/zeitde.recipe (100%) create mode 100644 src/calibre/web/feeds/recipes/collection.py create mode 100644 src/calibre/web/feeds/recipes/model.py diff --git a/.bzrignore b/.bzrignore index 2a6d1df889..73e5fd80b5 100644 --- a/.bzrignore +++ b/.bzrignore @@ -9,9 +9,9 @@ dist docs resources/localization resources/images.qrc -resources/recipes.pickle resources/scripts.pickle resources/ebook-convert-complete.pickle +resources/builtin_recipes.xml setup/installer/windows/calibre/build.log src/calibre/translations/.errors src/cssutils/.svn/ diff --git a/src/calibre/web/feeds/recipes/recipe_24sata.py b/resources/recipes/24sata.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_24sata.py rename to resources/recipes/24sata.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_24sata_rs.py b/resources/recipes/24sata_rs.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_24sata_rs.py rename to resources/recipes/24sata_rs.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_7dias.py b/resources/recipes/7dias.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_7dias.py rename to resources/recipes/7dias.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_accountancyage.py b/resources/recipes/accountancyage.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_accountancyage.py rename to resources/recipes/accountancyage.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_adventuregamers.py b/resources/recipes/adventuregamers.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_adventuregamers.py rename to resources/recipes/adventuregamers.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_al_jazeera.py b/resources/recipes/al_jazeera.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_al_jazeera.py rename to resources/recipes/al_jazeera.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ambito.py b/resources/recipes/ambito.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ambito.py rename to resources/recipes/ambito.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_amspec.py b/resources/recipes/amspec.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_amspec.py rename to resources/recipes/amspec.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ap.py b/resources/recipes/ap.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ap.py rename to resources/recipes/ap.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ars_technica.py b/resources/recipes/ars_technica.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ars_technica.py rename to resources/recipes/ars_technica.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_atlantic.py b/resources/recipes/atlantic.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_atlantic.py rename to resources/recipes/atlantic.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_axxon_news.py b/resources/recipes/axxon_news.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_axxon_news.py rename to resources/recipes/axxon_news.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_azstarnet.py b/resources/recipes/azstarnet.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_azstarnet.py rename to resources/recipes/azstarnet.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_b92.py b/resources/recipes/b92.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_b92.py rename to resources/recipes/b92.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_barrons.py b/resources/recipes/barrons.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_barrons.py rename to resources/recipes/barrons.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_bbc.py b/resources/recipes/bbc.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_bbc.py rename to resources/recipes/bbc.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_bbcvietnamese.py b/resources/recipes/bbcvietnamese.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_bbcvietnamese.py rename to resources/recipes/bbcvietnamese.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_beta.py b/resources/recipes/beta.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_beta.py rename to resources/recipes/beta.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_beta_en.py b/resources/recipes/beta_en.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_beta_en.py rename to resources/recipes/beta_en.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_blic.py b/resources/recipes/blic.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_blic.py rename to resources/recipes/blic.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_borba.py b/resources/recipes/borba.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_borba.py rename to resources/recipes/borba.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_buenosaireseconomico.py b/resources/recipes/buenosaireseconomico.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_buenosaireseconomico.py rename to resources/recipes/buenosaireseconomico.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_business_standard.py b/resources/recipes/business_standard.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_business_standard.py rename to resources/recipes/business_standard.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_business_week.py b/resources/recipes/business_week.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_business_week.py rename to resources/recipes/business_week.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_businessworldin.py b/resources/recipes/businessworldin.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_businessworldin.py rename to resources/recipes/businessworldin.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_carta.py b/resources/recipes/carta.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_carta.py rename to resources/recipes/carta.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_chicago_breaking_news.py b/resources/recipes/chicago_breaking_news.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_chicago_breaking_news.py rename to resources/recipes/chicago_breaking_news.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_chicago_tribune.py b/resources/recipes/chicago_tribune.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_chicago_tribune.py rename to resources/recipes/chicago_tribune.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_chr_mon.py b/resources/recipes/chr_mon.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_chr_mon.py rename to resources/recipes/chr_mon.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_cincinnati_enquirer.py b/resources/recipes/cincinnati_enquirer.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_cincinnati_enquirer.py rename to resources/recipes/cincinnati_enquirer.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_clarin.py b/resources/recipes/clarin.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_clarin.py rename to resources/recipes/clarin.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_climate_progress.py b/resources/recipes/climate_progress.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_climate_progress.py rename to resources/recipes/climate_progress.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_cnn.py b/resources/recipes/cnn.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_cnn.py rename to resources/recipes/cnn.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_coding_horror.py b/resources/recipes/coding_horror.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_coding_horror.py rename to resources/recipes/coding_horror.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_common_dreams.py b/resources/recipes/common_dreams.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_common_dreams.py rename to resources/recipes/common_dreams.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_corriere_della_sera_en.py b/resources/recipes/corriere_della_sera_en.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_corriere_della_sera_en.py rename to resources/recipes/corriere_della_sera_en.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_corriere_della_sera_it.py b/resources/recipes/corriere_della_sera_it.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_corriere_della_sera_it.py rename to resources/recipes/corriere_della_sera_it.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_courrierinternational.py b/resources/recipes/courrierinternational.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_courrierinternational.py rename to resources/recipes/courrierinternational.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_craigslist.py b/resources/recipes/craigslist.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_craigslist.py rename to resources/recipes/craigslist.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_criticadigital.py b/resources/recipes/criticadigital.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_criticadigital.py rename to resources/recipes/criticadigital.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_cubadebate.py b/resources/recipes/cubadebate.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_cubadebate.py rename to resources/recipes/cubadebate.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_cyberpresse.py b/resources/recipes/cyberpresse.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_cyberpresse.py rename to resources/recipes/cyberpresse.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_daily_mail.py b/resources/recipes/daily_mail.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_daily_mail.py rename to resources/recipes/daily_mail.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_daily_telegraph.py b/resources/recipes/daily_telegraph.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_daily_telegraph.py rename to resources/recipes/daily_telegraph.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_danas.py b/resources/recipes/danas.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_danas.py rename to resources/recipes/danas.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_darknet.py b/resources/recipes/darknet.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_darknet.py rename to resources/recipes/darknet.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_de_standaard.py b/resources/recipes/de_standaard.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_de_standaard.py rename to resources/recipes/de_standaard.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_degentenaar.py b/resources/recipes/degentenaar.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_degentenaar.py rename to resources/recipes/degentenaar.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_demorgen_be.py b/resources/recipes/demorgen_be.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_demorgen_be.py rename to resources/recipes/demorgen_be.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_der_standard.py b/resources/recipes/der_standard.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_der_standard.py rename to resources/recipes/der_standard.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_diagonales.py b/resources/recipes/diagonales.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_diagonales.py rename to resources/recipes/diagonales.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_diepresse.py b/resources/recipes/diepresse.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_diepresse.py rename to resources/recipes/diepresse.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_discover_magazine.py b/resources/recipes/discover_magazine.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_discover_magazine.py rename to resources/recipes/discover_magazine.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_dna.py b/resources/recipes/dna.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_dna.py rename to resources/recipes/dna.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_dnevni_avaz.py b/resources/recipes/dnevni_avaz.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_dnevni_avaz.py rename to resources/recipes/dnevni_avaz.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_dnevnik_cro.py b/resources/recipes/dnevnik_cro.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_dnevnik_cro.py rename to resources/recipes/dnevnik_cro.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_e_novine.py b/resources/recipes/e_novine.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_e_novine.py rename to resources/recipes/e_novine.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ecogeek.py b/resources/recipes/ecogeek.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ecogeek.py rename to resources/recipes/ecogeek.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_economist.py b/resources/recipes/economist.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_economist.py rename to resources/recipes/economist.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_el_mercurio_chile.py b/resources/recipes/el_mercurio_chile.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_el_mercurio_chile.py rename to resources/recipes/el_mercurio_chile.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_el_pais.py b/resources/recipes/el_pais.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_el_pais.py rename to resources/recipes/el_pais.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_el_universal.py b/resources/recipes/el_universal.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_el_universal.py rename to resources/recipes/el_universal.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elargentino.py b/resources/recipes/elargentino.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elargentino.py rename to resources/recipes/elargentino.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elcronista.py b/resources/recipes/elcronista.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elcronista.py rename to resources/recipes/elcronista.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elektrolese.py b/resources/recipes/elektrolese.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elektrolese.py rename to resources/recipes/elektrolese.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elmundo.py b/resources/recipes/elmundo.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elmundo.py rename to resources/recipes/elmundo.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elperiodico_catalan.py b/resources/recipes/elperiodico_catalan.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elperiodico_catalan.py rename to resources/recipes/elperiodico_catalan.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_elperiodico_spanish.py b/resources/recipes/elperiodico_spanish.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_elperiodico_spanish.py rename to resources/recipes/elperiodico_spanish.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_eltiempo_hn.py b/resources/recipes/eltiempo_hn.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_eltiempo_hn.py rename to resources/recipes/eltiempo_hn.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_endgadget.py b/resources/recipes/endgadget.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_endgadget.py rename to resources/recipes/endgadget.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_espn.py b/resources/recipes/espn.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_espn.py rename to resources/recipes/espn.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_esquire.py b/resources/recipes/esquire.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_esquire.py rename to resources/recipes/esquire.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_estadao.py b/resources/recipes/estadao.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_estadao.py rename to resources/recipes/estadao.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_exiled.py b/resources/recipes/exiled.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_exiled.py rename to resources/recipes/exiled.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_expansion_spanish.py b/resources/recipes/expansion_spanish.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_expansion_spanish.py rename to resources/recipes/expansion_spanish.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_fastcompany.py b/resources/recipes/fastcompany.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_fastcompany.py rename to resources/recipes/fastcompany.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_faznet.py b/resources/recipes/faznet.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_faznet.py rename to resources/recipes/faznet.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_financial_times.py b/resources/recipes/financial_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_financial_times.py rename to resources/recipes/financial_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_forbes.py b/resources/recipes/forbes.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_forbes.py rename to resources/recipes/forbes.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_freakonomics.py b/resources/recipes/freakonomics.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_freakonomics.py rename to resources/recipes/freakonomics.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ftd.py b/resources/recipes/ftd.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ftd.py rename to resources/recipes/ftd.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_fudzilla.py b/resources/recipes/fudzilla.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_fudzilla.py rename to resources/recipes/fudzilla.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_glas_srpske.py b/resources/recipes/glas_srpske.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_glas_srpske.py rename to resources/recipes/glas_srpske.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_glasgow_herald.py b/resources/recipes/glasgow_herald.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_glasgow_herald.py rename to resources/recipes/glasgow_herald.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_glasjavnosti.py b/resources/recipes/glasjavnosti.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_glasjavnosti.py rename to resources/recipes/glasjavnosti.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_globe_and_mail.py b/resources/recipes/globe_and_mail.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_globe_and_mail.py rename to resources/recipes/globe_and_mail.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_granma.py b/resources/recipes/granma.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_granma.py rename to resources/recipes/granma.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_greader.py b/resources/recipes/greader.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_greader.py rename to resources/recipes/greader.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_guardian.py b/resources/recipes/guardian.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_guardian.py rename to resources/recipes/guardian.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_gva_be.py b/resources/recipes/gva_be.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_gva_be.py rename to resources/recipes/gva_be.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_h1.py b/resources/recipes/h1.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_h1.py rename to resources/recipes/h1.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_h2.py b/resources/recipes/h2.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_h2.py rename to resources/recipes/h2.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_h3.py b/resources/recipes/h3.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_h3.py rename to resources/recipes/h3.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_harpers.py b/resources/recipes/harpers.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_harpers.py rename to resources/recipes/harpers.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_harpers_full.py b/resources/recipes/harpers_full.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_harpers_full.py rename to resources/recipes/harpers_full.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_heise.py b/resources/recipes/heise.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_heise.py rename to resources/recipes/heise.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_hindu.py b/resources/recipes/hindu.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_hindu.py rename to resources/recipes/hindu.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_hln.py b/resources/recipes/hln.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_hln.py rename to resources/recipes/hln.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_hln_be.py b/resources/recipes/hln_be.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_hln_be.py rename to resources/recipes/hln_be.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_hna.py b/resources/recipes/hna.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_hna.py rename to resources/recipes/hna.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_honoluluadvertiser.py b/resources/recipes/honoluluadvertiser.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_honoluluadvertiser.py rename to resources/recipes/honoluluadvertiser.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_honvedelem.py b/resources/recipes/honvedelem.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_honvedelem.py rename to resources/recipes/honvedelem.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_hrt.py b/resources/recipes/hrt.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_hrt.py rename to resources/recipes/hrt.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_huntechnet.py b/resources/recipes/huntechnet.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_huntechnet.py rename to resources/recipes/huntechnet.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_iht.py b/resources/recipes/iht.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_iht.py rename to resources/recipes/iht.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_index_hu.py b/resources/recipes/index_hu.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_index_hu.py rename to resources/recipes/index_hu.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_indy_star.py b/resources/recipes/indy_star.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_indy_star.py rename to resources/recipes/indy_star.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_infobae.py b/resources/recipes/infobae.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_infobae.py rename to resources/recipes/infobae.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_infoworld.py b/resources/recipes/infoworld.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_infoworld.py rename to resources/recipes/infoworld.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_inquirer_net.py b/resources/recipes/inquirer_net.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_inquirer_net.py rename to resources/recipes/inquirer_net.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_instapaper.py b/resources/recipes/instapaper.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_instapaper.py rename to resources/recipes/instapaper.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_intelligencer.py b/resources/recipes/intelligencer.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_intelligencer.py rename to resources/recipes/intelligencer.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_irish_times.py b/resources/recipes/irish_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_irish_times.py rename to resources/recipes/irish_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_japan_times.py b/resources/recipes/japan_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_japan_times.py rename to resources/recipes/japan_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_javalobby.py b/resources/recipes/javalobby.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_javalobby.py rename to resources/recipes/javalobby.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_jb_online.py b/resources/recipes/jb_online.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_jb_online.py rename to resources/recipes/jb_online.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py b/resources/recipes/joelonsoftware.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_joelonsoftware.py rename to resources/recipes/joelonsoftware.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_jpost.py b/resources/recipes/jpost.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_jpost.py rename to resources/recipes/jpost.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_jutarnji.py b/resources/recipes/jutarnji.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_jutarnji.py rename to resources/recipes/jutarnji.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_juventudrebelde.py b/resources/recipes/juventudrebelde.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_juventudrebelde.py rename to resources/recipes/juventudrebelde.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_juventudrebelde_english.py b/resources/recipes/juventudrebelde_english.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_juventudrebelde_english.py rename to resources/recipes/juventudrebelde_english.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_kellog_faculty.py b/resources/recipes/kellog_faculty.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_kellog_faculty.py rename to resources/recipes/kellog_faculty.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_kellog_insight.py b/resources/recipes/kellog_insight.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_kellog_insight.py rename to resources/recipes/kellog_insight.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_krstarica.py b/resources/recipes/krstarica.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_krstarica.py rename to resources/recipes/krstarica.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_krstarica_en.py b/resources/recipes/krstarica_en.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_krstarica_en.py rename to resources/recipes/krstarica_en.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_la_cuarta.py b/resources/recipes/la_cuarta.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_la_cuarta.py rename to resources/recipes/la_cuarta.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_la_republica.py b/resources/recipes/la_republica.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_la_republica.py rename to resources/recipes/la_republica.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_la_segunda.py b/resources/recipes/la_segunda.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_la_segunda.py rename to resources/recipes/la_segunda.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_la_tercera.py b/resources/recipes/la_tercera.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_la_tercera.py rename to resources/recipes/la_tercera.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lamujerdemivida.py b/resources/recipes/lamujerdemivida.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lamujerdemivida.py rename to resources/recipes/lamujerdemivida.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lanacion.py b/resources/recipes/lanacion.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lanacion.py rename to resources/recipes/lanacion.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lanacion_chile.py b/resources/recipes/lanacion_chile.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lanacion_chile.py rename to resources/recipes/lanacion_chile.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_laprensa.py b/resources/recipes/laprensa.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_laprensa.py rename to resources/recipes/laprensa.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_laprensa_hn.py b/resources/recipes/laprensa_hn.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_laprensa_hn.py rename to resources/recipes/laprensa_hn.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_laprensa_ni.py b/resources/recipes/laprensa_ni.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_laprensa_ni.py rename to resources/recipes/laprensa_ni.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_latimes.py b/resources/recipes/latimes.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_latimes.py rename to resources/recipes/latimes.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_latribuna.py b/resources/recipes/latribuna.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_latribuna.py rename to resources/recipes/latribuna.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lavanguardia.py b/resources/recipes/lavanguardia.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lavanguardia.py rename to resources/recipes/lavanguardia.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_le_monde.py b/resources/recipes/le_monde.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_le_monde.py rename to resources/recipes/le_monde.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_le_temps.py b/resources/recipes/le_temps.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_le_temps.py rename to resources/recipes/le_temps.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lemonde_dip.py b/resources/recipes/lemonde_dip.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lemonde_dip.py rename to resources/recipes/lemonde_dip.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_liberation.py b/resources/recipes/liberation.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_liberation.py rename to resources/recipes/liberation.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_linux_magazine.py b/resources/recipes/linux_magazine.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_linux_magazine.py rename to resources/recipes/linux_magazine.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_linuxdevices.py b/resources/recipes/linuxdevices.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_linuxdevices.py rename to resources/recipes/linuxdevices.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_livemint.py b/resources/recipes/livemint.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_livemint.py rename to resources/recipes/livemint.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_lrb.py b/resources/recipes/lrb.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_lrb.py rename to resources/recipes/lrb.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_marca.py b/resources/recipes/marca.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_marca.py rename to resources/recipes/marca.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_mediapart.py b/resources/recipes/mediapart.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_mediapart.py rename to resources/recipes/mediapart.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_miami_herald.py b/resources/recipes/miami_herald.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_miami_herald.py rename to resources/recipes/miami_herald.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_miradasalsur.py b/resources/recipes/miradasalsur.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_miradasalsur.py rename to resources/recipes/miradasalsur.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_mondedurable.py b/resources/recipes/mondedurable.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_mondedurable.py rename to resources/recipes/mondedurable.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_moneynews.py b/resources/recipes/moneynews.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_moneynews.py rename to resources/recipes/moneynews.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_monitor.py b/resources/recipes/monitor.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_monitor.py rename to resources/recipes/monitor.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_moscow_times.py b/resources/recipes/moscow_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_moscow_times.py rename to resources/recipes/moscow_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_msdnmag_en.py b/resources/recipes/msdnmag_en.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_msdnmag_en.py rename to resources/recipes/msdnmag_en.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nacional_cro.py b/resources/recipes/nacional_cro.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nacional_cro.py rename to resources/recipes/nacional_cro.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nasa.py b/resources/recipes/nasa.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nasa.py rename to resources/recipes/nasa.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_new_scientist.py b/resources/recipes/new_scientist.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_new_scientist.py rename to resources/recipes/new_scientist.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_new_york_review_of_books.py b/resources/recipes/new_york_review_of_books.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_new_york_review_of_books.py rename to resources/recipes/new_york_review_of_books.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_new_york_review_of_books_no_sub.py b/resources/recipes/new_york_review_of_books_no_sub.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_new_york_review_of_books_no_sub.py rename to resources/recipes/new_york_review_of_books_no_sub.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_new_yorker.py b/resources/recipes/new_yorker.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_new_yorker.py rename to resources/recipes/new_yorker.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_news_times.py b/resources/recipes/news_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_news_times.py rename to resources/recipes/news_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_newsweek.py b/resources/recipes/newsweek.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_newsweek.py rename to resources/recipes/newsweek.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_newsweek_argentina.py b/resources/recipes/newsweek_argentina.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_newsweek_argentina.py rename to resources/recipes/newsweek_argentina.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nin.py b/resources/recipes/nin.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nin.py rename to resources/recipes/nin.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_noaa.py b/resources/recipes/noaa.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_noaa.py rename to resources/recipes/noaa.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_novosti.py b/resources/recipes/novosti.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_novosti.py rename to resources/recipes/novosti.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nspm.py b/resources/recipes/nspm.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nspm.py rename to resources/recipes/nspm.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nspm_int.py b/resources/recipes/nspm_int.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nspm_int.py rename to resources/recipes/nspm_int.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nytimes.py b/resources/recipes/nytimes.recipe similarity index 99% rename from src/calibre/web/feeds/recipes/recipe_nytimes.py rename to resources/recipes/nytimes.recipe index af78856010..8b95c8432b 100644 --- a/src/calibre/web/feeds/recipes/recipe_nytimes.py +++ b/resources/recipes/nytimes.recipe @@ -14,7 +14,7 @@ class NYTimes(BasicNewsRecipe): title = 'New York Times Top Stories' __author__ = 'GRiker' - language = _('English') + language = 'en' description = 'Top Stories from the New York Times' # List of sections typically included in Top Stories. Use a keyword from the diff --git a/src/calibre/web/feeds/recipes/recipe_nytimes_sub.py b/resources/recipes/nytimes_sub.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nytimes_sub.py rename to resources/recipes/nytimes_sub.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_nzz_ger.py b/resources/recipes/nzz_ger.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_nzz_ger.py rename to resources/recipes/nzz_ger.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_o_globo.py b/resources/recipes/o_globo.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_o_globo.py rename to resources/recipes/o_globo.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_ourdailybread.py b/resources/recipes/ourdailybread.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_ourdailybread.py rename to resources/recipes/ourdailybread.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_outlook_india.py b/resources/recipes/outlook_india.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_outlook_india.py rename to resources/recipes/outlook_india.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_pagina12.py b/resources/recipes/pagina12.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_pagina12.py rename to resources/recipes/pagina12.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_pcworld_hu.py b/resources/recipes/pcworld_hu.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_pcworld_hu.py rename to resources/recipes/pcworld_hu.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_pescanik.py b/resources/recipes/pescanik.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_pescanik.py rename to resources/recipes/pescanik.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_phd_comics.py b/resources/recipes/phd_comics.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_phd_comics.py rename to resources/recipes/phd_comics.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_philly.py b/resources/recipes/philly.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_philly.py rename to resources/recipes/philly.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_physics_today.py b/resources/recipes/physics_today.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_physics_today.py rename to resources/recipes/physics_today.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_physics_world.py b/resources/recipes/physics_world.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_physics_world.py rename to resources/recipes/physics_world.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_pobjeda.py b/resources/recipes/pobjeda.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_pobjeda.py rename to resources/recipes/pobjeda.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_politico.py b/resources/recipes/politico.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_politico.py rename to resources/recipes/politico.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_politika.py b/resources/recipes/politika.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_politika.py rename to resources/recipes/politika.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_portfolio.py b/resources/recipes/portfolio.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_portfolio.py rename to resources/recipes/portfolio.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_pressonline.py b/resources/recipes/pressonline.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_pressonline.py rename to resources/recipes/pressonline.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_publico.py b/resources/recipes/publico.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_publico.py rename to resources/recipes/publico.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_republika.py b/resources/recipes/republika.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_republika.py rename to resources/recipes/republika.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_reuters.py b/resources/recipes/reuters.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_reuters.py rename to resources/recipes/reuters.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_rga.py b/resources/recipes/rga.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_rga.py rename to resources/recipes/rga.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_rts.py b/resources/recipes/rts.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_rts.py rename to resources/recipes/rts.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_salon.py b/resources/recipes/salon.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_salon.py rename to resources/recipes/salon.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_san_fran_chronicle.py b/resources/recipes/san_fran_chronicle.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_san_fran_chronicle.py rename to resources/recipes/san_fran_chronicle.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_sanjosemercurynews.py b/resources/recipes/sanjosemercurynews.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_sanjosemercurynews.py rename to resources/recipes/sanjosemercurynews.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_science_aas.py b/resources/recipes/science_aas.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_science_aas.py rename to resources/recipes/science_aas.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_science_news.py b/resources/recipes/science_news.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_science_news.py rename to resources/recipes/science_news.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_sciencedaily.py b/resources/recipes/sciencedaily.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_sciencedaily.py rename to resources/recipes/sciencedaily.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_scientific_american.py b/resources/recipes/scientific_american.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_scientific_american.py rename to resources/recipes/scientific_american.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_scott_hanselman.py b/resources/recipes/scott_hanselman.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_scott_hanselman.py rename to resources/recipes/scott_hanselman.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_seattle_times.py b/resources/recipes/seattle_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_seattle_times.py rename to resources/recipes/seattle_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_security_watch.py b/resources/recipes/security_watch.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_security_watch.py rename to resources/recipes/security_watch.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_serverside.py b/resources/recipes/serverside.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_serverside.py rename to resources/recipes/serverside.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_shacknews.py b/resources/recipes/shacknews.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_shacknews.py rename to resources/recipes/shacknews.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_slashdot.py b/resources/recipes/slashdot.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_slashdot.py rename to resources/recipes/slashdot.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_slate.py b/resources/recipes/slate.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_slate.py rename to resources/recipes/slate.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_smashing.py b/resources/recipes/smashing.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_smashing.py rename to resources/recipes/smashing.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_smh.py b/resources/recipes/smh.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_smh.py rename to resources/recipes/smh.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_soldiers.py b/resources/recipes/soldiers.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_soldiers.py rename to resources/recipes/soldiers.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_spiegel_int.py b/resources/recipes/spiegel_int.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_spiegel_int.py rename to resources/recipes/spiegel_int.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_spiegelde.py b/resources/recipes/spiegelde.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_spiegelde.py rename to resources/recipes/spiegelde.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_st_petersburg_times.py b/resources/recipes/st_petersburg_times.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_st_petersburg_times.py rename to resources/recipes/st_petersburg_times.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_stackoverflow.py b/resources/recipes/stackoverflow.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_stackoverflow.py rename to resources/recipes/stackoverflow.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_starbulletin.py b/resources/recipes/starbulletin.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_starbulletin.py rename to resources/recipes/starbulletin.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_straitstimes.py b/resources/recipes/straitstimes.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_straitstimes.py rename to resources/recipes/straitstimes.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_sueddeutsche.py b/resources/recipes/sueddeutsche.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_sueddeutsche.py rename to resources/recipes/sueddeutsche.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tanjug.py b/resources/recipes/tanjug.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tanjug.py rename to resources/recipes/tanjug.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_telegraph_uk.py b/resources/recipes/telegraph_uk.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_telegraph_uk.py rename to resources/recipes/telegraph_uk.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_telepolis.py b/resources/recipes/telepolis.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_telepolis.py rename to resources/recipes/telepolis.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_telepolis_artikel.py b/resources/recipes/telepolis_artikel.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_telepolis_artikel.py rename to resources/recipes/telepolis_artikel.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_teleread.py b/resources/recipes/teleread.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_teleread.py rename to resources/recipes/teleread.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_age.py b/resources/recipes/the_age.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_age.py rename to resources/recipes/the_age.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_budget_fashionista.py b/resources/recipes/the_budget_fashionista.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_budget_fashionista.py rename to resources/recipes/the_budget_fashionista.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_nation.py b/resources/recipes/the_nation.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_nation.py rename to resources/recipes/the_nation.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_new_republic.py b/resources/recipes/the_new_republic.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_new_republic.py rename to resources/recipes/the_new_republic.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_oz.py b/resources/recipes/the_oz.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_oz.py rename to resources/recipes/the_oz.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_register.py b/resources/recipes/the_register.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_register.py rename to resources/recipes/the_register.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_the_scotsman.py b/resources/recipes/the_scotsman.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_the_scotsman.py rename to resources/recipes/the_scotsman.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_thedgesingapore.py b/resources/recipes/thedgesingapore.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_thedgesingapore.py rename to resources/recipes/thedgesingapore.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_theeconomictimes_india.py b/resources/recipes/theeconomictimes_india.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_theeconomictimes_india.py rename to resources/recipes/theeconomictimes_india.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_themarketticker.py b/resources/recipes/themarketticker.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_themarketticker.py rename to resources/recipes/themarketticker.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_theoldfoodie.py b/resources/recipes/theoldfoodie.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_theoldfoodie.py rename to resources/recipes/theoldfoodie.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_theonion.py b/resources/recipes/theonion.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_theonion.py rename to resources/recipes/theonion.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_thestar.py b/resources/recipes/thestar.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_thestar.py rename to resources/recipes/thestar.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tijd.py b/resources/recipes/tijd.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tijd.py rename to resources/recipes/tijd.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_time_magazine.py b/resources/recipes/time_magazine.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_time_magazine.py rename to resources/recipes/time_magazine.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_times_online.py b/resources/recipes/times_online.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_times_online.py rename to resources/recipes/times_online.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tnxm.py b/resources/recipes/tnxm.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tnxm.py rename to resources/recipes/tnxm.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tomshardware.py b/resources/recipes/tomshardware.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tomshardware.py rename to resources/recipes/tomshardware.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tomshardware_de.py b/resources/recipes/tomshardware_de.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tomshardware_de.py rename to resources/recipes/tomshardware_de.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_toronto_sun.py b/resources/recipes/toronto_sun.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_toronto_sun.py rename to resources/recipes/toronto_sun.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_tweakers.py b/resources/recipes/tweakers.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_tweakers.py rename to resources/recipes/tweakers.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_twitchfilms.py b/resources/recipes/twitchfilms.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_twitchfilms.py rename to resources/recipes/twitchfilms.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_uncrate.py b/resources/recipes/uncrate.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_uncrate.py rename to resources/recipes/uncrate.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_upi.py b/resources/recipes/upi.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_upi.py rename to resources/recipes/upi.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_usatoday.py b/resources/recipes/usatoday.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_usatoday.py rename to resources/recipes/usatoday.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_usnews.py b/resources/recipes/usnews.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_usnews.py rename to resources/recipes/usnews.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_utne.py b/resources/recipes/utne.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_utne.py rename to resources/recipes/utne.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_vecernji_list.py b/resources/recipes/vecernji_list.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_vecernji_list.py rename to resources/recipes/vecernji_list.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_veintitres.py b/resources/recipes/veintitres.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_veintitres.py rename to resources/recipes/veintitres.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_vijesti.py b/resources/recipes/vijesti.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_vijesti.py rename to resources/recipes/vijesti.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_vnexpress.py b/resources/recipes/vnexpress.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_vnexpress.py rename to resources/recipes/vnexpress.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_volksrant.py b/resources/recipes/volksrant.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_volksrant.py rename to resources/recipes/volksrant.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_vreme.py b/resources/recipes/vreme.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_vreme.py rename to resources/recipes/vreme.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_wash_post.py b/resources/recipes/wash_post.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_wash_post.py rename to resources/recipes/wash_post.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_wikinews_en.py b/resources/recipes/wikinews_en.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_wikinews_en.py rename to resources/recipes/wikinews_en.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_winsupersite.py b/resources/recipes/winsupersite.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_winsupersite.py rename to resources/recipes/winsupersite.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_wired.py b/resources/recipes/wired.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_wired.py rename to resources/recipes/wired.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_woz_die.py b/resources/recipes/woz_die.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_woz_die.py rename to resources/recipes/woz_die.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_wsj.py b/resources/recipes/wsj.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_wsj.py rename to resources/recipes/wsj.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_xkcd.py b/resources/recipes/xkcd.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_xkcd.py rename to resources/recipes/xkcd.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_zaobao.py b/resources/recipes/zaobao.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_zaobao.py rename to resources/recipes/zaobao.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_zdnet.py b/resources/recipes/zdnet.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_zdnet.py rename to resources/recipes/zdnet.recipe diff --git a/src/calibre/web/feeds/recipes/recipe_zeitde.py b/resources/recipes/zeitde.recipe similarity index 100% rename from src/calibre/web/feeds/recipes/recipe_zeitde.py rename to resources/recipes/zeitde.recipe diff --git a/setup/check.py b/setup/check.py index 14992b1628..86df693b68 100644 --- a/setup/check.py +++ b/setup/check.py @@ -42,26 +42,38 @@ class Check(Command): BUILTINS = ['_', '__', 'dynamic_property', 'I', 'P'] CACHE = '.check-cache.pickle' + def get_files(self, cache): + for x in os.walk(self.j(self.SRC, 'calibre')): + for f in x[-1]: + y = self.j(x[0], f) + mtime = os.stat(y).st_mtime + if f.endswith('.py') and f not in ('ptempfile.py', 'feedparser.py', + 'pyparsing.py', 'markdown.py') and \ + 'genshi' not in y and cache.get(y, 0) != mtime and \ + 'prs500/driver.py' not in y: + yield y, mtime + + for x in os.walk(self.j(self.d(self.SRC), 'resources', 'recipes')): + for f in x[-1]: + f = self.j(x[0], f) + mtime = os.stat(f).st_mtime + if f.endswith('.recipe') and cache.get(f, 0) != mtime: + yield f, mtime + + def run(self, opts): cache = {} if os.path.exists(self.CACHE): cache = cPickle.load(open(self.CACHE, 'rb')) - for x in os.walk(self.j(self.SRC, 'calibre')): - for f in x[-1]: - f = self.j(x[0], f) - mtime = os.stat(f).st_mtime - if f.endswith('.py') and cache.get(f, 0) != mtime and \ - self.b(f) not in ('ptempfile.py', 'feedparser.py', - 'pyparsing.py', 'markdown.py') and 'genshi' not in f and \ - 'prs500/driver.py' not in f: - self.info('\tChecking', f) - w = check_for_python_errors(f, self.BUILTINS) - if w: - self.report_errors(w) - cPickle.dump(cache, open(self.CACHE, 'wb'), -1) - subprocess.call(['gvim', '-f', f]) - raise SystemExit(1) - cache[f] = mtime + for f, mtime in self.get_files(cache): + self.info('\tChecking', f) + w = check_for_python_errors(f, self.BUILTINS) + if w: + self.report_errors(w) + cPickle.dump(cache, open(self.CACHE, 'wb'), -1) + subprocess.call(['gvim', '-f', f]) + raise SystemExit(1) + cache[f] = mtime cPickle.dump(cache, open(self.CACHE, 'wb'), -1) diff --git a/setup/resources.py b/setup/resources.py index 253876989e..d40d31bbf5 100644 --- a/setup/resources.py +++ b/setup/resources.py @@ -26,17 +26,6 @@ class Resources(Command): description = 'Compile various needed calibre resources' - def get_recipes(self): - sdir = os.path.join('src', 'calibre', 'web', 'feeds', 'recipes') - resources= {} - files = [] - for f in os.listdir(sdir): - if f.endswith('.py') and f != '__init__.py': - files.append(os.path.join(sdir, f)) - resources[f.replace('.py', '')] = open(files[-1], 'rb').read() - return resources, files - - def run(self, opts): scripts = {} for x in ('console', 'gui'): @@ -51,13 +40,15 @@ class Resources(Command): f = open(dest, 'wb') cPickle.dump(scripts, f, -1) - recipes, files = self.get_recipes() + from calibre.web.feeds.recipes.collection import \ + serialize_builtin_recipes, iterate_over_builtin_recipe_files - dest = self.j(self.RESOURCES, 'recipes.pickle') + files = [x[1] for x in iterate_over_builtin_recipe_files()] + + dest = self.j(self.RESOURCES, 'builtin_recipes.xml') if self.newer(dest, files): - self.info('\tCreating recipes.pickle') - f = open(dest, 'wb') - cPickle.dump(recipes, f, -1) + self.info('\tCreating builtin_recipes.xml') + open(dest, 'wb').write(serialize_builtin_recipes()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.pickle') files = [] @@ -70,8 +61,9 @@ class Resources(Command): complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) - from calibre.web.feeds.recipes import recipes - complete['input_recipes'] = [t.title+'.recipe ' for t in recipes] + from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles + complete['input_recipes'] = [t+'.recipe ' for t in + get_builtin_recipe_titles()] from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser @@ -90,9 +82,6 @@ class Resources(Command): cPickle.dump(complete, open(dest, 'wb'), -1) - - - def clean(self): for x in ('scripts', 'recipes', 'ebook-convert-complete'): x = self.j(self.RESOURCES, x+'.pickle') diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 32f7a32efa..426f4da50b 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -396,7 +396,8 @@ class FileDialog(QObject): if filters: for filter in filters: text, extensions = filter - extensions = ['*.'+i if not i.startswith('.') else i for i in extensions] + extensions = ['*'+(i if i.startswith('.') else '.'+i) for i in + extensions] ftext += '%s (%s);;'%(text, ' '.join(extensions)) if add_all_files_filter or not ftext: ftext += 'All files (*)' diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index 878bdf3c25..79fe926909 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -7,417 +7,188 @@ __docformat__ = 'restructuredtext en' Scheduler for automated recipe downloads ''' -import sys, copy, time -from datetime import datetime, timedelta, date -from PyQt4.Qt import QDialog, QApplication, SIGNAL, \ - QColor, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \ - QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex +from datetime import datetime, timedelta + +from PyQt4.Qt import QDialog, QApplication, SIGNAL, Qt, QTime, QObject, QMenu, \ + QAction, QIcon, QMutex, QTimer -from calibre import english_sort from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog from calibre.gui2.search_box import SearchBox2 -from calibre.web.feeds.recipes import recipes, recipe_modules, compile_recipe -from calibre.utils.search_query_parser import SearchQueryParser -from calibre.utils.pyparsing import ParseException -from calibre.utils.localization import get_language -from calibre.gui2 import NONE, error_dialog, config as gconf -from calibre.utils.config import DynamicConfig +from calibre.gui2 import config as gconf +from calibre.web.feeds.recipes.model import RecipeModel from calibre.ptempfile import PersistentTemporaryFile -from calibre.gui2.dialogs.user_profiles import UserProfiles - -config = DynamicConfig('scheduler') - -class Recipe(object): - - def __init__(self, id=None, recipe_class=None, builtin=True): - self.id = id - self.title = getattr(recipe_class, 'title', None) - self.description = getattr(recipe_class, 'description', None) - self.language = getattr(recipe_class, 'language', 'und') - self.last_downloaded = datetime.fromordinal(1) - self.downloading = False - self.builtin = builtin - self.schedule = None - self.author = getattr(recipe_class, '__author__', _('Unknown')) - if self.author == _('Unknown') and not builtin: - self.author = _('You') - self.needs_subscription = getattr(recipe_class, 'needs_subscription', False) - - def pickle(self): - return self.__dict__.copy() - - def unpickle(self, dict): - self.__dict__.update(dict) - return self - - def __cmp__(self, other): - if self.id == getattr(other, 'id', None): - return 0 - if self.schedule is None and getattr(other, 'schedule', None) is not None: - return 1 - if self.schedule is not None and getattr(other, 'schedule', None) is None: - return -1 - if self.builtin and not getattr(other, 'builtin', True): - return 1 - if not self.builtin and getattr(other, 'builtin', True): - return -1 - return english_sort(self.title, getattr(other, 'title', '')) - - def __hash__(self): - return hash(self.id) - - def __eq__(self, other): - return self.id == getattr(other, 'id', None) - - def __repr__(self): - schedule = self.schedule - if schedule and schedule > 1e5: - schedule = decode_schedule(schedule) - return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule) - -builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)] - -def save_recipes(recipes): - config['scheduled_recipes'] = [r.pickle() for r in recipes] - -def load_recipes(): - config.refresh() - recipes = [] - for r in config.get('scheduled_recipes', []): - r = Recipe().unpickle(r) - if r.builtin and \ - (not str(r.id).startswith('recipe_') or not str(r.id) in recipe_modules): - continue - recipes.append(r) - return recipes - -class RecipeModel(QAbstractItemModel, SearchQueryParser): - - LOCATIONS = ['all'] - - def __init__(self, db, *args): - QAbstractItemModel.__init__(self, *args) - SearchQueryParser.__init__(self) - self.default_icon = QIcon(I('news.svg')) - self.custom_icon = QIcon(I('user_profile.svg')) - self.recipes = copy.deepcopy(builtin_recipes) - for x in db.get_recipes(): - recipe = compile_recipe(x[1]) - self.recipes.append(Recipe(x[0], recipe, False)) - self.refresh() - self.bold_font = QFont() - self.bold_font.setBold(True) - self.bold_font = QVariant(self.bold_font) - - - def refresh(self): - sr = load_recipes() - for recipe in self.recipes: - if recipe in sr: - recipe.schedule = sr[sr.index(recipe)].schedule - recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded - - self.recipes.sort() - self.num_of_recipes = len(self.recipes) - - self.category_map = {} - for r in self.recipes: - category = get_language(getattr(r, 'language', 'und')) - if not r.builtin: - category = _('Custom') - if r.schedule is not None: - category = _('Scheduled') - if category not in self.category_map.keys(): - self.category_map[category] = [] - self.category_map[category].append(r) - - self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories) - self._map = dict(self.category_map) - - def scheduled_recipes(self): - for recipe in self.category_map.get(_('Scheduled'), []): - yield recipe - - def sort_categories(self, x, y): - - def decorate(x): - if x == _('Scheduled'): - x = '0' + x - elif x == _('Custom'): - x = '1' + x - else: - x = '2' + x - return x - - return cmp(decorate(x), decorate(y)) - - - def universal_set(self): - return set(self.recipes) - - def get_matches(self, location, query): - query = query.strip().lower() - if not query: - return set(self.recipes) - results = set([]) - for recipe in self.recipes: - if query in recipe.title.lower() or query in recipe.description.lower(): - results.add(recipe) - return results - - def search(self, query, refinement): - try: - results = self.parse(unicode(query)) - except ParseException: - self._map = dict(self.category_map) - else: - self._map = {} - for category in self.categories: - self._map[category] = [] - for recipe in self.category_map[category]: - if recipe in results: - self._map[category].append(recipe) - self.reset() - self.emit(SIGNAL('searched(PyQt_PyObject)'), True) - - def resort(self): - self.recipes.sort() - self.reset() - - def index(self, row, column, parent): - return self.createIndex(row, column, parent.row()+1 if parent.isValid() else 0) - - def parent(self, index): - if index.internalId() == 0: - return QModelIndex() - return self.createIndex(index.internalId()-1, 0, 0) - - def columnCount(self, parent): - if not parent.isValid() or not parent.parent().isValid(): - return 1 - return 0 - - def rowCount(self, parent): - if not parent.isValid(): - return len(self.categories) - if not parent.parent().isValid(): - category = self.categories[parent.row()] - return len(self._map[category]) - return 0 - - def data(self, index, role): - if index.parent().isValid(): - category = self.categories[index.parent().row()] - recipe = self._map[category][index.row()] - if role == Qt.DisplayRole: - return QVariant(recipe.title) - elif role == Qt.UserRole: - return recipe - elif role == Qt.DecorationRole: - icon = self.default_icon - icon_path = (I('news/%s.png')%recipe.id).replace('recipe_', '') - if not recipe.builtin: - icon = self.custom_icon - elif QFile().exists(icon_path): - icon = QIcon(icon_path) - return QVariant(icon) - else: - category = self.categories[index.row()] - if role == Qt.DisplayRole: - num = len(self._map[category]) - return QVariant(category + ' [%d]'%num) - elif role == Qt.FontRole: - return self.bold_font - elif role == Qt.ForegroundRole and category == _('Scheduled'): - return QVariant(QColor(0, 255, 0)) - return NONE - - def update_recipe_schedule(self, recipe): - for srecipe in self.recipes: - if srecipe == recipe: - srecipe.schedule = recipe.schedule - - -def encode_schedule(day, hour, minute): - day = 1e7 * (day+1) - hour = 1e4 * (hour+1) - return day + hour + minute + 1 - -def decode_schedule(num): - raw = '%d'%int(num) - day = int(raw[0]) - hour = int(raw[2:4]) - minute = int(raw[-2:]) - return day-1, hour-1, minute-1 class SchedulerDialog(QDialog, Ui_Dialog): - def __init__(self, db, *args): - QDialog.__init__(self, *args) + def __init__(self, recipe_model, parent=None): + QDialog.__init__(self, parent) self.setupUi(self) + self.recipe_model = recipe_model + self.recipe_model.do_refresh() + self.search = SearchBox2(self) self.search.initialize('scheduler_search_history') self.recipe_box.layout().insertWidget(0, self.search) + self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'), + self.recipe_model.search) + self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), + self.search.search_done) + self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), + self.search_done) + self.search.setFocus(Qt.OtherFocusReason) + + self.recipes.setModel(self.recipe_model) self.detail_box.setVisible(False) - self._model = RecipeModel(db) - self.current_recipe = None - self.recipes.setModel(self._model) - self.recipes.currentChanged = self.currentChanged - self.connect(self.username, SIGNAL('textEdited(QString)'), self.set_account_info) - self.connect(self.password, SIGNAL('textEdited(QString)'), self.set_account_info) - self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule) + self.download_button.setVisible(False) + self.recipes.currentChanged = self.current_changed + self.interval_button.setChecked(True) + + self.connect(self.schedule, SIGNAL('stateChanged(int)'), + self.toggle_schedule_info) self.connect(self.show_password, SIGNAL('stateChanged(int)'), lambda state: self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password)) - self.connect(self.interval, SIGNAL('valueChanged(double)'), self.do_schedule) - self.connect(self.day, SIGNAL('currentIndexChanged(int)'), self.do_schedule) - self.connect(self.time, SIGNAL('timeChanged(QTime)'), self.do_schedule) - for button in (self.daily_button, self.interval_button): - self.connect(button, SIGNAL('toggled(bool)'), self.do_schedule) - self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'), self._model.search) - self.connect(self._model, SIGNAL('searched(PyQt_PyObject)'), - self.search.search_done) - self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False)) + self.connect(self.download_button, SIGNAL('clicked()'), + self.download_clicked) self.connect(self.download_all_button, SIGNAL('clicked()'), - self.download_all) - self.connect(self.download, SIGNAL('clicked()'), self.download_now) - self.search.setFocus(Qt.OtherFocusReason) + self.download_all_clicked) + self.old_news.setValue(gconf['oldest_news']) - self.rnumber.setText(_('%d recipes')%self._model.num_of_recipes) - for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), - _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')): - self.day.addItem(day) - def currentChanged(self, current, previous): - if current.parent().isValid(): - self.show_recipe(current) + def break_cycles(self): + self.recipe_model = None - def download_all(self, *args): - for recipe in self._model.scheduled_recipes(): - self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe) + def search_done(self, *args): + if self.recipe_model.showing_count < 10: + self.recipes.expandAll() - def download_now(self): - recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) - self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe) + def toggle_schedule_info(self, *args): + enabled = self.schedule.isChecked() + for x in ('daily_button', 'day', 'time', 'interval_button', 'interval'): + getattr(self, x).setEnabled(enabled) + self.last_downloaded.setVisible(enabled) - def set_account_info(self, *args): - username, password = map(unicode, (self.username.text(), self.password.text())) - username, password = username.strip(), password.strip() - recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) - key = 'recipe_account_info_%s'%recipe.id - config[key] = (username, password) if username else None + def current_changed(self, current, previous): + if previous.isValid(): + self.commit(urn=getattr(previous.internalPointer(), 'urn', None)) - def do_schedule(self, *args): - if not getattr(self, 'allow_scheduling', False): + urn = self.current_urn + if urn is not None: + self.initialize_detail_box(urn) + + def accept(self): + self.commit() + return QDialog.accept(self) + + def download_clicked(self): + self.commit() + if self.current_urn: + self.emit(SIGNAL('download(PyQt_PyObject)'), self.current_urn) + + def download_all_clicked(self): + self.commit() + self.emit(SIGNAL('download(PyQt_PyObject)'), None) + + @property + def current_urn(self): + current = self.recipes.currentIndex() + if current.isValid(): + return getattr(current.internalPointer(), 'urn', None) + + def commit(self, urn=None): + urn = self.current_urn if urn is None else urn + if not self.detail_box.isVisible() or urn is None: return - recipe = self.recipes.currentIndex() - if not recipe.isValid(): - return - recipe = self._model.data(recipe, Qt.UserRole) - recipes = load_recipes() - if self.schedule.checkState() == Qt.Checked: - if recipe in recipes: - recipe = recipes[recipes.index(recipe)] - else: - recipe.last_downloaded = datetime.fromordinal(1) - recipes.append(recipe) - if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]: - error_dialog(self, _('Must set account information'), - _('This recipe requires a username and password')).exec_() - self.schedule.setCheckState(Qt.Unchecked) - return - if self.interval_button.isChecked(): - recipe.schedule = self.interval.value() - if recipe.schedule < 0.1: - recipe.schedule = 1/24. + + if self.account.isVisible(): + un, pw = map(unicode, (self.username.text(), self.password.text())) + self.recipe_model.set_account_info(urn, un.strip(), pw.strip()) + + if self.schedule.isChecked(): + schedule_type = 'interval' if self.interval_button.isChecked() else 'day/time' + if schedule_type == 'interval': + schedule = self.interval.value() + if schedule < 0.1: + schedule = 1./24. else: day_of_week = self.day.currentIndex() - 1 - if day_of_week < 0: - day_of_week = 7 t = self.time.time() hour, minute = t.hour(), t.minute() - recipe.schedule = encode_schedule(day_of_week, hour, minute) + schedule = (day_of_week, hour, minute) + self.recipe_model.schedule_recipe(urn, schedule_type, schedule) else: - recipe.schedule = None - if recipe in recipes: - recipes.remove(recipe) - save_recipes(recipes) - self._model.update_recipe_schedule(recipe) - self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes) + self.recipe_model.un_schedule_recipe(urn) + + add_title_tag = self.add_title_tag.isChecked() + custom_tags = unicode(self.custom_tags.text()).strip() + custom_tags = [x.strip() for x in custom_tags.split(',')] + self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags) + + def initialize_detail_box(self, urn): + self.detail_box.setVisible(True) + self.download_button.setVisible(True) + self.detail_box.setCurrentIndex(0) + recipe = self.recipe_model.recipe_from_urn(urn) + schedule_info = self.recipe_model.schedule_info_from_urn(urn) + account_info = self.recipe_model.account_info_from_urn(urn) + customize_info = self.recipe_model.get_customize_info(urn) + + self.account.setVisible(recipe.get('needs_subscription', '') == 'yes') + un = pw = '' + if account_info is not None: + un, pw = account_info[:2] + if not un: un = '' + if not pw: pw = '' + self.username.setText(un) + self.password.setText(pw) + self.show_password.setChecked(False) - def show_recipe(self, index): - recipe = self._model.data(index, Qt.UserRole) - self.current_recipe = recipe self.blurb.setText('''

%(title)s
%(cb)s %(author)s
%(description)s

- '''%dict(title=recipe.title, cb=_('Created by: '), author=recipe.author, - description=recipe.description if recipe.description else '')) - self.allow_scheduling = False - schedule = -1 if recipe.schedule is None else recipe.schedule - if schedule < 1e5 and schedule >= 0: - self.interval.setValue(schedule) - self.interval_button.setChecked(True) - self.day.setEnabled(False), self.time.setEnabled(False) - else: - if schedule > 0: - day, hour, minute = decode_schedule(schedule) - else: - day, hour, minute = 7, 12, 0 - if day == 7: - day = -1 - self.day.setCurrentIndex(day+1) - self.time.setTime(QTime(hour, minute)) - self.daily_button.setChecked(True) - self.interval_button.setChecked(False) - self.interval.setEnabled(False) - self.schedule.setChecked(recipe.schedule is not None) - self.allow_scheduling = True - self.detail_box.setVisible(True) - self.account.setVisible(recipe.needs_subscription) - self.interval.setEnabled(self.schedule.checkState() == Qt.Checked) - key = 'recipe_account_info_%s'%recipe.id - account_info = config[key] - self.show_password.setChecked(False) - if account_info: - self.username.blockSignals(True) - self.password.blockSignals(True) - self.username.setText(account_info[0]) - self.password.setText(account_info[1]) - self.username.blockSignals(False) - self.password.blockSignals(False) - d = datetime.utcnow() - recipe.last_downloaded - def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60 - hours, minutes = hm(d.seconds) - tm = _('%d days, %d hours and %d minutes ago')%(d.days, hours, minutes) - if d < timedelta(days=366): - self.last_downloaded.setText(_('Last downloaded')+': '+tm) - else: - self.last_downloaded.setText(_('Last downloaded: never')) + '''%dict(title=recipe.get('title'), cb=_('Created by: '), + author=recipe.get('author', _('Unknown')), + description=recipe.get('description', ''))) + + scheduled = schedule_info is not None + self.schedule.setChecked(scheduled) + self.toggle_schedule_info() + self.last_downloaded.setText(_('Last downloaded: never')) + if scheduled: + typ, sch, last_downloaded = schedule_info + if typ == 'interval': + self.interval_button.setChecked(True) + self.interval.setValue(sch) + elif typ == 'day/time': + self.daily_button.setChecked(True) + day, hour, minute = sch + self.day.setCurrentIndex(day+1) + self.time.setTime(QTime(hour, minute)) + + d = datetime.utcnow() - last_downloaded + def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60 + hours, minutes = hm(d.seconds) + tm = _('%d days, %d hours and %d minutes ago')%(d.days, hours, minutes) + if d < timedelta(days=366): + self.last_downloaded.setText(_('Last downloaded')+': '+tm) + + add_title_tag, custom_tags = customize_info + self.add_title_tag.setChecked(add_title_tag) + self.custom_tags.setText(u', '.join(custom_tags)) + class Scheduler(QObject): INTERVAL = 1 # minutes - def __init__(self, main): - self.main = main - self.verbose = main.verbose - QObject.__init__(self) + def __init__(self, parent, db): + QObject.__init__(self, parent) + self._parent = parent + self.recipe_model = RecipeModel(db) self.lock = QMutex(QMutex.Recursive) - self.queue = set([]) - recipes = load_recipes() - self.refresh_schedule(recipes) - self.timer = QTimer() - self.dirtied = False - self.connect(self.timer, SIGNAL('timeout()'), self.check) - self.timer.start(int(self.INTERVAL * 60000)) - self.oldest_timer = QTimer() - self.connect(self.oldest_timer, SIGNAL('timeout()'), self.oldest_check) - self.oldest = gconf['oldest_news'] - self.oldest_timer.start(int(60 * 60000)) - self.oldest_check() + self.download_queue = set([]) self.news_menu = QMenu() self.news_icon = QIcon(I('news.svg')) @@ -428,132 +199,97 @@ class Scheduler(QObject): self.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds) self.news_menu.addAction(self.cac) + self.timer = QTimer(self) + self.timer.start(int(self.INTERVAL * 60000)) + self.oldest_timer = QTimer() + self.connect(self.oldest_timer, SIGNAL('timeout()'), self.oldest_check) + self.connect(self.timer, SIGNAL('timeout()'), self.check) + self.oldest = gconf['oldest_news'] + self.oldest_timer.start(int(60 * 60000)) + self.oldest_check() + def oldest_check(self): if self.oldest > 0: delta = timedelta(days=self.oldest) - ids = self.main.library_view.model().db.tags_older_than(_('News'), delta) + ids = self.recipe_model.db.tags_older_than(_('News'), delta) if ids: - self.main.library_view.model().delete_books_by_id(ids) - - def customize_feeds(self, *args): - main = self.main - d = UserProfiles(main, main.library_view.model().db.get_feeds()) - d.exec_() - feeds = tuple(d.profiles()) - main.library_view.model().db.set_feeds(feeds) - - - def debug(self, *args): - if self.verbose: - sys.stdout.write(' '.join(map(unicode, args))+'\n') - sys.stdout.flush() - - def check(self): - if not self.lock.tryLock(): - return - try: - if self.dirtied: - self.refresh_schedule(load_recipes()) - self.dirtied = False - needs_downloading = set([]) - self.debug('Checking...') - nowt = datetime.utcnow() - for recipe in self.recipes: - if recipe.schedule is None: - continue - delta = nowt - recipe.last_downloaded - if recipe.schedule < 1e5: - if delta > timedelta(days=recipe.schedule): - needs_downloading.add(recipe) - else: - day, hour, minute = decode_schedule(recipe.schedule) - now = time.localtime() - day_matches = day > 6 or day == now.tm_wday - tnow = now.tm_hour*60 + now.tm_min - matches = day_matches and (hour*60+minute) < tnow - if matches and recipe.last_downloaded.toordinal() < date.today().toordinal(): - needs_downloading.add(recipe) - - self.debug('Needs downloading:', needs_downloading) - - needs_downloading = [r for r in needs_downloading if r not in self.queue] - for recipe in needs_downloading: - self.do_download(recipe) - finally: - self.lock.unlock() - - def do_download(self, recipe): - try: - id = int(recipe.id) - script = self.main.library_view.model().db.get_recipe(id) - if script is None: - self.recipes.remove(recipe) - save_recipes(self.recipes) - return - pt = PersistentTemporaryFile('_builtin.recipe') - pt.write(script) - pt.close() - script = pt.name - except ValueError: - script = recipe.title + '.recipe' - self.debug('\tQueueing:', recipe) - self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded) - self.queue.add(recipe) - - def recipe_downloaded(self, recipe): - self.lock.lock() - try: - if recipe in self.recipes: - recipe = self.recipes[self.recipes.index(recipe)] - now = datetime.utcnow() - d = now - recipe.last_downloaded - if recipe.schedule is not None and recipe.schedule < 1e4: - interval = timedelta(days=recipe.schedule) - if abs(d - interval) < timedelta(hours=1): - recipe.last_downloaded += interval - else: - recipe.last_downloaded = now - else: - recipe.last_downloaded = now - save_recipes(self.recipes) - self.queue.remove(recipe) - self.dirtied = True - finally: - self.lock.unlock() - self.debug('Downloaded:', recipe) - - def download(self, recipe): - self.lock.lock() - try: - if recipe in self.recipes: - recipe = self.recipes[self.recipes.index(recipe)] - if recipe not in self.queue: - self.do_download(recipe) - finally: - self.lock.unlock() - - def refresh_schedule(self, recipes): - self.recipes = recipes + self.emit(SIGNAL('delete_old_news(PyQt_PyObject)'), ids) def show_dialog(self, *args): self.lock.lock() try: - d = SchedulerDialog(self.main.library_view.model().db) - self.connect(d, SIGNAL('new_schedule(PyQt_PyObject)'), self.refresh_schedule) - self.connect(d, SIGNAL('download_now(PyQt_PyObject)'), self.download) + d = SchedulerDialog(self.recipe_model) + self.connect(d, SIGNAL('download(PyQt_PyObject)'), + self.download_clicked) d.exec_() - gconf['oldest_news'] = d.old_news.value() - self.recipes = load_recipes() - self.oldest = d.old_news.value() + gconf['oldest_news'] = self.oldest = d.old_news.value() + d.break_cycles() finally: self.lock.unlock() -def main(args=sys.argv): - QApplication([]) - from calibre.library.database2 import LibraryDatabase2 - d = SchedulerDialog(LibraryDatabase2('/home/kovid/documents/library')) - d.exec_() - return 0 + def customize_feeds(self, *args): + from calibre.gui2.dialogs.user_profiles import UserProfiles + d = UserProfiles(self._parent, self.recipe_model) + d.exec_() + d.break_cycles() + + def do_download(self, urn): + self.lock.lock() + try: + account_info = self.recipe_model.get_account_info(urn) + customize_info = self.recipe_model.get_customize_info(urn) + recipe = self.recipe_model.recipe_from_urn(urn) + un = pw = None + if account_info is not None: + un, pw = account_info + add_title_tag, custom_tags = customize_info + script = self.recipe_model.get_recipe(urn) + pt = PersistentTemporaryFile('_builtin.recipe') + pt.write(script) + pt.close() + arg = { + 'username': un, + 'password': pw, + 'add_title_tag':add_title_tag, + 'custom_tags':custom_tags, + 'recipe':pt.name, + 'title':recipe.get('title',''), + 'urn':urn, + } + self.download_queue.add(urn) + self.emit(SIGNAL('start_recipe_fetch(PyQt_PyObject)'), arg) + finally: + self.lock.unlock() + + def recipe_downloaded(self, arg): + self.lock.lock() + try: + self.recipe_model.update_last_downloaded(arg['urn']) + self.download_queue.remove(arg['urn']) + finally: + self.lock.unlock() + + def download_clicked(self, urn): + if urn is not None: + return self.download(urn) + for urn in self.recipe_model.scheduled_urns(): + self.download(urn) + + def download(self, urn): + self.lock.lock() + doit = urn not in self.download_queue + self.lock.unlock() + if doit: + self.do_download(urn) + + def check(self): + recipes = self.recipe_model.get_to_be_downloaded_recipes() + for urn in recipes: + self.download(urn) if __name__ == '__main__': - sys.exit(main()) + QApplication([]) + from calibre.library.database2 import LibraryDatabase2 + d = SchedulerDialog(RecipeModel(LibraryDatabase2('/home/kovid/documents/library'))) + d.exec_() + diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index b8769ff47f..ec2c526768 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -78,7 +78,7 @@ 0 0 - 381 + 375 500 @@ -87,196 +87,276 @@ 0 - - - Schedule for download + + + + 0 + 100 + - - - - - blurb - - - Qt::RichText - - - true - - - true - - - - - - - &Schedule for download: - - - - - - - false - - + + 0 + + + + &Schedule + + + + + + blurb + + + Qt::RichText + + + true + + + true + + + + + + + &Schedule for download: + + + + + - - - - - Every - - - - - - - - - - at - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + Every + + - + - - - Every - - + + day + - - - - 0 - 0 - - - - Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour. - - - days - - - 1 - - - 0.000000000000000 - - - 365.100000000000023 - - - 1.000000000000000 - - - 1.000000000000000 - - + + Monday + - + + + Tuesday + + + + + Wednesday + + + + + Thursday + + + + + Friday + + + + + Saturday + + + + + Sunday + + + + + + + + at + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - - - - - true - - - - - - - &Account - - - - - - - + + + + + - &Username: - - - username + Every - - - - &Password: + + + + + 0 + 0 + - - password + + Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour. - - - - - - QLineEdit::Password + + days - - - - - - &Show password + + 1 + + + 0.000000000000000 + + + 365.100000000000023 + + + 1.000000000000000 + + + 1.000000000000000 - - - - - - For the scheduling to work, you must leave calibre running. - - - true - - - - - - - &Download now - - - - + + + + + + + + + + + + &Account + + + + + + + + + &Username: + + + username + + + + + + + &Password: + + + password + + + + + + + QLineEdit::Password + + + + + + + &Show password + + + + + + + + + + For the scheduling to work, you must leave calibre running. + + + true + + + + + + + + &Advanced + + + + + + Add &title as tag + + + + + + + &Extra tags: + + + custom_tags + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + &Download now + @@ -324,8 +404,8 @@ accept() - 613 - 824 + 762 + 570 157 @@ -340,8 +420,8 @@ reject() - 681 - 824 + 762 + 570 286 @@ -349,38 +429,6 @@ - - schedule - toggled(bool) - widget - setDisabled(bool) - - - 454 - 147 - - - 461 - 168 - - - - - schedule - toggled(bool) - widget - setEnabled(bool) - - - 458 - 137 - - - 461 - 169 - - - daily_button toggled(bool) @@ -388,12 +436,12 @@ setEnabled(bool) - 421 - 186 + 456 + 173 - 500 - 184 + 537 + 176 @@ -404,12 +452,12 @@ setEnabled(bool) - 442 - 193 + 456 + 173 - 603 - 183 + 647 + 176 @@ -420,8 +468,8 @@ setEnabled(bool) - 428 - 213 + 456 + 239 495 diff --git a/src/calibre/gui2/dialogs/user_profiles.py b/src/calibre/gui2/dialogs/user_profiles.py index 4584e10793..4b2552e43f 100644 --- a/src/calibre/gui2/dialogs/user_profiles.py +++ b/src/calibre/gui2/dialogs/user_profiles.py @@ -1,27 +1,89 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import time, os, cPickle -from PyQt4.QtCore import SIGNAL, QUrl -from PyQt4.QtGui import QDesktopServices +import time, os + +from PyQt4.Qt import SIGNAL, QUrl, QDesktopServices, QAbstractListModel, Qt, \ + QVariant, QInputDialog from calibre.web.feeds.recipes import compile_recipe from calibre.web.feeds.news import AutomaticNewsRecipe from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog, \ - choose_files, ResizableDialog + choose_files, ResizableDialog, NONE from calibre.gui2.widgets import PythonHighlighter from calibre.ptempfile import PersistentTemporaryFile +class CustomRecipeModel(QAbstractListModel): + + def __init__(self, recipe_model): + QAbstractListModel.__init__(self) + self.recipe_model = recipe_model + + def title(self, index): + row = index.row() + if row > -1 and row < self.rowCount(): + return self.recipe_model.custom_recipe_collection[row].get('title', '') + + def script(self, index): + row = index.row() + if row > -1 and row < self.rowCount(): + urn = self.recipe_model.custom_recipe_collection[row].get('id') + return self.recipe_model.get_recipe(urn) + + def has_title(self, title): + for x in self.recipe_model.custom_recipe_collection: + if x.get('title', False) == title: + return True + return False + + def rowCount(self, *args): + return len(self.recipe_model.custom_recipe_collection) + + def data(self, index, role): + if role == Qt.DisplayRole: + ans = self.title(index) + if ans is not None: + return QVariant(ans) + return NONE + + def replace_by_title(self, title, script): + urn = None + for x in self.recipe_model.custom_recipe_collection: + if x.get('title', False) == title: + urn = x.get('id') + if urn is not None: + self.recipe_model.update_custom_recipe(urn, title, script) + self.reset() + + def add(self, title, script): + self.recipe_model.add_custom_recipe(title, script) + self.reset() + + def remove(self, rows): + urns = [] + for r in rows: + try: + urn = self.recipe_model.custom_recipe_collection[r].get('id') + urns.append(urn) + except: + pass + self.recipe_model.remove_custom_recipes(urns) + self.reset() + class UserProfiles(ResizableDialog, Ui_Dialog): - def __init__(self, parent, feeds): + def __init__(self, parent, recipe_model): ResizableDialog.__init__(self, parent) + self._model = self.model = CustomRecipeModel(recipe_model) + self.available_profiles.setModel(self._model) + self.available_profiles.currentChanged = self.current_changed + self.connect(self.remove_feed_button, SIGNAL('clicked(bool)'), self.added_feeds.remove_selected_items) self.connect(self.remove_profile_button, SIGNAL('clicked(bool)'), - self.available_profiles.remove_selected_items) + self.remove_selected_items) self.connect(self.add_feed_button, SIGNAL('clicked(bool)'), self.add_feed) self.connect(self.load_button, SIGNAL('clicked()'), self.load) @@ -33,13 +95,16 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.add_profile) self.connect(self.feed_url, SIGNAL('returnPressed()'), self.add_feed) self.connect(self.feed_title, SIGNAL('returnPressed()'), self.add_feed) - self.connect(self.available_profiles, - SIGNAL('currentItemChanged(QListWidgetItem*, QListWidgetItem*)'), - self.edit_profile) self.connect(self.toggle_mode_button, SIGNAL('clicked(bool)'), self.toggle_mode) self.clear() - for title, src in feeds: - self.available_profiles.add_item(title, (title, src), replace=True) + + def break_cycles(self): + self.recipe_model = self._model.recipe_model = None + + def remove_selected_items(self): + indices = self.available_profiles.selectionModel().selectedRows() + self._model.remove([i.row() for i in indices]) + self.clear() def up(self): row = self.added_feeds.currentRow() @@ -56,12 +121,11 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.added_feeds.setCurrentItem(item) def share(self): - row = self.available_profiles.currentRow() - item = self.available_profiles.item(row) - if item is None: + index = self.available_profiles.currentIndex() + title, src = self._model.title(index), self._model.script(index) + if not title or not src: error_dialog(self, _('No recipe selected'), _('No recipe selected')).exec_() return - title, src = item.user_data pt = PersistentTemporaryFile(suffix='.py') pt.write(src.encode('utf-8')) pt.close() @@ -74,10 +138,11 @@ class UserProfiles(ResizableDialog, Ui_Dialog): QDesktopServices.openUrl(url) - def edit_profile(self, current, previous): - if not current: - current = previous - src = current.user_data[1] + def current_changed(self, current, previous): + if not current.isValid(): return + src = self._model.script(current) + if src is None: + return if 'class BasicUserRecipe' in src: recipe = compile_recipe(src) self.populate_options(recipe) @@ -172,72 +237,64 @@ class %(classname)s(%(base_class)s): _('

Could not create recipe. Error:
%s')%str(err)).exec_() return profile = src.replace('BasicUserRecipe', 'AdvancedUserRecipe') - try: - self.available_profiles.add_item(title, (title, profile), replace=False) - except ValueError: + if self._model.has_title(title): if question_dialog(self, _('Replace recipe?'), _('A custom recipe named %s already exists. Do you want to ' 'replace it?')%title): - self.available_profiles.add_item(title, (title, profile), replace=True) + self._model.replace_by_title(title, profile) else: return + else: + self.model.add(title, profile) self.clear() def add_builtin_recipe(self): - from calibre.web.feeds.recipes import recipes, recipe_modules, english_sort - from PyQt4.Qt import QInputDialog + from calibre.web.feeds.recipes.collection import \ + get_builtin_recipe_by_title, get_builtin_recipe_titles + items = get_builtin_recipe_titles() - rdat = cPickle.load(open(P('recipes.pickle'), 'rb')) - class Recipe(object): - def __init__(self, title, id, recipes): - self.title = unicode(title) - self.id = id - self.text = recipes[id] - def __cmp__(self, other): - return english_sort(self.title, other.title) - - recipes = sorted([Recipe(r.title, i, rdat) for r, i in zip(recipes, recipe_modules)]) - items = [r.title for r in recipes] title, ok = QInputDialog.getItem(self, _('Pick recipe'), _('Pick the recipe to customize'), items, 0, False) if ok: title = unicode(title) - for r in recipes: - if r.title == title: - try: - self.available_profiles.add_item(title, (title, r.text), replace=False) - except ValueError: - if question_dialog(self, _('Replace recipe?'), - _('A custom recipe named %s already exists. Do you ' - 'want to replace it?')%title): - self.available_profiles.add_item(title, (title, r.text), replace=True) - else: - return - self.clear() - break + profile = get_builtin_recipe_by_title(title) + if self._model.has_title(title): + if question_dialog(self, _('Replace recipe?'), + _('A custom recipe named %s already exists. Do you want to ' + 'replace it?')%title): + self._model.replace_by_title(title, profile) + else: + return + else: + self.model.add(title, profile) + + self.clear() def load(self): - files = choose_files(self, 'recipe loader dialog', _('Choose a recipe file'), filters=[(_('Recipes'), '*.py')], all_files=False, select_only_single_file=True) + files = choose_files(self, 'recipe loader dialog', + _('Choose a recipe file'), + filters=[(_('Recipes'), ['.py', '.recipe'])], + all_files=False, select_only_single_file=True) if files: file = files[0] try: - src = open(file, 'rb').read().decode('utf-8') - title = compile_recipe(src).title + profile = open(file, 'rb').read().decode('utf-8') + title = compile_recipe(profile).title except Exception, err: error_dialog(self, _('Invalid input'), _('

Could not create recipe. Error:
%s')%str(err)).exec_() return - try: - self.available_profiles.add_item(title, (title, src), replace=False) - except ValueError: + if self._model.has_title(title): if question_dialog(self, _('Replace recipe?'), _('A custom recipe named %s already exists. Do you want to ' 'replace it?')%title): - self.available_profiles.add_item(title, (title, src), replace=True) + self._model.replace_by_title(title, profile) else: return + else: + self.model.add(title, profile) self.clear() def populate_options(self, profile): @@ -256,8 +313,11 @@ class %(classname)s(%(base_class)s): self.populate_options(AutomaticNewsRecipe) self.source_code.setText('') - def profiles(self): - for i in self.available_profiles.items(): - yield i.user_data - +if __name__ == '__main__': + from PyQt4.Qt import QApplication + QApplication([]) + from calibre.library.database2 import LibraryDatabase2 + from calibre.web.feeds.recipes.model import RecipeModel + d=UserProfiles(None, RecipeModel(LibraryDatabase2('/home/kovid/documents/library'))) + d.exec_() diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui index 64b6d10123..48f92a108c 100644 --- a/src/calibre/gui2/dialogs/user_profiles.ui +++ b/src/calibre/gui2/dialogs/user_profiles.ui @@ -34,8 +34,8 @@ 0 0 - 720 - 586 + 730 + 600 @@ -46,7 +46,7 @@ - 680 + 580 550 @@ -58,7 +58,7 @@ - 0 + 1 0 @@ -67,14 +67,7 @@ - - - - 0 - 0 - - - + @@ -136,6 +129,12 @@ + + + 10 + 0 + + QFrame::StyledPanel diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index cc3fcb3f7d..147c2780c5 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -222,8 +222,8 @@ class BooksModel(QAbstractTableModel): self.count_changed() return ret - def add_news(self, path, recipe): - ret = self.db.add_news(path, recipe) + def add_news(self, path, arg): + ret = self.db.add_news(path, arg) self.count_changed() return ret diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 06e7749fad..1e06f2eb0c 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -518,10 +518,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.test_server_timer = QTimer.singleShot(10000, self.test_server) - self.scheduler = Scheduler(self) + self.scheduler = Scheduler(self, self.library_view.model().db) self.action_news.setMenu(self.scheduler.news_menu) self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog) + self.connect(self.scheduler, SIGNAL('delete_old_news(PyQt_PyObject)'), + self.library_view.model().delete_books_by_id, + Qt.QueuedConnection) + self.connect(self.scheduler, + SIGNAL('start_recipe_fetch(PyQt_PyObject)'), + self.download_scheduled_recipe, Qt.QueuedConnection) + self.location_view.setCurrentIndex(self.location_view.model().index(0)) def resizeEvent(self, ev): @@ -1155,27 +1162,27 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################### Fetch news ################################# - def download_scheduled_recipe(self, recipe, script, callback): + def download_scheduled_recipe(self, arg): func, args, desc, fmt, temp_files = \ - fetch_scheduled_recipe(recipe, script) + fetch_scheduled_recipe(arg) job = self.job_manager.run_job( Dispatcher(self.scheduled_recipe_fetched), func, args=args, description=desc) - self.conversion_jobs[job] = (temp_files, fmt, recipe, callback) - self.status_bar.showMessage(_('Fetching news from ')+recipe.title, 2000) + self.conversion_jobs[job] = (temp_files, fmt, arg) + self.status_bar.showMessage(_('Fetching news from ')+arg['title'], 2000) def scheduled_recipe_fetched(self, job): - temp_files, fmt, recipe, callback = self.conversion_jobs.pop(job) + temp_files, fmt, arg = self.conversion_jobs.pop(job) pt = temp_files[0] if job.failed: return self.job_exception(job) - id = self.library_view.model().add_news(pt.name, recipe) + id = self.library_view.model().add_news(pt.name, arg) self.library_view.model().reset() sync = dynamic.get('news_to_be_synced', set([])) sync.add(id) dynamic.set('news_to_be_synced', sync) - callback(recipe) - self.status_bar.showMessage(recipe.title + _(' fetched.'), 3000) + self.scheduler.recipe_downloaded(arg) + self.status_bar.showMessage(arg['title'] + _(' fetched.'), 3000) self.email_news(id) self.sync_news() diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 525efec25b..f6431581b5 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -207,8 +207,7 @@ class QueueBulk(QProgressDialog): self.jobs.reverse() self.queue(self.jobs, self.changed, self.bad, *self.args) -def fetch_scheduled_recipe(recipe, script): - from calibre.gui2.dialogs.scheduler import config +def fetch_scheduled_recipe(arg): from calibre.ebooks.conversion.config import load_defaults fmt = prefs['output_format'].lower() pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) @@ -228,16 +227,14 @@ def fetch_scheduled_recipe(recipe, script): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) - args = [script, pt.name, recs] - if recipe.needs_subscription: - x = config.get('recipe_account_info_%s'%recipe.id, False) - if not x: - raise ValueError(_('You must set a username and password for %s')%recipe.title) - recs.append(('username', x[0], OptionRecommendation.HIGH)) - recs.append(('password', x[1], OptionRecommendation.HIGH)) + args = [arg['recipe'], pt.name, recs] + if arg['username'] is not None: + recs.append(('username', arg['username'], OptionRecommendation.HIGH)) + if arg['password'] is not None: + recs.append(('password', arg['password'], OptionRecommendation.HIGH)) - return 'gui_convert', args, _('Fetch news from ')+recipe.title, fmt.upper(), [pt] + return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] def convert_existing(parent, db, book_ids, output_format): already_converted_ids = [] diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index e04df68949..cfd2213eed 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -8,7 +8,6 @@ import datetime, re, cPickle, sre_constants from zlib import compress, decompress from calibre.ebooks.metadata import MetaInformation -from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe from calibre.ebooks.metadata import string_to_authors class Concatenate(object): @@ -739,10 +738,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; @staticmethod def upgrade_version8(conn): - feeds = conn.execute('SELECT title, script FROM feeds').fetchall() - for title, script in feeds: - script = migrate_automatic_profile_to_automatic_recipe(script) - conn.execute('UPDATE feeds SET script=? WHERE title=?', (script, title)) + conn.execute('DELETE FROM feeds') conn.execute('pragma user_version=9') conn.commit() @@ -1363,6 +1359,26 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; for title, script in feeds: yield title, script + def get_feed(self, id): + return self.conn.get('SELECT script FROM feeds WHERE id=%d'%id, + all=False) + + def update_feed(self, id, script, title): + self.conn.execute('UPDATE feeds set title=? WHERE id=?', (title, id)) + self.conn.execute('UPDATE feeds set script=? WHERE id=?', (script, id)) + self.conn.commit() + + def remove_feeds(self, ids): + for x in ids: + self.conn.execute('DELETE FROM feeds WHERE id=?', (x,)) + self.conn.commit() + + def add_feed(self, title, script): + self.conn.execute('INSERT INTO feeds(title, script) VALUES (?, ?)', + (title, script)) + self.conn.commit() + + def set_feeds(self, feeds): self.conn.execute('DELETE FROM feeds') for title, script in feeds: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 44685aa8aa..6cdcd631bc 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1323,14 +1323,18 @@ class LibraryDatabase2(LibraryDatabase): if notify: self.notify('metadata', [id]) - def add_news(self, path, recipe): + def add_news(self, path, arg): format = os.path.splitext(path)[1][1:].lower() stream = path if hasattr(path, 'read') else open(path, 'rb') stream.seek(0) mi = get_metadata(stream, format, use_libprs_metadata=False) stream.seek(0) mi.series_index = 1.0 - mi.tags = [_('News'), recipe.title] + mi.tags = [_('News')] + if arg['add_title_tag']: + mi.tags += [arg['title']] + if arg['custom_tags']: + mi.tags += arg['custom_tags'] obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', (mi.title, mi.authors[0])) id = obj.lastrowid @@ -1696,6 +1700,11 @@ books_series_link feeds return duplicates + def get_custom_recipes(self): + for id, title, script in self.conn.get('SELECT id,title,script FROM feeds'): + yield id, title, script + + def check_integrity(self, callback): callback(0., _('Checking SQL integrity...')) user_version = self.user_version diff --git a/src/calibre/web/feeds/input.py b/src/calibre/web/feeds/input.py index 39f7be154f..adad69f885 100644 --- a/src/calibre/web/feeds/input.py +++ b/src/calibre/web/feeds/input.py @@ -43,14 +43,15 @@ class RecipeInput(InputFormatPlugin): def convert(self, recipe_or_file, opts, file_ext, log, accelerators): - from calibre.web.feeds.recipes import \ - get_builtin_recipe, compile_recipe + from calibre.web.feeds.recipes import compile_recipe + from calibre.web.feeds.recipes.collection import \ + get_builtin_recipe_by_title if os.access(recipe_or_file, os.R_OK): recipe = compile_recipe(open(recipe_or_file, 'rb').read()) else: title = getattr(opts, 'original_recipe_input_arg', recipe_or_file) title = os.path.basename(title).rpartition('.')[0] - recipe = get_builtin_recipe(title) + recipe = compile_recipe(get_builtin_recipe_by_title(title, log)) if recipe is None: raise ValueError('%r is not a valid recipe file or builtin recipe' % diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index fe8ae80997..fb83fa04b3 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -4,95 +4,15 @@ __copyright__ = '2008, Kovid Goyal ' ''' Builtin recipes. ''' -recipe_modules = ['recipe_' + r for r in ( - 'newsweek', 'atlantic', 'economist', 'portfolio', 'the_register', - 'usatoday', 'bbc', 'greader', 'wsj', #'outlook_india', - 'wired', 'globe_and_mail', 'smh', 'espn', 'business_week', 'miami_herald', - 'ars_technica', 'upi', 'new_yorker', 'irish_times', 'lanacion', - 'discover_magazine', 'scientific_american', 'new_york_review_of_books', - 'daily_telegraph', 'guardian', 'el_pais', 'new_scientist', 'b92', - 'politika', 'moscow_times', 'latimes', 'japan_times', 'san_fran_chronicle', - 'demorgen_be', 'de_standaard', 'ap', 'barrons', 'chr_mon', 'cnn', 'faznet', - 'jpost', 'jutarnji', 'nasa', 'reuters', 'spiegelde', 'wash_post', 'zeitde', - 'blic', 'novosti', 'danas', 'vreme', 'times_online', 'the_scotsman', - 'nytimes_sub', 'nytimes', 'security_watch', 'cyberpresse', 'st_petersburg_times', - 'clarin', 'financial_times', 'heise', 'le_monde', 'harpers', 'science_aas', - 'science_news', 'the_nation', 'lrb', 'harpers_full', 'liberation', - 'linux_magazine', 'telegraph_uk', 'utne', 'sciencedaily', 'forbes', - 'time_magazine', 'endgadget', 'fudzilla', 'nspm_int', 'nspm', 'pescanik', - 'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet', - 'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de', - 'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age', - 'laprensa', 'amspec', 'freakonomics', 'criticadigital', 'elcronista', - 'shacknews', 'teleread', 'granma', 'juventudrebelde', 'juventudrebelde_english', - 'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda', - 'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz', - 'honoluluadvertiser', 'starbulletin', 'exiled', 'indy_star', 'dna', - 'pobjeda', 'chicago_breaking_news', 'glasgow_herald', 'linuxdevices', - 'hindu', 'cincinnati_enquirer', 'physics_world', 'pressonline', - 'la_republica', 'physics_today', 'chicago_tribune', 'e_novine', - 'al_jazeera', 'winsupersite', 'borba', 'courrierinternational', - 'lamujerdemivida', 'soldiers', 'theonion', 'news_times', - 'el_universal', 'mediapart', 'wikinews_en', 'ecogeek', 'daily_mail', - 'new_york_review_of_books_no_sub', 'politico', 'adventuregamers', - 'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list', - 'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs', - 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet', - 'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en', - 'moneynews', 'der_standard', 'diepresse', 'nzz_ger', 'hna', - 'seattle_times', 'scott_hanselman', 'coding_horror', 'twitchfilms', - 'stackoverflow', 'telepolis_artikel', 'zaobao', 'usnews', - 'straitstimes', 'index_hu', 'pcworld_hu', 'hrt', 'rts', 'axxon_news', - 'h1', 'h2', 'h3', 'phd_comics', 'woz_die', 'elektrolese', - 'climate_progress', 'carta', 'slashdot', 'publico', - 'the_budget_fashionista', 'elperiodico_catalan', - 'elperiodico_spanish', 'expansion_spanish', 'lavanguardia', - 'marca', 'kellog_faculty', 'kellog_insight', 'noaa', - '7dias', 'buenosaireseconomico', 'huntechnet', 'cubadebate', - 'diagonales', 'miradasalsur', 'newsweek_argentina', 'veintitres', - 'gva_be', 'hln', 'tijd', 'degentenaar', 'inquirer_net', 'uncrate', - 'fastcompany', 'accountancyage', 'laprensa_hn', 'latribuna', - 'eltiempo_hn', 'slate', 'tnxm', 'bbcvietnamese', 'vnexpress', - 'volksrant', 'theeconomictimes_india', 'ourdailybread', - 'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti', - 'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga', - 'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem', - 'the_new_republic', 'philly', 'salon', 'tweakers', 'smashing', - 'thestar', 'business_standard', 'lemonde_dip', 'javalobby', - 'serverside', 'infoworld', 'sanjosemercurynews', 'businessworldin', - )] - - import re, imp, inspect, time, os from calibre.web.feeds.news import BasicNewsRecipe, CustomIndexRecipe, AutomaticNewsRecipe from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ptempfile import PersistentTemporaryDirectory from calibre import __appname__, english_sort +BeautifulSoup, time, english_sort + basic_recipes = (BasicNewsRecipe, AutomaticNewsRecipe, CustomIndexRecipe) -basic_recipe_names = (i.__name__ for i in basic_recipes) - - -#: Compile builtin recipe/profile classes -def load_recipe(module, package='calibre.web.feeds.recipes'): - module = __import__(package+'.'+module, fromlist=['']) - for attr in dir(module): - obj = getattr(module, attr) - if type(obj) is not type: - continue - recipe = False - for b in obj.__bases__: - if b in basic_recipes: - recipe = True - break - if not recipe: - continue - if obj not in basic_recipes: - return obj - - -recipes = [load_recipe(i) for i in recipe_modules] - _tdir = None _crep = 0 def compile_recipe(src): @@ -133,40 +53,3 @@ def compile_recipe(src): return classes[0][1] -def get_builtin_recipe(title): - ''' - Return a builtin recipe/profile class whose title == C{title} or None if no such - recipe exists. - - @type title: string - @rtype: class or None - ''' - for r in recipes: - if r.title == title: - return r - return None - -_titles = [r.title for r in recipes] -_titles.sort(cmp=english_sort) -titles = _titles - -def migrate_automatic_profile_to_automatic_recipe(profile): - BeautifulSoup - oprofile = profile - profile = compile_recipe(profile) - if 'BasicUserProfile' not in profile.__name__: - return oprofile - return '''\ -class BasicUserRecipe%d(AutomaticNewsRecipe): - - title = %s - oldest_article = %d - max_articles_per_feed = %d - summary_length = %d - - feeds = %s - -'''%(int(time.time()), repr(profile.title), profile.oldest_article, - profile.max_articles_per_feed, profile.summary_length, repr(profile.feeds)) - - diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py new file mode 100644 index 0000000000..852325f706 --- /dev/null +++ b/src/calibre/web/feeds/recipes/collection.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, calendar +from threading import RLock +from datetime import datetime, timedelta + +from lxml import etree +from lxml.builder import ElementMaker +from dateutil import parser + +NS = 'http://calibre-ebook.com/recipe_collection' +E = ElementMaker(namespace=NS, nsmap={None:NS}) + +def iterate_over_builtin_recipe_files(): + exclude = ['craigslist', 'iht', 'le_temps', 'outlook_india', 'toronto_sun'] + d = os.path.dirname + base = os.path.join(d(d(d(d(d(d(os.path.abspath(__file__))))))), 'resources', 'recipes') + for x in os.walk(base): + for f in x[-1]: + fbase, ext = os.path.splitext(f) + if ext != '.recipe' or fbase in exclude: + continue + f = os.path.join(x[0], f) + rid = os.path.splitext(os.path.relpath(f, base).replace(os.sep, + '/'))[0] + yield rid, os.path.join(x[0], f) + + +def serialize_recipe(urn, recipe_class): + + def attr(n, d): + ans = getattr(recipe_class, n, d) + if isinstance(ans, str): + ans = ans.decode('utf-8', 'replace') + return ans + + default_author = _('You') if urn.startswith('custom:') else _('Unknown') + return E.recipe({ + 'id' : str(urn), + 'title' : attr('title', _('Unknown')), + 'author' : attr('__author__', default_author), + 'language' : attr('language', 'und'), + 'needs_subscription' : 'yes' if attr('needs_subscription', False) else 'no', + 'description' : attr('description', '') + }) + +def serialize_collection(mapping_of_recipe_classes): + collection = E.recipe_collection() + for urn, recipe_class in mapping_of_recipe_classes.items(): + recipe = serialize_recipe(urn, recipe_class) + collection.append(recipe) + collection.set('count', str(len(collection))) + return etree.tostring(collection, encoding='utf-8', xml_declaration=True, + pretty_print=True) + +def serialize_builtin_recipes(): + from calibre.web.feeds.recipes import compile_recipe + recipe_mapping = {} + for rid, f in iterate_over_builtin_recipe_files(): + recipe_class = compile_recipe(open(f, 'rb').read()) + if recipe_class is not None: + recipe_mapping['builtin:'+rid] = recipe_class + + return serialize_collection(recipe_mapping) + +def get_builtin_recipe_collection(): + return etree.parse(P('builtin_recipes.xml')).getroot() + +def get_custom_recipe_collection(db): + from calibre.web.feeds.recipes import compile_recipe + rmap = {} + for id, title, recipe in db.get_custom_recipes(): + try: + recipe_class = compile_recipe(recipe) + if recipe_class is not None: + rmap['custom:%d'%id] = recipe_class + except: + continue + + return etree.fromstring(serialize_collection(rmap)) + +def get_builtin_recipe_titles(): + return [r.get('title') for r in get_builtin_recipe_collection()] + +def get_builtin_recipe_by_title(title, log=None): + for x in get_builtin_recipe_collection(): + if x.get('title') == title: + urn = x.get('id')[8:] + return P('recipes/%s.recipe'%urn, data=True) + +class SchedulerConfig(object): + + def __init__(self): + from calibre.utils.config import config_dir + from calibre.utils.lock import ExclusiveFile + self.conf_path = os.path.join(config_dir, 'scheduler.xml') + old_conf_path = os.path.join(config_dir, 'scheduler.pickle') + self.root = E.recipe_collection() + self.lock = RLock() + if os.access(self.conf_path, os.R_OK): + with ExclusiveFile(self.conf_path) as f: + self.root = etree.fromstring(f.read()) + elif os.path.exists(old_conf_path): + self.migrate_old_conf(old_conf_path) + + def iter_recipes(self): + for x in self.root: + if x.tag == '{%s}scheduled_recipe'%NS: + yield x + + def iter_accounts(self): + for x in self.root: + if x.tag == '{%s}account_info'%NS: + yield x + + def iter_customization(self): + for x in self.root: + if x.tag == '{%s}recipe_customization'%NS: + yield x + + def schedule_recipe(self, recipe, schedule_type, schedule, last_downloaded=None): + with self.lock: + for x in list(self.iter_recipes()): + if x.get('id', False) == recipe.get('id'): + ld = x.get('last_downloaded', None) + if ld and last_downloaded is None: + try: + last_downloaded = parser.parse(ld) + except: + pass + self.root.remove(x) + break + if last_downloaded is None: + last_downloaded = datetime.fromordinal(1) + sr = E.scheduled_recipe({ + 'id' : recipe.get('id'), + 'title': recipe.get('title'), + 'last_downloaded':last_downloaded.isoformat(), + }, self.serialize_schedule(schedule_type, schedule)) + self.root.append(sr) + self.write_scheduler_file() + + def customize_recipe(self, urn, add_title_tag, custom_tags): + with self.lock: + for x in list(self.iter_customization()): + if x.get('id') == urn: + self.root.remove(x) + cs = E.recipe_customization({ + 'id' : urn, + 'add_title_tag' : 'yes' if add_title_tag else 'no', + 'custom_tags' : ','.join(custom_tags), + }) + self.root.append(cs) + self.write_scheduler_file() + + def un_schedule_recipe(self, recipe_id): + with self.lock: + for x in list(self.iter_recipes()): + if x.get('id', False) == recipe_id: + self.root.remove(x) + break + self.write_scheduler_file() + + def update_last_downloaded(self, recipe_id): + with self.lock: + now = datetime.utcnow() + for x in self.iter_recipes(): + if x.get('id', False) == recipe_id: + typ, sch, last_downloaded = self.un_serialize_schedule(x) + if typ == 'interval': + actual_interval = now - last_downloaded + nominal_interval = timedelta(days=sch) + if abs(actual_interval - nominal_interval) < \ + timedelta(hours=1): + now = last_downloaded + nominal_interval + x.set('last_downloaded', now.isoformat()) + break + self.write_scheduler_file() + + def get_to_be_downloaded_recipes(self): + ans = [] + with self.lock: + for recipe in self.iter_recipes(): + if self.recipe_needs_to_be_downloaded(recipe): + ans.append(recipe.get('id')) + return ans + + def write_scheduler_file(self): + from calibre.utils.lock import ExclusiveFile + with ExclusiveFile(self.conf_path) as f: + f.seek(0) + f.truncate() + f.write(etree.tostring(self.root, encoding='utf-8', + xml_declaration=True, pretty_print=True)) + + def serialize_schedule(self, typ, schedule): + s = E.schedule({'type':typ}) + if typ == 'interval': + if schedule < 0.1: + schedule = 1/24. + text = '%f'%schedule + elif typ == 'day/time': + text = '%d:%d:%d'%schedule + s.text = text + return s + + def un_serialize_schedule(self, recipe): + for x in recipe.iterdescendants(): + if 'schedule' in x.tag: + sch, typ = x.text, x.get('type') + if typ == 'interval': + sch = float(sch) + elif typ == 'day/time': + sch = list(map(int, sch.split(':'))) + return typ, sch, parser.parse(recipe.get('last_downloaded')) + + def recipe_needs_to_be_downloaded(self, recipe): + try: + typ, sch, ld = self.un_serialize_schedule(recipe) + except: + return False + if typ == 'interval': + return datetime.utcnow() - ld > timedelta(sch) + elif typ == 'day/time': + day, hour, minute = sch + now = datetime.now() + is_today = day < 0 or day > 6 or \ + day == calendar.weekday(now.year, now.month, now.day) + return is_today and datetime.utcnow().date() != ld.date() and \ + now.hour >= hour and now.minute >= minute + return False + + def set_account_info(self, urn, un, pw): + with self.lock: + for x in list(self.iter_accounts()): + if x.get('id', False) == urn: + self.root.remove(x) + break + ac = E.account_info({'id':urn, 'username':un, 'password':pw}) + self.root.append(ac) + self.write_scheduler_file() + + def get_account_info(self, urn): + with self.lock: + for x in self.iter_accounts(): + if x.get('id', False) == urn: + return x.get('username', ''), x.get('password', '') + + def get_customize_info(self, urn): + add_title_tag = True + custom_tags = [] + with self.lock: + for x in self.iter_customization(): + if x.get('id', False) == urn: + add_title_tag = x.get('add_title_tag', 'yes') == 'yes' + custom_tags = [i.strip() for i in x.get('custom_tags', + '').split(',')] + break + return add_title_tag, custom_tags + + def get_schedule_info(self, urn): + with self.lock: + for x in self.iter_recipes(): + if x.get('id', False) == urn: + ans = list(self.un_serialize_schedule(x)) + return ans + + def migrate_old_conf(self, old_conf_path): + from calibre.utils.config import DynamicConfig + c = DynamicConfig('scheduler') + for r in c.get('scheduled_recipes', []): + try: + self.add_old_recipe(r) + except: + continue + for k in c.keys(): + if k.startswith('recipe_account_info'): + try: + urn = k.replace('recipe_account_info_', '') + if urn.startswith('recipe_'): + urn = 'builtin:'+urn[7:] + else: + urn = 'custom:%d'%int(urn) + try: + username, password = c[k] + except: + username = password = '' + self.set_account_info(urn, unicode(username), + unicode(password)) + except: + continue + del c + self.write_scheduler_file() + try: + os.remove(old_conf_path) + except: + pass + + def add_old_recipe(self, r): + urn = None + if r['builtin'] and r['id'].startswith('recipe_'): + urn = 'builtin:'+r['id'][7:] + elif not r['builtin']: + try: + urn = 'custom:%d'%int(r['id']) + except: + return + schedule = r['schedule'] + typ = 'interval' + if schedule > 1e5: + typ = 'day/time' + raw = '%d'%int(schedule) + day = int(raw[0]) - 1 + hour = int(raw[2:4]) - 1 + minute = int(raw[-2:]) - 1 + if day >= 7: + day = -1 + schedule = [day, hour, minute] + recipe = {'id':urn, 'title':r['title']} + self.schedule_recipe(recipe, typ, schedule, + last_downloaded=r['last_downloaded']) + + + + diff --git a/src/calibre/web/feeds/recipes/model.py b/src/calibre/web/feeds/recipes/model.py new file mode 100644 index 0000000000..9efcd9e78e --- /dev/null +++ b/src/calibre/web/feeds/recipes/model.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, copy + +from PyQt4.Qt import QAbstractItemModel, QVariant, Qt, QColor, QFont, QIcon, \ + QModelIndex, SIGNAL + +from calibre.utils.search_query_parser import SearchQueryParser +from calibre.gui2 import NONE +from calibre.utils.localization import get_language +from calibre.web.feeds.recipes.collection import \ + get_builtin_recipe_collection, get_custom_recipe_collection, \ + SchedulerConfig +from calibre.utils.pyparsing import ParseException + +class NewsTreeItem(object): + + def __init__(self, builtin, custom, scheduler_config, parent=None): + self.builtin, self.custom = builtin, custom + self.scheduler_config = scheduler_config + self.parent = parent + if self.parent is not None: + self.parent.append(self) + self.children = [] + + def row(self): + if self.parent is not None: + return self.parent.children.index(self) + return 0 + + def append(self, child): + child.parent = self + self.children.append(child) + + def data(self, role): + return NONE + + def flags(self): + return Qt.ItemIsEnabled|Qt.ItemIsSelectable + + def sort(self): + self.children.sort() + for child in self.children: + child.sort() + + def prune(self): + for child in list(self.children): + if len(child.children) == 0: + self.children.remove(child) + child.parent = None + +class NewsCategory(NewsTreeItem): + + def __init__(self, category, builtin, custom, scheduler_config, parent): + NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent) + self.category = category + self.cdata = get_language(self.category) + self.bold_font = QFont() + self.bold_font.setBold(True) + self.bold_font = QVariant(self.bold_font) + + def data(self, role): + if role == Qt.DisplayRole: + return QVariant(self.cdata + ' [%d]'%len(self.children)) + elif role == Qt.FontRole: + return self.bold_font + elif role == Qt.ForegroundRole and self.category == _('Scheduled'): + return QVariant(QColor(0, 255, 0)) + return NONE + + def flags(self): + return Qt.ItemIsEnabled + + def __cmp__(self, other): + def decorate(x): + if x == _('Scheduled'): + x = '0' + x + elif x == _('Custom'): + x = '1' + x + else: + x = '2' + x + return x + + return cmp(decorate(self.cdata), decorate(getattr(other, 'cdata', ''))) + + +class NewsItem(NewsTreeItem): + + def __init__(self, urn, title, default_icon, custom_icon, + builtin, custom, scheduler_config, parent): + NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent) + self.urn, self.title = urn, title + if 'custom:' in self.urn: + self.icon = custom_icon + else: + icon = I('news/%s.png'%self.urn[8:]) + if os.path.exists(icon): + self.icon = QVariant(QIcon(icon)) + else: + self.icon = default_icon + + def data(self, role): + if role == Qt.DisplayRole: + return QVariant(self.title) + if role == Qt.DecorationRole: + return self.icon + return NONE + + def __cmp__(self, other): + return cmp(self.title, getattr(other, 'title', '')) + +class RecipeModel(QAbstractItemModel, SearchQueryParser): + + LOCATIONS = ['all'] + + def __init__(self, db, *args): + QAbstractItemModel.__init__(self, *args) + SearchQueryParser.__init__(self) + self.db = db + self.default_icon = QVariant(QIcon(I('news.svg'))) + self.custom_icon = QVariant(QIcon(I('user_profile.svg'))) + self.builtin_recipe_collection = get_builtin_recipe_collection() + self.scheduler_config = SchedulerConfig() + self.do_refresh() + + def get_recipe(self, urn): + coll = self.custom_recipe_collection if urn.startswith('custom:') \ + else self.builtin_recipe_collection + for recipe in coll: + if recipe.get('id', False) == urn: + if coll is self.builtin_recipe_collection: + return P('recipes/%s.recipe'%urn[8:], data=True) + return self.db.get_feed(int(urn[len('custom:'):])) + + def update_custom_recipe(self, urn, title, script): + self.db.update_feed(int(urn[len('custom:'):]), script, title) + self.custom_recipe_collection = get_custom_recipe_collection(self.db) + + def add_custom_recipe(self, title, script): + self.db.add_feed(title, script) + self.custom_recipe_collection = get_custom_recipe_collection(self.db) + + def remove_custom_recipes(self, urns): + ids = [int(x[len('custom:'):]) for x in urns] + self.db.remove_feeds(ids) + self.custom_recipe_collection = get_custom_recipe_collection(self.db) + + def do_refresh(self, restrict_to_urns=set([])): + self.custom_recipe_collection = get_custom_recipe_collection(self.db) + + def factory(cls, parent, *args): + args = list(args) + if cls is NewsItem: + args.extend([self.default_icon, self.custom_icon]) + args += [self.builtin_recipe_collection, + self.custom_recipe_collection, self.scheduler_config, + parent] + return cls(*args) + + def ok(urn): + return not restrict_to_urns or urn in restrict_to_urns + + new_root = factory(NewsTreeItem, None) + scheduled = factory(NewsCategory, new_root, _('Scheduled')) + custom = factory(NewsCategory, new_root, _('Custom')) + lang_map = {} + self.all_urns = set([]) + self.showing_count = 0 + for x in self.scheduler_config.iter_recipes(): + urn = x.get('id') + if ok(urn): + factory(NewsItem, scheduled, urn, x.get('title')) + for x in self.custom_recipe_collection: + urn = x.get('id') + self.all_urns.add(urn) + if ok(urn): + factory(NewsItem, custom, urn, x.get('title')) + self.showing_count += 1 + for x in self.builtin_recipe_collection: + urn = x.get('id') + self.all_urns.add(urn) + if ok(urn): + lang = x.get('language', 'und') + if lang not in lang_map: + lang_map[lang] = factory(NewsCategory, new_root, lang) + factory(NewsItem, lang_map[lang], urn, x.get('title')) + self.showing_count += 1 + new_root.prune() + new_root.sort() + self.root = new_root + self.reset() + + def recipe_from_urn(self, urn): + coll = self.custom_recipe_collection if 'custom:' in urn else \ + self.builtin_recipe_collection + for x in coll: + if x.get('id', None) == urn: + return copy.deepcopy(x) + + def schedule_info_from_urn(self, urn): + return self.scheduler_config.get_schedule_info(urn) + + def account_info_from_urn(self, urn): + return self.scheduler_config.get_account_info(urn) + + def universal_set(self): + return self.all_urns + + def get_customize_info(self, urn): + return self.scheduler_config.get_customize_info(urn) + + def get_matches(self, location, query): + query = query.strip().lower() + if not query: + return self.universal_set() + results = set([]) + for urn in self.universal_set(): + recipe = self.recipe_from_urn(urn) + if query in recipe.get('title', '').lower() or \ + query in recipe.get('description', '').lower(): + results.add(urn) + return results + + def search(self, query, refinement): + try: + results = self.parse(unicode(query)) + except ParseException: + results = [] + self.do_refresh(restrict_to_urns=results) + self.emit(SIGNAL('searched(PyQt_PyObject)'), True) + + def columnCount(self, parent): + return 1 + + def data(self, index, role): + if not index.isValid(): + return NONE + item = index.internalPointer() + return item.data(role) + + def headerData(self, *args): + return NONE + + def flags(self, index): + if not index.isValid(): + return Qt.ItemIsEnabled|Qt.ItemIsSelectable + item = index.internalPointer() + return item.flags() + + def resort(self): + self.do_refresh() + + def index(self, row, column, parent): + if not self.hasIndex(row, column, parent): + return QModelIndex() + + if not parent.isValid(): + parent_item = self.root + else: + parent_item = parent.internalPointer() + + try: + child_item = parent_item.children[row] + except IndexError: + return QModelIndex() + + ans = self.createIndex(row, column, child_item) + return ans + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + child_item = index.internalPointer() + parent_item = child_item.parent + + if parent_item is self.root or parent_item is None: + return QModelIndex() + + ans = self.createIndex(parent_item.row(), 0, parent_item) + return ans + + def rowCount(self, parent): + if parent.column() > 0: + return 0 + + if not parent.isValid(): + parent_item = self.root + else: + parent_item = parent.internalPointer() + + return len(parent_item.children) + + def update_recipe_schedule(self, urn, schedule_type, schedule, + add_title_tag=True, custom_tags=[]): + recipe = self.recipe_from_urn(urn) + self.scheduler_config.schedule_recipe(recipe, schedule_type, schedule, + add_title_tag=add_title_tag, custom_tags=custom_tags) + + def update_last_downloaded(self, urn): + self.scheduler_config.update_last_downloaded(urn) + + def set_account_info(self, urn, un, pw): + self.scheduler_config.set_account_info(urn, un, pw) + + def get_account_info(self, urn): + return self.scheduler_config.get_account_info(urn) + + def get_schedule_info(self, urn): + return self.scheduler_config.get_schedule_info(urn) + + def un_schedule_recipe(self, urn): + self.scheduler_config.un_schedule_recipe(urn) + + def schedule_recipe(self, urn, sched_type, schedule): + self.scheduler_config.schedule_recipe(self.recipe_from_urn(urn), + sched_type, schedule) + + def customize_recipe(self, urn, add_title_tag, custom_tags): + self.scheduler_config.customize_recipe(urn, add_title_tag, + custom_tags) + + def get_to_be_downloaded_recipes(self): + return self.scheduler_config.get_to_be_downloaded_recipes() + + def scheduled_urns(self): + ans = [] + with self.scheduler_config.lock: + for recipe in self.scheduler_config.iter_recipes(): + ans.append(recipe.get('id')) + return ans + + +