rbitter 0.0.8 → 0.1.0

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