epodder 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/epodder ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require "bundler/setup"
4
+ require_relative '../lib/epodder'
5
+
6
+ Epodder.run
data/lib/arguments.rb ADDED
@@ -0,0 +1,57 @@
1
+ module Epodder
2
+ class Arguments
3
+ attr_accessor :args
4
+
5
+ def initialize
6
+ @args = Hash.new
7
+ @args[:arguments] = []
8
+ verb = Struct.new(:name,:desc,:block)
9
+ @verbs = [
10
+ verb.new('add','Add a new podcast.', Proc.new {|args| @args[:action] = :add; @args[:arguments] = args}),
11
+ verb.new('catchup','Mark older episodes as downloaded.', Proc.new {|args| @args[:action] = :catchup; @args[:arguments] = args}),
12
+ verb.new('remove','Remove a feed by supplying an id.', Proc.new {|args| @args[:action] = :remove; @args[:arguments] = args}),
13
+ verb.new('lscasts','List podcasts.', Proc.new {|args| @args[:action] = :list_podcast; @args[:arguments] = args}),
14
+ verb.new('lseps','List episodes of a podcast by id.', Proc.new {|args| @args[:action] = :list_episodes; @args[:arguments] = args}),
15
+ verb.new('fetch','Update then download podcasts', Proc.new {|args| @args[:action] = :fetch; @args[:arguments] = args}),
16
+ verb.new('download','Download podcasts specified by a list of id or all podcasts', Proc.new {|args| @args[:action] = :download; @args[:arguments] = args}),
17
+ verb.new('update','Update podcasts specified by a list of id or all podcasts', Proc.new {|args| @args[:action] = :update; @args[:arguments] = args}),
18
+ verb.new("clean", "Remove old content from the database", Proc.new {|args| @args[:action] = :clean; @args[:arguments] = args})
19
+ ]
20
+ get_args
21
+ end
22
+
23
+ def get_args
24
+ @args[:path] = "~/.epodder"
25
+ cmd = CmdParse::CommandParser.new( true, true )
26
+ cmd.program_name = "ePodder"
27
+ cmd.program_version = [0, 0, 2]
28
+ cmd.options = CmdParse::OptionParserWrapper.new do |opt|
29
+ opt.separator "Global options:"
30
+ opt.on("-v", "--verbose", "Be verbose when outputting info") {|t| @args[:verbose] = true }
31
+ opt.on("-c", "--conf-dir [PATH]", "Set the configuration directory") {|path| @args[:path] = path}
32
+ opt.on("-l", "--log-path [PATH]", "Set logging to the specified file") {|path| @args[:log_file] = path}
33
+ end
34
+
35
+ cmd.add_command( CmdParse::HelpCommand.new )
36
+ cmd.add_command( CmdParse::VersionCommand.new)
37
+
38
+ @verbs.each do |verb|
39
+ command = CmdParse::Command.new(verb.name, false, false)
40
+ command.short_desc = verb.desc
41
+ command.set_execution_block(&verb.block)
42
+ cmd.add_command(command)
43
+ end
44
+
45
+ cmd.parse
46
+ end
47
+
48
+ def method_missing(name, value=nil, *args)
49
+ if @args.has_key? name
50
+ @args[name]
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,4 @@
1
+ module Epodder
2
+ module Configuration
3
+ end
4
+ end
@@ -0,0 +1,90 @@
1
+ module Epodder
2
+ class Configurator
3
+ @@default_path = "~/.epodder/"
4
+ @@db_path = "epodder.db"
5
+ @@yaml_path = "epodder.yaml"
6
+ @@default = {
7
+ :path_to_db => "epodder.db",
8
+ :path_to_download => "~/podcasts"
9
+ }
10
+
11
+ def initialize args
12
+ @args = args
13
+ load_working_dir! @args.path
14
+ load_config!
15
+ start_logging
16
+ load_download_dir!
17
+ load_database!
18
+ end
19
+
20
+
21
+ def load_config!
22
+ #Check to see if we have a config file or if we need to create it
23
+ if !File.exists? @@yaml_path
24
+ begin
25
+ File.open(@@yaml_path, "w") do |io|
26
+ YAML.dump(@@default, io)
27
+ end
28
+ rescue SystemCallError, NameError => error
29
+ puts "Could not load #{@@yaml_path}: #{error}"
30
+ exit
31
+ end
32
+ end
33
+ @conf = YAML.load_file(@@yaml_path)
34
+ end
35
+
36
+ private
37
+
38
+ def load_download_dir!
39
+ Dir.mkdir File.expand_path(@conf[:path_to_download]) unless Dir.exists?(File.expand_path(@conf[:path_to_download]))
40
+ File.symlink(File.expand_path(@conf[:path_to_download]), "download") unless File.exists? "download"
41
+ end
42
+
43
+ def load_database!
44
+ #Datamapper magic goes here
45
+ DataMapper.setup(:default, "sqlite://#{Dir.pwd}/#{@conf[:path_to_db]}")
46
+ DataMapper.finalize
47
+ DataMapper.auto_upgrade!
48
+ end
49
+
50
+ def load_working_dir! path
51
+ @path = File.expand_path(path.nil? ? @@default_path : path)
52
+ Dir.mkdir @path unless Dir.exists? @path
53
+ Dir.chdir @path unless file_error
54
+ end
55
+
56
+ def file_error
57
+ if !File.directory? @path
58
+ puts "#{@path} is not a directory"
59
+ exit
60
+ elsif !File.readable? @path
61
+ puts "Can not read #{@path}"
62
+ exit
63
+ elsif !File.writable? @path
64
+ puts "Can not write to #{@path}"
65
+ exit
66
+ end
67
+ false
68
+ end
69
+
70
+ def start_logging
71
+ logger = Yell.new :name => 'log' do |l|
72
+ if @args.log_file.nil?
73
+ if @args.verbose
74
+ l.adapter :stdout, "epodder.log", :level => Yell.level(:info)
75
+ else
76
+ l.adapter :stdout, "epodder.log", :level => Yell.level(:error)
77
+ end
78
+ else
79
+ if @args.verbose
80
+ l.adapter :datefile, "epodder.log", :level => Yell.level(:info)
81
+ else
82
+ l.adapter :datefile, "epodder.log", :level => Yell.level(:error)
83
+ end
84
+ end
85
+ end
86
+ logger.info "Loaded Logger"
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ require 'data_mapper'
2
+ module Epodder
3
+ class Episode
4
+ include DataMapper::Resource
5
+
6
+ property :id, Serial
7
+ property :title, String, :length=> 500
8
+ property :url, URI
9
+ property :downloaded, Boolean
10
+ property :pub_date, DateTime
11
+
12
+ belongs_to :podcast
13
+
14
+ def self.lookup episode
15
+ return if episode.enclosure.nil?
16
+ @episode = Episode.first_or_create(
17
+ :title => episode.title,
18
+ :url => episode.enclosure.url,
19
+ :pub_date => episode.pubdate,
20
+ :downloaded => false
21
+ )
22
+ end
23
+
24
+ def mark_as_downloaded
25
+ self.downloaded = true
26
+ success = self.save
27
+ if !success
28
+ self.errors.each do |e|
29
+ puts e
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ module Epodder
2
+ class Podcast
3
+ include DataMapper::Resource
4
+ property :id, Serial
5
+ property :title, String, :length=> 500
6
+ property :uri, URI
7
+ has n, :episodes, :constraint => :destroy
8
+
9
+ end
10
+ end
data/lib/eclass.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Epodder
2
+ class Eclass
3
+ def initialize
4
+ @log = Yell['log']
5
+ end
6
+ end
7
+ end
data/lib/epodder.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Epodder
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ require 'require_all'
5
+ require 'data_mapper'
6
+ require 'cmdparse'
7
+ require 'yell'
8
+
9
+ require_all File.dirname(File.dirname(__FILE__)) << '/lib'
10
+
11
+ @@verbose = false
12
+
13
+ def verbose?
14
+ @@verbose
15
+ end
16
+
17
+ def verbose= state
18
+ @@verbose = state
19
+ end
20
+
21
+ def self.do_verb verb, args
22
+ c = Epodder.const_get(verb.to_s.capitalize)
23
+ verb_object = c.send :new
24
+ verb_object.send verb, args
25
+ end
26
+
27
+ def self.run
28
+ args = Arguments.new
29
+ Configurator.new(args)
30
+ do_verb args.action, args.arguments
31
+ end
32
+
33
+ end
data/lib/verb/add.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Epodder
2
+ class Add < Verb
3
+ def initialize
4
+ @mechanize = Mechanize.new
5
+ end
6
+
7
+ def add args
8
+ args.each do |url|
9
+ lookup_podcast url
10
+ end
11
+ end
12
+
13
+ def lookup_podcast url
14
+ @mechanize.get(url) do |feed|
15
+ save_podcast feed, url
16
+ end
17
+ end
18
+
19
+ def save_podcast feed, url
20
+ cast = FeedMe.parse feed.body
21
+ cast.emulate_atom!
22
+ podcast = Podcast.first_or_create(
23
+ :title => cast.title,
24
+ :uri => url
25
+ )
26
+ podcast.save
27
+
28
+ podcast.errors.each do |error|
29
+ @log.error error
30
+ end
31
+ puts "#{podcast.id} - #{podcast.title}"
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ module Epodder
2
+ class Catchup < Verb
3
+ def initialize
4
+ end
5
+
6
+ def catchup args
7
+ if args.empty?
8
+ podcasts = Podcast.all
9
+ else
10
+ podcasts = args.map {|id| Podcast.get(id)}
11
+ end
12
+ podcasts.each do |podcast|
13
+ Episode.all(:downloaded => false, :podcast => podcast).update(:downloaded => true)
14
+ end
15
+ end
16
+ end
17
+
18
+ end
data/lib/verb/clean.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Epodder
2
+ class Clean < Verb
3
+ def initialize
4
+ @mechanize = @mechanize = Mechanize.new
5
+ end
6
+
7
+ def clean args
8
+ if args.empty?
9
+ podcasts = Podcast.all
10
+ else
11
+ podcasts = args.map{|podcast| Podcast.get(podcast)}
12
+ end
13
+ podcasts.each do |podcast|
14
+ count = 0
15
+ known_episodes = Episode.all(:downloaded => true, :podcast => podcast)
16
+ feed_episodes = open_podcast podcast, DateTime.now
17
+ known_episodes = known_episodes.map{|episode| episode.url.to_s}
18
+ feed_episodes = feed_episodes.map{|episode|@episode = episode; episode.enclosure.url}
19
+ episodes = (known_episodes - feed_episodes)
20
+ puts "know episodes was #{known_episodes.length - feed_episodes.length} longer than feed episodes"
21
+ episodes.map{|episode| Episode.all(:url => episode)}.each do |episode|
22
+ count += 1
23
+ win = episode.destroy
24
+ if !win
25
+ episode.errors.each {|error| puts error}
26
+ end
27
+ end
28
+ puts "#{podcast.title} -- cleaned #{count} episodes"
29
+ end
30
+ end
31
+
32
+ def open_podcast podcast, max_pub
33
+ begin
34
+ @mechanize.get(podcast.uri) do |feed|
35
+ return parse_feed feed, max_pub
36
+ end
37
+ rescue Exception => e
38
+ puts e
39
+ end
40
+ end
41
+
42
+ def parse_feed feed, max_pub
43
+ podcast = FeedMe.parse feed.body
44
+ podcast.emulate_atom!
45
+ temp = podcast.items.select {|item| !item.enclosure.nil?}
46
+ temp.select {|item| !item.enclosure.empty?}
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ require 'mechanize'
2
+ #require 'mechanize/progressbar'
3
+ #require 'progress'
4
+ module Epodder
5
+ class Download < Verb
6
+
7
+ def initialize
8
+ @mechanize = Mechanize.new
9
+ end
10
+
11
+ def download args
12
+ if args.empty?
13
+ podcasts = Podcast.all
14
+ else
15
+ podcasts = args.map {|id| Podcast.get(id)}
16
+ end
17
+ look_for_episodes podcasts
18
+ end
19
+
20
+ def look_for_episodes podcasts
21
+ podcasts.each do |podcast|
22
+ episodes = Episode.all(:downloaded => false, :podcast => podcast)
23
+ episodes.select{|ep| !ep.nil?}.each do |episode|
24
+ puts episode.podcast.title
25
+ title = (episode.podcast.title).strip
26
+ Dir.mkdir "download/#{title}" unless Dir.exists? "download/#{title}"
27
+ puts "Downloading #{title} - #{episode.title || episode.id}"
28
+ download_episode episode
29
+ end
30
+ end
31
+ end
32
+
33
+ def download_episode episode
34
+ begin
35
+ @mechanize.get(episode.url).save_as("download/#{episode.podcast.title.strip}/#{episode.url.to_s.match('((?!\/).)*$')}")
36
+ episode.mark_as_downloaded
37
+ rescue Exception => e
38
+ puts e
39
+ end
40
+ end
41
+
42
+ end
43
+ end
data/lib/verb/fetch.rb ADDED
@@ -0,0 +1,12 @@
1
+ module Epodder
2
+ class Fetch < Verb
3
+ def initialize
4
+ #Nothing to do in initalize
5
+ end
6
+
7
+ def fetch args
8
+ Update.new.update args
9
+ Download.new.download args
10
+ end
11
+ end
12
+ end
data/lib/verb/list.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Epodder
2
+ class List_podcast < Verb
3
+ def initialize
4
+ #Nothing to do in initalize
5
+ end
6
+
7
+ def list_podcast *args
8
+ Podcast.all.each do |pod|
9
+ puts "#{pod.id} : \"#{pod.title}\""
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'highline/import'
2
+ module Epodder
3
+ class Remove < Verb
4
+ def initialize
5
+
6
+ end
7
+
8
+ def remove *args
9
+ args.map{|id| Podcast.get(id)}.each do |podcast|
10
+ input = ask("Remove #{podcast.id} : #{podcast.title}? Type \"YES\" to remove")
11
+ if input == "YES"
12
+ podcast.destroy
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ require 'date'
2
+ require 'feedme'
3
+ require 'mechanize'
4
+ #require 'progress'
5
+ module Epodder
6
+ class Update < Verb
7
+
8
+ def initialize
9
+ super
10
+ @mechanize = Mechanize.new
11
+ end
12
+
13
+ def update args
14
+ if args.empty?
15
+ podcasts = Podcast.all
16
+ else
17
+ podcasts = args.map {|id| Podcast.get(id)}
18
+ end
19
+ check_for_new_episodes podcasts if podcasts.any?
20
+ end
21
+
22
+ def check_for_new_episodes podcasts
23
+ podcasts.each do |podcast|
24
+ @podcast = podcast
25
+ @podcast_urls = @podcast.episodes.map {|episode| episode.url}
26
+ max_pub = get_max_pubdate podcast
27
+ open_podcast podcast, max_pub
28
+ end
29
+ end
30
+
31
+ def get_max_pubdate podcast
32
+ Episode.max(:pub_date, :conditions => { :podcast => podcast}) || Time.at(0).to_date
33
+ end
34
+
35
+ def open_podcast podcast, max_pub
36
+ begin
37
+ @mechanize.get(podcast.uri) do |feed|
38
+ @log.info "Maximum pubdate for #{podcast.title} - #{podcast.id} is #{max_pub}"
39
+ parse_feed feed, max_pub
40
+ end
41
+ rescue Exception => e
42
+ puts e
43
+ end
44
+ end
45
+
46
+ def parse_feed feed, max_pub
47
+ podcast = FeedMe.parse feed.body
48
+ podcast.emulate_atom!
49
+ @count = 0
50
+ podcast.items.each do |item|
51
+ add_eligable_episodes item, max_pub
52
+ end
53
+ puts "#{@podcast.title} has #{@count} new episodes" if @count > 0
54
+ end
55
+
56
+ def add_eligable_episodes item, max_pub
57
+ if !item.enclosure.nil? && item.pubdate.to_date > max_pub.to_date
58
+ begin
59
+ @count += 1
60
+ ep = Episode.first_or_create(
61
+ :title => item.title,
62
+ :url => item.enclosure.url,
63
+ :pub_date => item.pubdate.to_date,
64
+ :downloaded => false,
65
+ :podcast => @podcast
66
+ )
67
+ ep.errors.each do |error|
68
+ @log.error error
69
+ end
70
+
71
+ rescue Exception => e
72
+ @log.error e
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+ end
data/lib/verb/verb.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Epodder
2
+ class Verb < Eclass
3
+ def verb_struct
4
+ super
5
+ Struct.new(:name,:description,:block)
6
+ end
7
+
8
+ def add_command (cmd, args)
9
+ command = CmdParse::Command.new(@verb.name,false,false)
10
+ command.short_desc = @verb.description
11
+ command.set_execution_block(&@verb.block)
12
+ cmd.add_command(command)
13
+ end
14
+
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epodder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eric Bergstrom
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-25 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: bandwidthoracle@gmail.com
16
+ executables:
17
+ - epodder
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/arguments.rb
22
+ - lib/configuration/configuration.rb
23
+ - lib/configuration/configurator.rb
24
+ - lib/database/episode.rb
25
+ - lib/database/podcast.rb
26
+ - lib/eclass.rb
27
+ - lib/epodder.rb
28
+ - lib/verb/add.rb
29
+ - lib/verb/catchup.rb
30
+ - lib/verb/clean.rb
31
+ - lib/verb/download.rb
32
+ - lib/verb/fetch.rb
33
+ - lib/verb/list.rb
34
+ - lib/verb/remove.rb
35
+ - lib/verb/update.rb
36
+ - lib/verb/verb.rb
37
+ - bin/epodder
38
+ homepage: http://github.com/scribe/epodder
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.25
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Ruby re-do of hpodder
62
+ test_files: []