saber 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +4 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +18 -8
  5. data/Gemfile.lock +72 -33
  6. data/Guardfile +4 -0
  7. data/README.md +192 -52
  8. data/Rakefile +46 -0
  9. data/bin/1saber +5 -3
  10. data/bin/saber +4 -0
  11. data/bin/saber.bib +3 -0
  12. data/bin/saber.what +3 -0
  13. data/extconf.rb +4 -1
  14. data/lib/saber.rb +7 -13
  15. data/lib/saber/autofetcher.rb +8 -0
  16. data/lib/saber/autofetcher/client.rb +66 -0
  17. data/lib/saber/autofetcher/server.rb +78 -0
  18. data/lib/saber/cli.rb +65 -26
  19. data/lib/saber/{downloader.rb → fetcher.rb} +9 -11
  20. data/lib/saber/mechanize_ext.rb +136 -0
  21. data/lib/saber/rc.rb +3 -21
  22. data/lib/saber/task.rb +26 -11
  23. data/lib/saber/task/base.rb +18 -0
  24. data/lib/saber/task/clean.rb +18 -0
  25. data/lib/saber/task/generate.rb +38 -0
  26. data/lib/saber/task/make.rb +44 -0
  27. data/lib/saber/task/send.rb +21 -0
  28. data/lib/saber/task/upload.rb +37 -0
  29. data/lib/saber/tracker.rb +28 -0
  30. data/lib/saber/tracker/base.rb +115 -2
  31. data/lib/saber/tracker/bb.rb +244 -0
  32. data/lib/saber/tracker/bib.rb +225 -0
  33. data/lib/saber/tracker/ptp.rb +100 -0
  34. data/lib/saber/tracker/what.rb +55 -7
  35. data/lib/saber/ui.rb +65 -22
  36. data/lib/saber/version.rb +1 -1
  37. data/rutorrent/init.js +8 -9
  38. data/rutorrent/plugin.info +1 -1
  39. data/saber.gemspec +14 -10
  40. data/spec/data/_saber/.gitkeep +0 -0
  41. data/spec/saber/downloader_spec.rb +5 -2
  42. data/spec/saber/task_spec.rb +16 -0
  43. data/spec/saber/tracker/bib_spec.rb +24 -0
  44. data/spec/spec_helper.rb +25 -0
  45. data/templates/_saberrc +36 -0
  46. data/templates/bb/anime.yml +6 -0
  47. data/templates/bb/application.yml +7 -0
  48. data/templates/bb/audiobook.yml +12 -0
  49. data/templates/bb/comic.yml +7 -0
  50. data/templates/bb/documentary.yml +26 -0
  51. data/templates/bb/ebook.yml +8 -0
  52. data/templates/bb/elearning_video.yml +7 -0
  53. data/templates/bb/game_console.yml +6 -0
  54. data/templates/bb/game_pc.yml +6 -0
  55. data/templates/bb/magazine.yml +6 -0
  56. data/templates/bb/misc.yml +6 -0
  57. data/templates/bb/movie.yml +26 -0
  58. data/templates/bb/music.yml +21 -0
  59. data/templates/bb/tv.yml +6 -0
  60. data/templates/bib/application.yml +9 -0
  61. data/templates/bib/article.yml +18 -0
  62. data/templates/bib/audiobook.yml +17 -0
  63. data/templates/bib/comic.yml +22 -0
  64. data/templates/bib/ebook.yml +20 -0
  65. data/templates/bib/journal.yml +18 -0
  66. data/templates/bib/magazine.yml +18 -0
  67. data/templates/ptp/movie.yml +63 -0
  68. data/templates/ptp/movie_add.yml +35 -0
  69. data/templates/what/ebook.yml +6 -0
  70. data/templates/what/music.yml +2 -0
  71. data/templates/what/music_add.yml +2 -0
  72. metadata +182 -26
  73. data/doc/Development.md +0 -3
  74. data/lib/saber/client.rb +0 -58
  75. data/lib/saber/server.rb +0 -70
  76. data/saber.watchr +0 -23
@@ -0,0 +1,136 @@
1
+ class Mechanize::Form
2
+ # A Generic api to get value
3
+ # @see set
4
+ def get(type, criteial)
5
+ case type.to_sym
6
+ when :text, :hidden, :textarea, :keygen
7
+ (f=field(criteia)) ? f.value : nil
8
+ when :radiobutton
9
+ (f=radiobutton(criteia)) ? f.checked? : nil
10
+ when :checkbox
11
+ (f=checkbox(criteia)) ? f.checked? : nil
12
+ when :multi_select_list, :select_list
13
+ (f=field(criteia)) ? f.value : nil
14
+ when :multi_select_list_text, :select_list_text
15
+ (f=field(criteia)) ? f.text_value : nil
16
+ when :file_upload
17
+ (f=file_upload(criteia)) ? f.file_name : nil
18
+ else
19
+ raise ArgumentError, "the type argument is wrong -- #{type.insepect}"
20
+ end
21
+ end
22
+
23
+ # A Generic api to set value
24
+ #
25
+ # types: :text, :hidden, :textarea, :keygen, :radiobutton, :checkbox,
26
+ # :multi_select_list[_text] :select_list[_text] :file_upload
27
+ #
28
+ # @param type [Symbol,String]
29
+ # @param value [Object] use nil to pass the set.
30
+ #
31
+ # @example
32
+ #
33
+ # set :text, "foo"
34
+ # set :checkbox, true
35
+ # set :select_list, "1"
36
+ # set :select_list_text, "EPUB"
37
+ #
38
+ # @see get
39
+ def set(type, criteia, value)
40
+ return nil if value.nil?
41
+
42
+ case type.to_sym
43
+ when :text, :hidden, :textarea, :keygen
44
+ (f=field(criteia)) ? f.value = value : nil
45
+ when :radiobutton
46
+ (f=radiobutton(criteia)) ? f.check(value) : nil
47
+ when :checkbox
48
+ (f=checkbox(criteia)) ? f.check(value) : nil
49
+ when :multi_select_list, :select_list
50
+ (f=field(criteia)) ? f.value = value : nil
51
+ when :multi_select_list_text, :select_list_text
52
+ (f=field(criteia)) ? f.text_value = value : nil
53
+ when :file_upload
54
+ (f=file_upload(criteia)) ? f.file_name = value : nil
55
+ else
56
+ raise ArgumentError, "the type argument is wrong -- #{type.insepect}"
57
+ end
58
+ end
59
+ end
60
+
61
+ class Mechanize::Form::MultiSelectList
62
+ # Select no options
63
+ def select_none_with_tagen
64
+ @text_value = []
65
+ select_none_without_tagen
66
+ end
67
+ alias select_none_without_tagen select_none
68
+ alias select_none select_none_with_tagen
69
+
70
+ # Select all options
71
+ def select_all_with_tagen
72
+ @text_value = []
73
+ select_all_withoutout_tagen
74
+ end
75
+ alias select_all_without_tagen select_all
76
+ alias select_all select_all_with_tagen
77
+
78
+ def text_value
79
+ value = []
80
+ value.concat @text_value
81
+ value.concat selected_options.map { |o| o.text }
82
+ value
83
+ end
84
+
85
+ def text_value=(values)
86
+ select_none
87
+ [values].flatten.each do |value|
88
+ option = options.find { |o| o.text == value }
89
+ if option.nil?
90
+ @text_value.push(value)
91
+ else
92
+ option.select
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ class Mechanize::Form::SelectList
99
+ def text_value
100
+ value = super
101
+ if value.length > 0
102
+ value.last
103
+ elsif @options.length > 0
104
+ @options.first.value
105
+ else
106
+ nil
107
+ end
108
+ end
109
+
110
+ def text_value=(new)
111
+ if new != new.to_s and new.respond_to? :first
112
+ super([new.first])
113
+ else
114
+ super([new.to_s])
115
+ end
116
+ end
117
+ end
118
+
119
+ class Mechanize::Form::RadioButton
120
+ # check_with_tagen(nil)
121
+ # check_with_tagen(true)
122
+ # check_with_tagen(false)
123
+ def check_with_tagen(check=true)
124
+ if check.nil?
125
+ return
126
+ elsif check
127
+ check_without_tagen
128
+ else
129
+ uncheck
130
+ end
131
+ end
132
+
133
+ alias check_without_tagen check
134
+ alias check check_with_tagen
135
+ end
136
+
@@ -1,31 +1,13 @@
1
1
  scgi_server = "http://localhost/RPC2"
2
2
 
3
3
  p:
4
- download = Pa("~/download") # local download directory.
4
+ root = Pa.expand("../../..", __FILE__)
5
+ home = Pa("~/.saber")
6
+ homerc = Pa("~/.saberrc")
5
7
 
6
8
  aria2:
7
9
  rpc = "http://localhost:6800/rpc"
8
10
 
9
- label = "saber" # default auto-download label.
10
11
  port = 3014
11
12
  token = "641a16655dad688ab681c0279a4369b5"
12
13
  drb_uri = "druby://localhost:3015"
13
-
14
- server:
15
- download = Pa("/home/foo/download") # remote download directory.
16
- ftp = "ftp://seedbox/download" # download from "#{ftp}/<file>"
17
- host = "seedbox"
18
- user = "foo"
19
-
20
- xmpp:
21
- jid = "foo@jabber.org"
22
- password = "y"
23
- host = nil
24
- port = nil
25
-
26
- client:
27
- xmpp:
28
- jid = "bar@jabber.org"
29
- password = "y"
30
- host = nil
31
- port = nil
@@ -1,17 +1,32 @@
1
1
  module Saber
2
- class Task
3
- class << self
4
- def clean
5
- Retort::Service.configure do |c|
6
- c.url = Rc.scgi_server
7
- end
2
+ # Usage
3
+ #
4
+ # require "saber/task/make"
5
+ # Saber::Task["make"].invoke
6
+ #
7
+ # Define a new task
8
+ #
9
+ # class HelloWorld < Task::Base
10
+ # def invoke(*args, &blk)
11
+ # p args
12
+ # end
13
+ # end
14
+ #
15
+ # Task.hello_world(1, 2) -> [1, 2]
16
+ module Task
17
+ autoload :Base, "saber/task/base"
18
+ autoload :Clean, "saber/task/clean"
19
+ autoload :Make, "saber/task/make"
20
+ autoload :Upload, "saber/task/upload"
21
+
22
+ @@tasks = {}
8
23
 
9
- disk_files = Rc.p.download.ls2(:absolute => true)
10
- bt_files = Retort::Torrent.all.map{|t| Retort::Torrent.action("name", t.info_hash) }.map{|n| Rc.p.download.join2(n)}
24
+ # a list of all tasks
25
+ mattr_reader :tasks
11
26
 
12
- (disk_files - bt_files).each { |file|
13
- Pa.rm_r file, :verbose => true
14
- }
27
+ class << self
28
+ def [](name)
29
+ tasks[name]
15
30
  end
16
31
  end
17
32
  end
@@ -0,0 +1,18 @@
1
+ require "active_support/core_ext/string/inflections"
2
+
3
+ module Saber
4
+ module Task
5
+ class Base < Thor
6
+ class << self
7
+ def inherited(child)
8
+ Task.tasks[child.name.demodulize.underscore] = child
9
+ end
10
+
11
+ # invoke a task
12
+ def invoke(*args)
13
+ new.invoke(*args)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require "retort"
2
+ Retort::Service.configure { |c| c.url = Saber::Rc.scgi_server }
3
+
4
+ module Saber
5
+ module Task
6
+ class Clean < Base
7
+ desc "clean", "clean"
8
+ def clean
9
+ disk_files = Pa.ls2(Rc.p.download, absolute: true)
10
+ bt_files = Retort::Torrent.all.map{|t| Retort::Torrent.action("name", t.info_hash) }.map{|n| Pa.join2(Rc.p.download, n)}
11
+
12
+ (disk_files - bt_files).each { |file|
13
+ Pa.rm_r file, :verbose => true
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module Saber
2
+ module Task
3
+ # Generate meta data file.
4
+ #
5
+ # Usage
6
+ # -----
7
+ #
8
+ # Task["generate"].invoke(:generate, ["bib", "ebook", "Hello.epub", isbn])
9
+ # > generate Hello.epub.yml data file.
10
+ #
11
+ class Generate < Base
12
+ include Thor::Actions
13
+
14
+ source_root "#{Rc.p.root}/templates"
15
+
16
+ desc "generate", "generate"
17
+ def generate(tracker_name, type, filename, *args)
18
+ require "saber/tracker/#{tracker_name}" if !args.empty?
19
+ template_file = find_in_source_paths("#{tracker_name}/#{type}.yml")
20
+ dest = "#{filename}.yml"
21
+
22
+ if !args.empty? and Tracker[tracker_name].can_populate?(type)
23
+ populate = {}
24
+ data = YAML.load_file(template_file)
25
+
26
+ tracker = Tracker[tracker_name].new
27
+ tracker.login
28
+ populate = tracker.populate(type, *args)
29
+ data.merge!(populate)
30
+
31
+ create_file dest, YAML.dump(data)
32
+ else
33
+ copy_file template_file, dest
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ require "shellwords"
2
+ require "tagen/core/kernel/shell"
3
+
4
+ module Saber
5
+ module Task
6
+ #
7
+ # Usage
8
+ # -----
9
+ #
10
+ # Task["make"].invoke(:make, ["bib", "a.epub", "b.epub"])
11
+ # > create a.epub.torrent and send it to local and remote watch directory.
12
+ # > create b.epub.torrent ..
13
+ #
14
+ class Make < Base
15
+ desc "make", "make"
16
+ def make(tracker_name, *files)
17
+ Saber.ui.error! "You need set #{tracker_name}.announce_url in ~/.saberrc first" unless
18
+ Rc._has_key?("#{tracker_name}.announce_url")
19
+
20
+ files.each { |file|
21
+ torrent_file = "#{file}.torrent"
22
+
23
+ if Pa.exists?(torrent_file)
24
+ if options["force"]
25
+ Pa.rm torrent_file
26
+ else
27
+ Saber.ui.say "Skip make: #{file} (torrent alreay exists. use -f to overwrite it.)"
28
+ next
29
+ end
30
+ end
31
+
32
+ system "mktorrent -p -a #{Rc[tracker_name].announce_url} #{file.shellescape}", show_cmd: true
33
+ # cp tororent file to watch directory.
34
+ Pa.cp_f torrent_file, Rc.p.watch, show_cmd: true if Rc.p._has_key?(:watch)
35
+ if Rc.p._has_key?(:remote_watch)
36
+ require "saber/task/send"
37
+ Task["send"].invoke(:send, [torrent_file, Rc.p.remote_watch])
38
+ end
39
+ Saber.ui.say ""
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ require "shellwords"
2
+ require "tagen/core/kernel/shell"
3
+
4
+ module Saber
5
+ module Task
6
+ # send files to seedbox.
7
+ class Send < Base
8
+
9
+ desc "send1", "send"
10
+ # @overload send(*files, dest)
11
+ def send1(*args)
12
+ if args.length == 1 then
13
+ Saber.ui.error! "At least one src for send -- src: nil, dest: #{args[1].inspect}."
14
+ end
15
+
16
+ *files, dest = args
17
+ system "rsync -Phr #{files.shelljoin} #{Rc.server.user}@#{Rc.server.host}:#{dest}", show_cmd: true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Saber
2
+ module Task
3
+ # Usage
4
+ #
5
+ # Task["upload"].invoke(:upload, ["site", "Hello.epub"]) # make hello.epub.torrent
6
+ # Task["upload"].invoke(:upload, ["site", "Hello.epub.torrent"])
7
+ #
8
+ class Upload < Base
9
+ desc "upload", "upload"
10
+ # @param [String] tracker_name
11
+ # @param [String] file/torrent_file "foo" or "foo.torrent"
12
+ def upload(tracker_name, *torrent_files)
13
+ require "saber/tracker/#{tracker_name}"
14
+
15
+ torrent_files.map!{|v| Pa.add_ext2(v, ".torrent")}
16
+
17
+ ensure_torrent_file(tracker_name, *torrent_files)
18
+
19
+ tracker = Tracker[tracker_name].new
20
+ tracker.login
21
+ tracker.upload(*torrent_files)
22
+ end
23
+
24
+ private
25
+
26
+ def ensure_torrent_file(tracker_name, *torrent_files)
27
+ require "saber/task/make"
28
+
29
+ torrent_files.each { |torrent_file|
30
+ next if Pa.exists?(torrent_file)
31
+
32
+ Task["make"].invoke(:make, [tracker_name, Pa.delete_ext2(torrent_file, ".torrent")])
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ require "mechanize"
2
+ require "saber/mechanize_ext"
3
+ require "highline/import"
4
+ require "reverse_markdown"
5
+ require "json"
6
+
7
+ module Saber
8
+ # Usage
9
+ #
10
+ # bib = Tracker["bib"]
11
+ # bib.login
12
+ # bib.upload("Hello.epub.torrent")
13
+ #
14
+ module Tracker
15
+ autoload :Base, "saber/tracker/base"
16
+ autoload :What, "saber/tracker/what"
17
+ autoload :BIB, "saber/tracker/bib"
18
+
19
+ @@trackers = {}
20
+ mattr_reader :trackers
21
+
22
+ class << self
23
+ def [](name)
24
+ trackers[name]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,8 +1,121 @@
1
- require "httparty"
1
+ require "active_support/core_ext/string/inflections"
2
2
 
3
3
  module Saber
4
- class Tracker
4
+ module Tracker
5
5
  class Base
6
+ def self.inherited(child)
7
+ Tracker.trackers[child.name.demodulize.underscore] = child
8
+ end
9
+
10
+ # implement
11
+ @@POPULATE_TYPES = []
12
+
13
+ def self.can_populate?(type)
14
+ @@POPULATE_TYPES.include?(type.to_s)
15
+ end
16
+
17
+ # implement
18
+ @@BASE_URL = ""
19
+ @@LOGIN_CHECK_PATH = ""
20
+
21
+ attr_reader :agent
22
+ attr_reader :site_name
23
+
24
+ def initialize
25
+ @site_name = self.class.name.demodulize.underscore
26
+ @agent = Mechanize.new
27
+
28
+ @agent.get(@@BASE_URL)
29
+ end
30
+
31
+ def login
32
+ if login_with_cookie
33
+ return
34
+ end
35
+
36
+ login_with_username
37
+ end
38
+
39
+ def upload(*torrent_files)
40
+ files = torrent_files.map{|v| Pa.delete_ext(v, ".torrent")}
41
+
42
+ files.each {|file|
43
+ info = Optimism.require!("./#{file}.yml")
44
+
45
+ if do_upload(file, info)
46
+ Saber.ui.say "Upload Complete: #{file}"
47
+ else
48
+ Saber.ui.error "Upload Failed: #{file}"
49
+ end
50
+ }
51
+ end
52
+
53
+ # Return data by auto-fill functions provied by site.
54
+ #
55
+ # @example
56
+ #
57
+ # populate("ebook", isbn)
58
+ #
59
+ # @return [Hash]
60
+ def populate(type, *args)
61
+ meth = "populate_#{type}"
62
+
63
+ if respond_to?(meth) then
64
+ send meth, *args
65
+ else
66
+ raise ArgumentError, "Not support this type -- #{type}"
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ # Implement
73
+ #
74
+ # @return [Boolean] success?
75
+ def do_upload(file, info)
76
+ raise NotImplementedError
77
+ end
78
+
79
+ # Implement
80
+ #
81
+ # @return [Boolean] success?
82
+ def do_login_with_username(username)
83
+ raise NotImplementedError
84
+ end
85
+
86
+ def login_with_cookie
87
+ if Pa.exists?("#{Rc.p.home}/#{site_name}.cookies") then
88
+ open("#{Rc.p.home}/#{site_name}.cookies") { |io|
89
+ agent.cookie_jar.load_cookiestxt(io)
90
+ }
91
+
92
+ ret = agent.get(@@LOGIN_CHECK_PATH)
93
+
94
+ if ret.uri.path == @@LOGIN_CHECK_PATH
95
+ true
96
+ else
97
+ Saber.ui.say "Login with cookie failed."
98
+ false
99
+ end
100
+ end
101
+ end
102
+
103
+ def login_with_username
104
+ username = Rc._fetch(["#{site_name}.username", "username"], nil)
105
+
106
+ Saber.ui.say "Begin to login manually."
107
+ Saber.ui.say "Username: #{username}" if username
108
+ loop do
109
+ if do_login_with_username(username)
110
+ open("#{Rc.p.home}/#{site_name}.cookies", "w") { |f|
111
+ agent.cookie_jar.dump_cookiestxt(f)
112
+ }
113
+ return true
114
+ end
115
+ end
116
+ end
6
117
  end
7
118
  end
8
119
  end
120
+
121
+ # vim: fdn=4