epodder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []