rbitter 0.0.8 → 0.1.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -21
  3. data/.rspec +2 -2
  4. data/.travis.yml +15 -9
  5. data/Gemfile +12 -11
  6. data/LICENSE.txt +22 -22
  7. data/README.md +23 -16
  8. data/Rakefile +8 -8
  9. data/XMLRPC.md +19 -19
  10. data/bin/rbitter +20 -6
  11. data/lib/rbitter/arcserver.rb +126 -97
  12. data/lib/rbitter/console.rb +93 -61
  13. data/lib/rbitter/default/config_json.rb +38 -37
  14. data/lib/rbitter/dlthread.rb +66 -66
  15. data/lib/rbitter/env.rb +62 -59
  16. data/lib/rbitter/libtwitter_connection_override.rb +45 -45
  17. data/lib/rbitter/records.rb +121 -109
  18. data/lib/rbitter/records_migrate/20150327_add_index.rb +11 -7
  19. data/lib/rbitter/records_migrate/20150504_add_replyto_column.rb +12 -0
  20. data/lib/rbitter/streaming.rb +104 -75
  21. data/lib/rbitter/version.rb +20 -3
  22. data/lib/rbitter/xmlrpc.rb +3 -3
  23. data/lib/rbitter/xmlrpcd/base.rb +24 -24
  24. data/lib/rbitter/xmlrpcd/rpchandles.rb +11 -11
  25. data/lib/rbitter/xmlrpcd/xmlrpc_auth_server.rb +82 -83
  26. data/lib/rbitter/xmlrpcd/xmlrpcd.rb +69 -63
  27. data/lib/rbitter.rb +86 -92
  28. data/rbitter.gemspec +42 -42
  29. data/spec/config/default.json +32 -32
  30. data/spec/rbitter/arcserver_spec.rb +30 -9
  31. data/spec/rbitter/console_spec.rb +9 -9
  32. data/spec/rbitter/default/config_json_spec.rb +3 -3
  33. data/spec/rbitter/dlthread_spec.rb +13 -9
  34. data/spec/rbitter/env_spec.rb +62 -56
  35. data/spec/rbitter/libtwitter_connection_override_spec.rb +8 -8
  36. data/spec/rbitter/records_spec.rb +13 -13
  37. data/spec/rbitter/streaming_spec.rb +9 -9
  38. data/spec/rbitter/version_spec.rb +8 -8
  39. data/spec/rbitter/xmlrpc_spec.rb +8 -8
  40. data/spec/rbitter/xmlrpcd/base_spec.rb +29 -29
  41. data/spec/rbitter/xmlrpcd/rpchandles_spec.rb +10 -10
  42. data/spec/rbitter/xmlrpcd/xmlrpc_auth_server_spec.rb +8 -8
  43. data/spec/rbitter/xmlrpcd/xmlrpcd_spec.rb +9 -9
  44. data/spec/rbitter_spec.rb +42 -46
  45. data/spec/spec_helper.rb +39 -36
  46. metadata +68 -41
  47. data/config.json.example +0 -30
@@ -1,61 +1,93 @@
1
- # encoding: utf-8
2
- #
3
- # Rbitter Archive Access console (irb)
4
-
5
- require "xmlrpc/client"
6
- require "ripl"
7
-
8
- module Rbitter
9
- class Console
10
- def initialize
11
- puts "Welcome to Rbitter console"
12
- help
13
- end
14
-
15
- def help
16
- puts "Predefined methods:"
17
- puts "help - to show this message again"
18
- puts "xmlrpc - to utilize xmlrpc"
19
- puts "activerec - to connect and utilize Rbitter::Record"
20
- puts "^D, 'exit' - to get out from here."
21
- end
22
-
23
- def activerec
24
- ARSupport.connect_database
25
- puts "Rbitter::Record is ready."
26
- end
27
-
28
- def exit
29
- Kernel.exit(0)
30
- end
31
-
32
- def xmlrpc *args
33
- if args.empty?
34
- puts "How to use: xmlrpc (command) [params in Array]"
35
- puts "Ex) xmlrpc rbitter.echo [\"Hello World!\"]"
36
- puts "Ex) To call XMLRPC function with zero parameter, use nil."
37
- return false
38
- end
39
-
40
- cl = XMLRPC::Client.new('localhost', '/', 1400) # TODO: External address?
41
- if @xmlrpc_cookie.nil?
42
- @xmlrpc_cookie = "auth_key=" + cl.call('rbitter.auth', Rbitter.env['xmlrpc']['auth'][0], Rbitter.env['xmlrpc']['auth'][1])
43
- end
44
-
45
- if cl.cookie != "auth_key="
46
- if args.length <= 1 or args[1].nil?
47
- cl.call(args[0])
48
- else
49
- cl.call(args[0], *args[1])
50
- end
51
- else
52
- puts "Authentication failed. Check your config.json"
53
- end
54
- end
55
-
56
- def start
57
- Ripl.start :binding => binding
58
- end
59
- end
60
- end
61
-
1
+ # encoding: utf-8
2
+ #
3
+ # Rbitter Archive Access console (irb)
4
+
5
+ require "xmlrpc/client"
6
+ require "rbitter/version"
7
+ require "ripl"
8
+
9
+ module Rbitter
10
+ class Console
11
+ def initialize
12
+ puts "Rbitter console #{Rbitter::VERSION}"
13
+ help
14
+ end
15
+
16
+ def help
17
+ puts "Predefined methods:"
18
+ puts "ar - shortcut to call Rbitter::Record"
19
+ puts "connect_ar - Prepare Rbitter::Record to be ready"
20
+ puts "csv_backup - export Rbitter::Record into comma-separated values"
21
+ puts "help - to show this message again"
22
+ puts "xmlrpc - send xmlrpc command to destination"
23
+ puts "xmlrpc_dest - set destination for xmlrpc command"
24
+ puts "^D, 'exit' to exit from here"
25
+ end
26
+
27
+ def connect_ar
28
+ ARSupport.connect_database
29
+ puts "Rbitter::Record is ready."
30
+ end
31
+
32
+ def csv_backup *args
33
+ if args.length < 0
34
+ puts "Usage: csv_backup('filename.csv')"
35
+ puts "Estimated running time depends on system environment"
36
+ else
37
+ ARSupport.export_to_csv(args[0])
38
+ end
39
+ end
40
+
41
+ def ar
42
+ Rbitter::Record
43
+ end
44
+
45
+ def exit
46
+ Kernel.exit(0)
47
+ end
48
+
49
+ def xmlrpc_dest args={}
50
+ if args.empty?
51
+ puts "Usage: xmlrpc_dest({ :rpchost => '', :rpcpath => '', :rpcport => 1400,"
52
+ puts " :xmlrpc_auth_id => '', xmlrpc_auth_password => '' })"
53
+ end
54
+
55
+ @rpchost = args.fetch(:rpchost) { "127.0.0.1" }
56
+ @rpcpath = args.fetch(:rpcpath) { "/" }
57
+ @rpcport = args.fetch(:rpcport) { 1400 }
58
+
59
+ cl = XMLRPC::Client.new(@rpchost, @rpcpath, @rpcport)
60
+ @xmlrpc_cookie = "auth_key=" + cl.call('rbitter.auth',
61
+ args.fetch(:xmlrpc_auth_id) { Rbitter.env['xmlrpc']['auth'][0] },
62
+ args.fetch(:xmlrpc_auth_password) { Rbitter.env['xmlrpc']['auth'][1] } )
63
+
64
+ if @xmlrpc_cookie != "auth_key="
65
+ puts "Authentication completed"
66
+ else
67
+ puts "Authentication failed"
68
+ end
69
+ end
70
+
71
+ def xmlrpc *args
72
+ if args.empty?
73
+ puts "Usage: xmlrpc(command, [params in Array])"
74
+ puts "Ex) xmlrpc(\'rbitter.echo\',' [\"Hello World!\"])"
75
+ puts "Please configure XMLRPC destination with xmlrpc_dest method"
76
+ return false
77
+ end
78
+
79
+ cl = XMLRPC::Client.new(@rpchost, @rpcpath, @rpcport, @xmlrpc_cookie)
80
+
81
+ if args.length <= 1 or args[1].nil?
82
+ cl.call(args[0])
83
+ else
84
+ cl.call(args[0], *args[1])
85
+ end
86
+ end
87
+
88
+ def start
89
+ Ripl.start :binding => binding
90
+ end
91
+ end
92
+ end
93
+
@@ -1,37 +1,38 @@
1
- module Rbitter
2
- DEFAULT_CONFIG_JSON = <<-ENDOFJSON
3
- {
4
- "twitter": {
5
- "consumer_key": "",
6
- "consumer_secret": "",
7
- "access_token": "",
8
- "access_token_secret": ""
9
- },
10
- "activerecord": "sqlite3",
11
- "sqlite3": {
12
- "dbfile": "rbitter.sqlite"
13
- },
14
- "mysql2": {
15
- "host": "localhost",
16
- "port": 3306,
17
- "dbname": "archive",
18
- "username": "",
19
- "password": ""
20
- },
21
- "media_downloader": {
22
- "cacert_path": "/cacerts/cacert.pem",
23
- "download_dir": "imgs/"
24
- },
25
- "xmlrpc": {
26
- "enable": true,
27
- "bind_host": "0.0.0.0",
28
- "bind_port": 1400,
29
- "auth": {
30
- "username": "username",
31
- "password": "password"
32
- },
33
- "handles": ["/path/to/handles"]
34
- }
35
- }
36
- ENDOFJSON
37
- end
1
+ module Rbitter
2
+ DEFAULT_CONFIG_JSON = <<-ENDOFJSON
3
+ {
4
+ "twitter": {
5
+ "consumer_key": "",
6
+ "consumer_secret": "",
7
+ "access_token": "",
8
+ "access_token_secret": ""
9
+ },
10
+ "activerecord": "sqlite3",
11
+ "sqlite3": {
12
+ "dbfile": "rbitter.sqlite"
13
+ },
14
+ "mysql2": {
15
+ "host": "localhost",
16
+ "port": 3306,
17
+ "dbname": "archive",
18
+ "username": "",
19
+ "password": ""
20
+ },
21
+ "media_downloader": {
22
+ "large_image": true,
23
+ "cacert_path": "/cacerts/cacert.pem",
24
+ "download_dir": "imgs/"
25
+ },
26
+ "xmlrpc": {
27
+ "enable": true,
28
+ "bind_host": "0.0.0.0",
29
+ "bind_port": 1400,
30
+ "auth": {
31
+ "username": "username",
32
+ "password": "password"
33
+ },
34
+ "handles": ["/path/to/handles"]
35
+ }
36
+ }
37
+ ENDOFJSON
38
+ end
@@ -1,66 +1,66 @@
1
- # encoding: utf-8
2
-
3
- require "net/http"
4
- require "openssl"
5
-
6
- module Rbitter
7
- class DLThread
8
- def initialize(dlfolder, cacert_path)
9
- @dest = dlfolder
10
- if not File.directory?(dlfolder)
11
- warn "[dlthread] Given download location is not available for downloading."
12
- warn "[dlthread] Current Dir is used instead."
13
- @dest = "./"
14
- end
15
-
16
- @cacert = cacert_path
17
- end
18
-
19
- def execute_urls(urls)
20
- urls.each { |url|
21
- download_once(url)
22
- }
23
- end
24
-
25
- private
26
- def download_once(url)
27
- download_task = Thread.new {
28
- uri = URI.parse(url)
29
- http = Net::HTTP.new(uri.host, uri.port)
30
- if uri.scheme.downcase == 'https'
31
- http.use_ssl = true
32
- http.ca_path = @cacert_path
33
- # XXX: Fix this soon as possible.
34
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
35
- end
36
-
37
- http.request_get(uri.path) { |res|
38
- case res
39
- when Net::HTTPOK
40
- fname = File.basename(uri.path)
41
- if fname.nil? or fname.size < 1
42
- fname = uri.path.gsub(/\//, "_")
43
- end
44
- puts "[fetch] remote: #{url} => local: #{fname}"
45
- File.open(@dest+"/"+fname, "wb") { |file|
46
- res.read_body { |chunk|
47
- file.write(chunk)
48
- }
49
- }
50
- end
51
- }
52
- }
53
-
54
- download_task.run
55
- end
56
- end
57
- end
58
-
59
- # XXX: Should be moved to dlthread_spec.rb
60
- =begin
61
- if __FILE__ == $0
62
- t = DLThread.new(".")
63
- t.execute_urls(["https://www.google.co.kr/images/nav_logo195.png"])
64
- sleep 4
65
- end
66
- =end
1
+ # encoding: utf-8
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+
6
+ module Rbitter
7
+ class DLThread
8
+ def initialize(dlfolder, cacert_path, large_flag)
9
+ @dest = dlfolder
10
+ if not File.directory?(dlfolder)
11
+ warn "[dlthread] Given download location is not available for downloading."
12
+ warn "[dlthread] Fallback to current directory."
13
+ @dest = "./"
14
+ end
15
+
16
+ @cacert = cacert_path
17
+ if large_flag.nil?
18
+ @large_image = false
19
+ else
20
+ @large_image = large_flag
21
+ end
22
+
23
+ @pool = Array.new
24
+ end
25
+
26
+ def <<(url_array)
27
+ download_task = Thread.new {
28
+ url_array.each { |url|
29
+ uri = URI.parse(@large_image ? url + ":large" : url)
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ if uri.scheme.downcase == 'https'
32
+ http.use_ssl = true
33
+ http.ca_path = @cacert
34
+ end
35
+
36
+ http.request_get(uri.path) { |res|
37
+ case res
38
+ when Net::HTTPOK
39
+ fname = File.basename(url)
40
+
41
+ puts "[fetch] remote: #{uri.path} => local: #{fname}"
42
+ open(File.join(@dest, fname), "wb") { |file|
43
+ res.read_body { |chunk| file.write(chunk) }
44
+ }
45
+ end
46
+ }
47
+ }
48
+ }
49
+
50
+ @pool.push download_task
51
+ end
52
+
53
+ def job_cleanup
54
+ until @pool.empty?
55
+ puts "[dlthread] Thread forceful cleaning up [remains: #{@pool.length}]"
56
+
57
+ dlthrd = @pool.shift
58
+
59
+ if dlthrd.alive?
60
+ dlthrd.terminate
61
+ dlthrd.join
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
data/lib/rbitter/env.rb CHANGED
@@ -1,59 +1,62 @@
1
- # encoding: utf-8
2
-
3
- require "json"
4
-
5
- module Rbitter
6
- @internal_configuration = {}
7
-
8
- class ConfigurationFileError < StandardError; end
9
-
10
- module_function
11
- def env
12
- @internal_configuration
13
- end
14
-
15
- def env_reset
16
- @internal_configuration.clear
17
- end
18
-
19
- def config_load path
20
- open(path, 'r') { |io|
21
- @internal_configuration = JSON.parse(io.read)
22
- }
23
- true
24
- end
25
-
26
- def config_initialize json_path=nil
27
- @internal_configuration = JSON.parse("{}")
28
-
29
- unless json_path.nil?
30
- begin
31
- config_load(json_path)
32
- # TODO: Configuration validation
33
- return @internal_configuration
34
- rescue => e
35
- fail ConfigurationFileError, "Provided configuration can not be loaded. (#{json_path})"
36
- end
37
- end
38
-
39
- # Configuration default location
40
- # Priorities
41
- # 1. (current_dir)/config.json
42
- # 2. (current_dir)/.rbitter/config.json
43
- locations = Array.new
44
- base_locations = ["config.json", ".rbitter/config.json"]
45
-
46
- base_locations.each { |bloc|
47
- locations.push File.join(Dir.pwd, bloc)
48
- }
49
-
50
- for location in locations
51
- next unless File.file?(location)
52
- break if config_load(location)
53
- end
54
-
55
- if @internal_configuration.empty?
56
- fail ConfigurationFileError, "Can not load any configuration in [#{locations.join(', ')}]"
57
- end
58
- end
59
- end
1
+ # encoding: utf-8
2
+
3
+ require "json"
4
+
5
+ module Rbitter
6
+ @@env = Hash.new
7
+
8
+ class ConfigFileError < StandardError; end
9
+
10
+ def self.[](k)
11
+ @@env[k]
12
+ end
13
+
14
+ module_function
15
+ def env
16
+ @@env
17
+ end
18
+
19
+ def env_reset
20
+ @@env.clear
21
+ end
22
+
23
+ def env_validate?
24
+ # TODO: Add validator
25
+ true
26
+ end
27
+
28
+ def config_initialize json_path=nil
29
+ env_reset
30
+
31
+ unless json_path.nil?
32
+ begin
33
+ open(json_path, 'r') { |file|
34
+ @@env = JSON.parse(file.read)
35
+ }
36
+
37
+ return @@env if env_validate?
38
+ fail StandardError, "Invalid configuration"
39
+ rescue => e
40
+ fail ConfigFileError, "Load Failure (#{json_path}): #{e.to_s}"
41
+ end
42
+ end
43
+
44
+ # Configuration default location
45
+ # 1. (current_dir)/config.json
46
+ # 2. (current_dir)/.rbitter/config.json
47
+ locations = ["config.json", ".rbitter/config.json"]
48
+ locations.collect! { |base| File.join(Dir.pwd, base) }
49
+
50
+ for location in locations
51
+ next unless File.file?(location)
52
+ open(location, 'r') { |file|
53
+ @@env = JSON.parse(file.read)
54
+ }
55
+ break if env_validate?
56
+ end
57
+
58
+ if @@env.empty?
59
+ fail ConfigFileError, "Can not load any configuration in [#{locations.join(', ')}]"
60
+ end
61
+ end
62
+ end
@@ -1,46 +1,46 @@
1
- # encoding: utf-8
2
-
3
- require 'http/parser'
4
- require 'openssl'
5
- require 'resolv'
6
-
7
- module Twitter
8
- module Streaming
9
- class Connection
10
- MODIFIED = true
11
- attr_reader :tcp_socket_class, :ssl_socket_class
12
-
13
- def initialize(options = {})
14
- @tcp_socket_class = options.fetch(:tcp_socket_class) { TCPSocket }
15
- @ssl_socket_class = options.fetch(:ssl_socket_class) { OpenSSL::SSL::SSLSocket }
16
- end
17
-
18
- def stream(request, response)
19
- client_context = OpenSSL::SSL::SSLContext.new
20
- client = @tcp_socket_class.new(Resolv.getaddress(request.uri.host), request.uri.port)
21
- ssl_client = @ssl_socket_class.new(client, client_context)
22
- ssl_client.connect
23
- request.stream(ssl_client)
24
-
25
- loop {
26
- begin
27
- body = ssl_client.read_nonblock(1024) # rubocop:disable AssignmentInCondition, WhileUntilModifier
28
- response << body
29
- rescue IO::WaitReadable
30
- # The reason for setting 90 seconds as a timeout is documented on:
31
- # https://dev.twitter.com/streaming/overview/connecting
32
- r, w, e = IO.select([ssl_client], [], [], 90)
33
- if r.nil?
34
- # If timeout occurs
35
- ssl_client.close
36
- raise Twitter::Error::ServerError.new("Connection stalled")
37
- else
38
- # If socket is readable
39
- retry
40
- end
41
- end
42
- }
43
- end
44
- end
45
- end
1
+ # encoding: utf-8
2
+
3
+ require 'http/parser'
4
+ require 'openssl'
5
+ require 'resolv'
6
+
7
+ module Twitter
8
+ module Streaming
9
+ class Connection
10
+ MODIFIED = true
11
+ attr_reader :tcp_socket_class, :ssl_socket_class
12
+
13
+ def initialize(options = {})
14
+ @tcp_socket_class = options.fetch(:tcp_socket_class) { TCPSocket }
15
+ @ssl_socket_class = options.fetch(:ssl_socket_class) { OpenSSL::SSL::SSLSocket }
16
+ end
17
+
18
+ def stream(request, response)
19
+ client_context = OpenSSL::SSL::SSLContext.new
20
+ client = @tcp_socket_class.new(Resolv.getaddress(request.uri.host), request.uri.port)
21
+ ssl_client = @ssl_socket_class.new(client, client_context)
22
+ ssl_client.connect
23
+ request.stream(ssl_client)
24
+
25
+ loop {
26
+ begin
27
+ body = ssl_client.read_nonblock(1024) # rubocop:disable AssignmentInCondition, WhileUntilModifier
28
+ response << body
29
+ rescue IO::WaitReadable
30
+ # The reason for setting 90 seconds as a timeout is documented on:
31
+ # https://dev.twitter.com/streaming/overview/connecting
32
+ r, w, e = IO.select([ssl_client], [], [], 90)
33
+ if r.nil?
34
+ # If timeout occurs
35
+ ssl_client.close
36
+ raise Twitter::Error::ServerError.new("Connection stalled")
37
+ else
38
+ # If socket is readable
39
+ retry
40
+ end
41
+ end
42
+ }
43
+ end
44
+ end
45
+ end
46
46
  end