saber 0.0.7 → 1.0.0

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.
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